iTerm2からの乗り換えで欲しかった「バッジ機能」
みなさん、Hyperをご存知ですか?Webの技術(Electron, Reactなど)で作られたターミナルアプリです。紹介記事だと「Hyperでイケてるターミナルをつかおう」や「愛着が湧く!HyperTermで作るMYターミナル!」があります。
最近Hyperがバージョン2になって流行の兆しがみえますが、個人的にiTerm2から乗り換えるにあたって足りない機能がありました。それはバッジ(Badges)機能です。リンク先を見てもらえばわかりますが、バッジ機能はターミナルの各セッション(タブとかペインとか)に応じてターミナル背景に文字列を大きく表示させる機能です。
インフラエンジニアは複数タブを開いてそれぞれ異なるサーバにsshで接続して作業することも多いかと思います。そんなとき、各Tabにバッジとしてサーバ名とかを表示させておくと、誤ったサーバでコマンドを発行してしまう、ということを防げて便利ですよね!
私もよく複数セッションを切り替えて作業をするのでこのバッジ機能は必須です。ですが、Awesome-Hyperなどでバッジ機能相当のプラグインを探してみましたが、残念ながら見つけられませんでした
プラグイン「Hyper Ink Badge」つくったよ
でも…、Reactの初歩的な知識さえあれば自分でプラグインを簡単にかけるのがHyperの素敵なところですね。 Electronとか知らなくても大丈夫。
そこで、バッジ機能をもつHyperプラグイン「Hyper Ink Badge」を作って公開してみました。私にとって初めて公開するnpmパッケージです
せっかくHTML+CSSでカンタンに装飾できるので、テキストだけでなく、画像を表示して画像の色を変えてセッションを識別するようにしました。画像とかフォントはうちの息子が大ファンの任天堂Splatoonっぽくしてみました。
画像はSVG画像であればなんでも良いので、下記のようにお気に入りの画像をつかうこともできます。2色まで指定の色を変化させられます。色の組み合わせはカラーピッカーでも選べますよ!
色を変える仕組みにSVG画像を利用したのですが、初めて扱ったのでつまづいたところを以下に書かせていただきます。
画像の色を変える
Hyperのターミナル上に画像を表示させるやり方はQiita記事「Hyperターミナルのプラグインを作ってみよう!」で書いたとおりですが、今回はPNG画像ではなくSVG画像を利用します。SVG画像はHTMLのようのな感じのマークアップとして埋め込まれます。そのためJavaScriptから普通のDOMと似たような感じで操作できるのです。これを利用して、JavaScriptで画像の色を変えています。
SVG画像を描く
今回使ったインクのSVG画像は自分で描いたのですが、SVG画像を描くには無料のWebサービス「Vectr」のオンライン版を利用させていただきました。無料ですが、今回のような簡単なイメージを描くにはまったく十分です。便利な時代ですね。色はJavaScriptで後から変更するので、仮として#010101
と#A0A0A0
で描いてます。
SVG画像を埋め込む
SVG画像の表示のさせ方はいろいろあるのですが、こちらの記事にありますとおり、表示方法によってはJavaScriptから操作できなくなってしまいます。JavaScriptから操作するには下記のようにobject要素のdataとして指定して表示する方法がおすすめです。
<div >
<object
id="svg-obj"
type="image/svg+xml" data="file:///ink.svg"
/>
</div>
JavaScriptでSVG画像を操作する
Vectrで描いたSVGをテキストエディタなどで開くと下記のようなマークアップ言語であることがわかると思います(あくまで雰囲気です)。
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<defs>...</defs>
<g>
<g>
<use xlink:href="#XXXXXXXX" opacity="1" fill="#010101" />
<g>
</g>
<g>
<use xlink:href="#YYYYYYYY" opacity="1" fill="#A0A0A0" />
</g>
</svg>
しかし、これが前述のobject要素の直接の子要素になるわけではありません。object内のデータは別ドキュメントのため、object要素のcontentDocument
プロパティを指定して取得する必要があります。
具体的には下のような感じで目的のuse要素にアクセスし、色を指定するfill属性を目的の色で書き換えていきます。
// object要素をIDで取得
const obj = document.querySelector('#svg-obj');
// object要素内のドキュメントから、fill属性をもつuse要素の配列を取得
const useElements = obj.contentDocument.querySelectorAll('use[fill]');
// 配列からuse要素を1つづつ取り出し、fill属性を赤色(#FF0000)に設定
useElements.forEach(e => {
e.setAttribute('fill', '#FF000');
});
SVG画像の扱いはタイミングに注意!
SVG画像が描画され終わった後にSVG画像の色を塗り替えたいので、ReactのライフサイクルメソッドであるcomponentDidMountの中で上記の塗り替え処理を行えばよいと思っていました。。
が、実際にやってみるとobject要素の中身がnullで取得できないー
これは、object要素内は別ドキュメントとして非同期で読み込まれ、componentDidMount呼び出し時にはまだobject要素の読み込みが完了していなかったことが原因でした。
そのため、下記のようにobject要素にonLoadイベントを設定し、onLoadイベントハンドラ内で色の塗り替えをおこなう必要がありました。
<div >
<object
id="svg-obj" type="image/svg+xml" data="file:///ink.svg"
onLoad={e => { /* ここで塗り替え処理 */ }}
/>
</div>
完成
こんな感じでSVG画像の扱いにつまづきつつも、なんとか完成できました。はじめてのnpm公開にはこちらの記事を参考にさせていただきました。
いろいろ至らないところはありますが、よろしければ本プラグインをお試しいただければ幸いです。
インストール
Hyper2系で導入されたHyperコマンドでインストールできます。
$ hyper install hyper-ink-badge
または、従来通り~/.hyper.js
のplugins配列に追加する方法でもインストールできます。
// ~/.hyper.js
plugins: ["hyper-ink-badge"]
簡単な使い方
- バッジをクリックすると色が変わります。
- "Command"キーを押しながらバッジをクリックするとカラーピッカーが開きます。
-
"ink-badge テキスト"
とターミナルで入力するとテキストをバッジテキストに設定できます。(引用符を忘れずに)
$ "ink-badge hello"
ホスト名とかも設定できますよ。
$ "ink-badge $(hostname)"
詳細はREADME.mdをごらんください