iTerm2 にステータスバーが付いた

α版の頃から追っかけてましたが、ついに 3.3.0 正式版がリリースされましたね！ 合わせて以下の記事も続編として公開しております。

* [iTerm2 のステータスバーで動くコンポーネント作る - Qiita](https://qiita.com/delphinus/items/9735f5ff07f5bcddbfc0)

---

<img width="1010" alt="スクリーンショット 2019-05-11 20.18.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/ad118698-5ba2-59ac-5006-30e355d9a215.png">

左から順に今いるディレクトリのブランチ名、実行中のプロセス、バッテリー・CPU・メモリ・ネットワークの状態、現在時刻が表示されています。それぞれの配置や並べるコンポーネントは GUI で簡単に設定できるようになっています（**Preferences > Profiles > Session > Status bar enabled > Configure Status Bar** から設定できます）。

<img width="629" alt="スクリーンショット 2019-04-14 17.55.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/632d877e-7fd6-9561-45bc-7cfc36a48fd5.png">

テキストの字体や色は自由に変更できますが、**Auto-Rainbow** をクリックすると、いい感じに色をつけてくれます。

## tmux Integration と組み合わせると吉

iTerm2 には **tmux Integration** という機能がありまして、これは tmux の Control Mode を使って tmux の各ウィンドウを iTerm2 のタブに統合できるのです。

* [iTerm2 2.0の新機能: tmuxの統合 - Qiita](https://qiita.com/ksato9700/items/ec14ed55187f626ddb84)

便利な機能なのですが、tmux のステータスバーが消えちゃうのが難点でした。これを iTerm2 のステータスバーで代替できるようになったのです。

## コンポーネントを自作する

もちろん、このコンポーネントは自作も可能です。実は、最初のスクリーンショットに挙げている機能のうち、バッテリーの状態は自作したコンポーネントで表示しています。

----

（2019/8/13 追記）

以下のコードは初めて自作コンポーネントに挑戦する人には複雑すぎるので、続編として簡単なコンポーネントの作り方をまとめました。Python 使わなくても自作が可能ですので参考にしてください。

* [iTerm2 のステータスバーで動くコンポーネント作る - Qiita](https://qiita.com/delphinus/items/9735f5ff07f5bcddbfc0)

----

（2019/5/11 追記）

バッテリー状態の表示は他の人にも便利そうだったので、[提案したところ][]、作者によってネイティブのものが実装されました（20190511 nightly build）。とはいえ、コンポーネントの自作手法としては相変わらず有用だと思われますので、以下のソースは残しておきます。

[提案したところ]: https://gitlab.com/gnachman/iterm2/issues/7776

----


iTerm2 には昔から AppleScript を使った API が用意されていたのですが、最近はこれが Python3 で書けるようになりました。

### 事前準備

Python Runtime はメニューからインストールする必要があります。**Scripts > Manage > Install Python Runtime** の順にクリックするとインストールされます。

* インストールパスは `~/Library/ApplicationSupport/iTerm2` です。
* すでにインストール済みの方は **Check for Updated Runtime** という表示になっているかもしれません。

ドキュメントやサンプルスクリプトはここにあります。

* [Python API — iTerm2 Python API 0.26 documentation](https://iterm2.com/python-api/index.html)
* [Status Bar Component — iTerm2 Python API 0.26 documentation](https://iterm2.com/python-api/examples/statusbar.html)

今回はこれを使ってバッテリー残量を表すコンポーネントを自作してみました。

* [delphinus/iterm2-battery-status: A component to show battery status for iTerm2's status bar feature](https://github.com/delphinus/iterm2-battery-status)

実際には Objective-C のコードを叩いたりなど、いくらか複雑なことをしていますが、ここには簡略化したものを載せておきます。これを `~/Library/ApplicationSupport/iTerm2/Scripts/Autolaunch/battery.py` というファイル名で保存すると GUI から選択できるようになります。

```python
#!/usr/bin/env python3

from iterm2 import Connection, StatusBarComponent, StatusBarRPC, run_forever
from iterm2.statusbar import Knob
from math import floor
from subprocess import CalledProcessError, check_output
from typing import List
import re

chars = ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]
thunder = "ϟ"
width = 5


async def main(connection: Connection) -> None:
    component: StatusBarComponent = StatusBarComponent(
        "Battery",
        "Show battery remaining",
        [],
        "|███▎  | 66% 2:34",
        30,
        "cx.remora.battery",
    )
    plugged = "🔌"

    @StatusBarRPC
    async def battery_status(knobs: List[Knob]) -> str:
        try:
            out: str = check_output(args=["/usr/bin/pmset", "-g", "batt"]).decode(
                "utf-8"
            )
        except CalledProcessError as err:
            return "`pmset` cannot be executed"

        matched1 = re.match(r".*; (.*);", out, flags=re.S)
        if matched1:
            status: str = matched1[1]
        else:
            return plugged

        matched2 = re.match(r".*?(\d+)%", out, flags=re.S)
        if matched2:
            percent: int = int(matched2[1])
        else:
            return plugged

        battery: str
        if status == "charged":
            battery = width * chars[-1]
        elif status == "charging":
            mid: int = floor(width / 2)
            battery = mid * " " + thunder + (width - mid - 1) * " "
        elif status == "discharging":
            unit: int = len(chars)
            total_char_len: int = len(chars) * width
            char_len: int = floor(total_char_len * percent / 100)
            full_len: int = floor(char_len / unit)
            remained: int = char_len % unit
            space_len: int = width - full_len - (0 if remained == 0 else 1)
            battery = chars[-1] * full_len
            if remained != 0:
                battery += chars[remained - 1]
            battery += " " * space_len
        else:
            battery = " " * width

        matched = re.match(r".*?(\d+:\d+)", out, flags=re.S)
        elapsed: str = matched[1] if matched and matched[1] != "0:00" else ""
        last_status: str = "{0} |{1}| {2:d}% {3}".format("🔋", battery, percent, elapsed)
        return last_status

    await component.async_register(connection, battery_status, timeout=None)


run_forever(main)
```

スクリプトの状況はメニューアイテム **Scripts > Manage > Console** から閲覧可能です。

<img width="777" alt="スクリーンショット 2019-04-14 18.02.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/e4abebfe-299e-4aaf-e6ef-dff2c3baa9d7.png">

表示イメージは [Code-Hex/battery][] を参考にしました。コンポーネントを作る際は async/await のような結構新し目の機能を使ってコーディングするようです。上記のサンプルスクリプトでは Type Annotation も頑張ってつけていますが必須ではありません。`battery_status` という、文字列を返す関数だけが肝で、あとはコピペでいけると思います。

[Code-Hex/battery]: https://github.com/Code-Hex/battery

## 最後に

最初はゴリゴリインターフェイスが変わって大変だったのですが、beta になってだいぶ仕様が固まってきたようです。公式の [Issues][] を随時確認して利用してください。

[Issues]: https://gitlab.com/gnachman/iterm2/issues