Python
iTerm2
python3

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

まだ今日(2018/09/11)現在では nightly のみですが、iTerm2 に tmux のようなステータスバーが付きました。

スクリーンショット 2018-09-10 10.18.20.png

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

スクリーンショット 2018-09-10 10.20.22.png

tmux Integration と組み合わせると吉

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

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

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

もちろん、このコンポーネントは自作も可能です。iTerm2 には昔から AppleScript を使った API が用意されていたのですが、最近はこれが Python3 に置き換えられています。

事前準備

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

  • すでにインストール済みの方は Check for Updated Runtime という表示になっているかもしれません。

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

今回はこれを使ってバッテリー残量を表すコンポーネントを自作してみました。以下のスクリプトを ~/Library/Application Support/iTerm2/Scripts/Autolaunch/battery.py というファイル名で保存すると GUI から選択できるようになります。

#!/usr/bin/env python3

import asyncio
from iterm2 import StatusBarComponent, Registration, run
from math import floor
import re
from subprocess import CalledProcessError, check_output

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


async def main(connection):
    component = StatusBarComponent(
        'Battery',
        'Show battery remaining',
        'Show remaining time for battery',
        [],
        '|███▎  | 66% 2:34',
        interval)

    async def coro(knobs):
        try:
            out = check_output(
                args=['/usr/bin/pmset', '-g', 'batt']).decode('utf-8')
        except CalledProcessError as err:
            return '`pmset` cannot be executed'
        try:
            status = re.match(r'.*; (.*);', out, flags=re.S)[1]
            percent = int(re.match(r'.*?(\d+)%', out, flags=re.S)[1])
        except:
            return '🔌'
        if status == 'charging':
            mid = floor(width / 2)
            battery = mid * ' ' + thunder + (width - mid) * ' '
        elif status == 'discharging':
            unit = len(chars)
            total_char_len = unit * width
            char_len = floor(total_char_len * percent / 100)
            full_len = floor(char_len / unit)
            remained = char_len % unit
            space_len = 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 = matched[1] if matched and matched[1] != '0:00' else ''
        return '{0} |{1}| {2:d}% {3}'.format('🔋', battery, percent, elapsed)


    await Registration.async_register_status_bar_component(
        connection, component, coro)

    future = asyncio.Future()
    await connection.async_dispatch_until_future(future)


run(main)

スクリーンショット 2018-09-11 18.13.32.png

こんな感じに設定すると

スクリーンショット 2018-09-10 10.29.19.png

こんな風に見えます。

表示イメージは Code-Hex/battery を参考にしました。コンポーネントを作る際は async/await のような結構新し目の機能を使ってコーディングするようです。coro という、文字列を返す関数だけが肝で、あとはコピペでいけると思います。

最後に

この機能は時期バージョンの 3.3 に入るのだと思われますがまだバグも多そうです。公式の Issues を確認して利用してください。