Abstract
VSCode使いのみなさん、スニペット機能使ってますか?
自作スニペットを作成して、コーディングを快適に行っている人もたくさんいることでしょう。ですが、通常の自作スニペットには致命的な弱点があります。それは静的なスニペットしか定義できないということです。
つまり、「正規表現などを利用して、動的にスニペットを定義することができない」ということです。
本記事では、正規表現も利用可能な、動的なスニペットを定義することができる HyperSnips を紹介します。
本記事の環境
- vscode 1.85.1
- hypersnips v0.2.8
- windows 10
ショートカットキーなどはwindowsで記載します。
Mac等のユーザーは適宜読み替えてください。
HyperSnips とは
HyperSnipsとは、動的スニペットを実現するVSCodeの拡張機能のひとつです。
次の公式のデモが使用例になっています。
ユーザーが入力した内容に応じて、スニペットが変化していることがわかります。
本記事では、動的(静的)スニペット: ユーザーの入力に応じて変化することができる(できない)スニペット として定義して用います。
動的スニペットは何の役に立つのかと言えば、例えばこのような遊び方ができます。
私はこのツイートからこの拡張機能を知りました。
usage
スニペットを定義するためには、スニペットファイルをもちろん記述する必要があります。
HyperSnips用のスニペットファイルは.hsnips
ファイルです。
コマンドパレットからHyperSnips: Open snippets directory
を実行して、設定ファイルを置いておくディレクトリを開けます...(と公式には書いてましたが自分の環境ではダメでした...?)
で、手動で探すなら下記になります。
- Windows: %APPDATA%\Code\User\hsnips(language).hsnips
- Mac: $HOME/Library/Application Support/Code/User/hsnips/(language).hsnips
- Linux: $HOME/.config/Code/User/hsnips/(language).hsnips
(language) は、言語が入ります。(どの言語で使うかに対応します。)
設定 hsnips.hsnipsPath
でこのディレクトリは別の場所に設定できます。
全ての言語モードで有効にするためには、all.hsnips
を作成しておきます。
スニペットファイルの文法
スニペットファイルは次のような形になっています。
# global ブロック
global
// javascript code
endglobal
# スニペット定義ブロック
context expression
snippet trigger "description" flags
body
endsnippet
global ブロックはcontextや、スニペットを動的に変化させるときに利用する関数を定義できます。
スニペット定義ブロック
何書いてるか意味わからないと思うので、まずはHello worldから。
Basic
snippet hello "hello" # トリガー 説明
hello, world # スニペット本体
endsnippet
保存してから適当なファイルでhelloと打つと、スニペットが登録されているはずです。
ここで、Aフラグを追加してみます。
A : Automatic snippet expansion; トリガーワードに一致したら即座にスニペットを展開するフラグです
snippet hello "hello" A # トリガー 説明 flag
hello, world # スニペット本体
endsnippet
適当なファイルで hello と打つと、変換候補を見るまでもなく一瞬でスニペットが呼ばれるようになります。
なんとなく基礎が見えてきましたか?
次に、スニペット本体にJavaScriptの結果を挿入する方法を紹介します。
snippet hello "hello" A
`` rv = 'hello, world.'.toUpperCase() ``
endsnippet
スニペット本体は、通常通りの文字列だけでなく、JavaScriptの実行結果を挿入することができます。
JavaScriptを挿入するには、下記の形で利用します。
`` (JavsScript コード) ``
ただし、表示されるのは自動変数 rv
(return value?) の値のみです。
なので基本的には `` rv = (hogefuga) `` の形が基本になります。
tab変数
それでは最初のデモを再現するスニペットを見てみます。
snippet box "Box" A
``rv = '┌' + '─'.repeat(t[0].length + 2) + '┐'``
│ $1 │
``rv = '└' + '─'.repeat(t[0].length + 2) + '┘'``
endsnippet
まず最初のスニペットの宣言部分。
box "Box" A
: trigger description flag
に対応。
- box : スニペットのトリガーワード
- "Box" : スニペットの説明
- A : Automatic snippet expansion; トリガーワードに一致したら即座にスニペットを展開する
もうここは簡単ですね。問題はスニペット本体でしょう。
``rv = '┌' + '─'.repeat(t[0].length + 2) + '┐'``
↑ t
って誰やねん?!??!
まず前提知識として、\$1, \$2, ... を知る必要があります。
これはタブステップのための文字列です。普通のスニペットを触ったことがある人は知っていると思います。
わからない人は、簡単なスニペットで試してみると良いかと思います。
snippet test "test" A
Hello, $1.
endsnippet
\$1として部分に、カーソルが飛んで入力を待つような形になったでしょう。
自動変数 t
: tabs はユーザーが入力中の内容 \$1, \$2 を格納してる変数です。
t[0]
は\$1に対応します。 (0-indexなのでややこしいですが...)
下記のスニペットを定義して試してみるとよくわかります。
# `t`: tabs. $1, $2, ...の値を配列として持っている
snippet ttest "ttest"
``rv = t[0]`` $1 $2 ``rv = t[1]``
endsnippet
この知識を持ったうえで、下記のスニペット定義とデモを見比べてみると、確かにユーザーが入力している内容に応じて上下のバーが増えていることがわかりますね。
snippet box "Box" A
``rv = '┌' + '─'.repeat(t[0].length + 2) + '┐'``
│ $1 │
``rv = '└' + '─'.repeat(t[0].length + 2) + '┘'``
endsnippet
正規表現によるトリガーの設定
メールアドレスの正規表現にマッチしたらトリガーするスニペット、なんてものも定義できます。
マッチした結果は自動変数 m
: match で受け取れます。
m[0]
はマッチした文字列本体がはいっており、m[1]
以降に()で囲った部分を取ってこれます。
# `m`: match. 正規表現のマッチ結果が入っているオブジェクト。
snippet `([\w\-._]+)@([\w\-._]+)\.[A-Za-z]+` "mtest"
m[0] = `` rv = m[0] ``
m[1] = `` rv = m[1] ``
m[2] = `` rv = m[2] ``
endsnippet
flag 一覧
A以外理解がまだできていません...のでひとまず公式からコピペ。
-
A: Automatic snippet expansion - トリガーにマッチしたら即座に展開する
-
i: In-word expansion* - By default, a snippet trigger will only match when the trigger is preceded by whitespace characters. A snippet with this option is triggered regardless of the preceding character, for example, a snippet can be triggered in the middle of a word.
-
w: Word boundary* - With this option the snippet trigger will match when the trigger is a word boundary character. Use this option, for example, to permit expansion where the trigger follows punctuation without expanding suffixes of larger words.
-
b: Beginning of line expansion* - A snippet with this option is expanded only if the tab trigger is the first word on the line. In other words, if only whitespace precedes the tab trigger, expand.
-
M: Multi-line mode - By default, regex matches will only match content on the current line, when this option is enabled the last hsnips.multiLineContext lines will be available for matching.
自動変数一覧
t
, m
以外にも、自動変数は存在します。
# `t`: tabs. $1, $2, ...の値を配列として持っている
snippet ttest "ttest"
``rv = t[0]`` $1 $2 ``rv = t[1]``
endsnippet
# `m`: match. 正規表現のマッチ結果が入っているオブジェクト。
snippet `([\w\-._]+)@([\w\-._]+)\.[A-Za-z]+` "mtest"
m[0] = `` rv = m[0] ``
m[1] = `` rv = m[1] ``
m[2] = `` rv = m[2] ``
endsnippet
# `w`: workspace. ワークスペースのURI文字列。
snippet wtest "wtest"
value: ``rv = w``
type : ``rv = (typeof w)``
endsnippet
# `path`: path of document. 今開いているファイルのURI文字列。
snippet pathtest "pathtest"
``rv = path``
endsnippet
global ブロック
スニペット本体のJSコードで呼び出せる関数や変数を定義できます。
後は、疑似的にVSCodeデフォルトのスニペットで使える組み込み変数を取得・利用するハックに利用したりします。
遊び方ギャラリー
ファイル上でサクッと計算をする
snippet `\beval ?(.*)=` "eval"
`` rv = eval(m[1]) ``
endsnippet
正規表現で = 手前の入力を受け取り、それをJSのeval
で評価しています。
まとめ
本記事では、動的スニペットを定義することができる拡張機能 HyperSnips を紹介しました。
ユーザーの入力に応じて、スニペットを変化させることで、色々と面白いことができることは間違いありません。
面白い拡張機能なのに、日本語はおろか英語の解説記事も特になさそうだったので、記事にしてみました。
もし面白いネタを思いついたら、ご自身で記事を書いてこの拡張機能を広めてください。
参考文献
多分唯一の日本語の紹介記事。だけど文法の解説とかはない。