本当は優しい Powerline(2日目 セグメント追加編)


“編”とか書きましたが、続くんですかね。

iTerm2 + tmux + vim + Powerline

iTerm2 + tmux + vim + Powerline

前回は Python 版の Powerline についてレポートしましたが、相も変わらず怒濤の勢いで更新されておりまして目が離せません。

前回の記事から更新された点で、前バージョンである vim-powerline紹介記事)からの改善点をいくつか挙げますとこんな感じです。

テーマやカラースキームがリアルタイムに反映されるようになった
vim や tmux の再起動を待つ必要がありません。
表示内容によってグラデーションがかかる表示が出来るようになった
たとえば CPU 使用率のセグメントでは、0% のとき緑色、50% のときオレンジ色、100% のときは赤色、など、色が自然に変わります。
powerline-daemon によって Powerline プロセスが永続化できるようになった
厳密には、これは別のプロダクトなのですが、powerline-daemon を使うと、Powerline プロセスを毎回起動する際のオーバーヘッドがなくなり、CPU 使用率を削減できます。

最初から用意されているセグメントは基本的なものばかりですので、欲しいセグメントはどんどん自分で書いて追加していきましょう。今日はその辺をまとめたいと思います。

config.json の書式について

基本的なことはドキュメントにまとめられていますので、ソコにないことを書いていきますね。

今僕が使っているものを見ていただくのが一番簡単なんですが、抜粋して説明しましょう。

"common": {

    ...

    "paths": [
        "~/.config/powerline"
    ],
    "log_file": "/tmp/powerline.log"
}

paths は自作のモジュールを収めるためのディレクトリ、log_file は名前の通りログファイル名です。pl.warn()self.warn() などとすることでセグメントのデバッグ情報を吐くことが出来ます。

"ext": {

    ...

    "vim": {
        "colorscheme": "custom",
        "theme": "custom",
        "local_themes": {
            "cmdwin": "cmdwin",
            "help": "help",
            "quickfix": "quickfix",
            "ext.vim.matcher.unite": "unite"
        }
    },

    ...

}

ext には vim, tmux, ipython といった、各アプリケーションごとの設定を書いていきます。基本的にカラースキームとテーマの設定だけなんですが、アプリケーションによっては local_themes というオプションが設定できます。この件は次回解説します。

セグメントの追加

ではお待ちかね。セグメントの追加についてです。手順は結構複雑ですが、以下のような感じです。

  1. 新しいセグメントの場合、自作の Python モジュールを書く。
  2. それに対応したカラースキームを定義する。
  3. テーマファイルに追記する。

既存のセグメントを流用するだけなら 3 番だけで大丈夫です。今回はオリジナルのセグメントを作成してみます。

カーソル下の文字の文字コードを表示する

Python モジュールの作成

文字コードの表示例

文字コードの表示例

まず、カーソル下の文字の文字コードを表示してみます。これはその昔、vim-powerline に実装したものを Python で書き直したものです。

import re
import vim

def _do_ex(command):
    '''Execute Ex command.

    Execute Ex command from args and return results string.
    '''
    vim.command('redir => tmp_do_ex | silent! {0} | redir END'.format(command))
    return vim.eval('tmp_do_ex')

def get_char_code(pl):
    '''Return charcode and char itself on cursol position.

    port from vim-powerline
    '''
    info = _do_ex('ascii')

    if info == '' or info.find('NUL') != -1:
        return 'NUL'

    enc = vim.eval('&encoding')
    fenc = vim.eval('&fileencoding')
    info = info.decode(enc)
    nrformat = u"'{}' " + (u'{:#06x}' if fenc == 'utf-8' else u'{:#04x}')

    # Get the character and the numeric value from the return value of :ascii
    # This matches the two first pieces of the return value, e.g.
    # "<F>  70" => char: 'F', nr: '70'
    m = re.compile(r'<(.+?)>¥s*(¥d+)').search(info)

    if m == None:
        return 'NUL'
    else:
        char, code = m.groups()
        code = int(code)
        return nrformat.format(char, code)

ascii コマンドの返値をパースして文字コードを返しています。:ascii は Ex コマンドなので返値を得るのにエラい苦労して(_do_ex() 関数)います。これ何とかなんないんですかね?

カラースキームの追加

セグメントの文字列を返す関数を作ったので、次にカラースキームを定義します。

{
    "name": "Solarized Light",
    "groups": {

        ...

        "get_char_code":            { "fg": "lightskyblue4", "bg": "lightyellow" },

        ...

    }
}

僕は vim と tmux に Solarized解説記事)というカラースキームを使っています。Powerline にも Solarized 用のカラースキームが定義されており、利用可能な色が colors.json に定義されています。

上記の設定では、薄い黄色の背景に、水色と灰色の中間のような色で文字列を表示しています。

テーマファイルに追記する

最後に、テーマファイルに追記すれば完成です。

{

    ...

    "segments": {

        ...

        "right": [

            ...

            {
                "name": "get_char_code",
                "module": "ext.vim.segments.custom",
                "exclude_modes": ["nc"],
                "priority": 50
            },

            ...

桁数をバイト数と文字数で表示する

桁数の表示例

桁数の表示例

次に、もう少し複雑なセグメントを定義してみます。

Powerline は標準で、カーソルのある場所の桁数をバイト数で表示するもの(vim の 'statusline' でいう %c)、及び、画面上の何桁目にあるのか(同じく、%v)、については用意されているのですが、%V に当たるものが用意されていません。

█←これがカーソルのある場所とする
%c %v %V
abcdef█ '7' '7' ''
あああ█ '10' '7' '-7'
abcあf█ '8' '7' '-7'

%c%v が異なるときだけ、%V に値が入るんですね。こんなセグメントを書いてみます。

Python モジュールの作成

from powerline.bindings.vim import vim_get_func, getbufvar

vim_funcs = {
        'col': vim_get_func('col', rettype=int),
        'virtcol': vim_get_func('virtcol', rettype=int),
        }

def col_current_virt(pl, gradient=True):
    '''Return the current cursor column.

    Since default 'col_current()' function returns current OR virtual column
    only, this function returns current AND virtual columns.
    '''
    virtcol = str(vim_funcs['virtcol']('.'))
    col = str(vim_funcs['col']('.'))
    res = [{'contents': col if virtcol == col else col + '-' + virtcol,
        'highlight_group': ['virtcol_current', 'col_current']}]
    if gradient:
        textwidth = int(getbufvar('%', '&textwidth'))
        res[-1]['gradient_level'] = min(int(virtcol) * 100 / textwidth, 100) ¥
                if textwidth else 0
        res[-1]['highlight_group'].insert(0, 'virtcol_current_gradient')
    return res

get_char_code() では単なる文字列を返しただけでしたが、今度は配列を返しており、更に、引数 gradient によって返値の形式が異なります。

# gradient == False の場合
[{
    'contents': '10-7',
    'highlight_group': ['virtcol_current', 'col_current'],
}]

# gradient == True の場合
[{
    'contents': '10-7',
    'gradient_level': 8,
    'highlight_group': ['virtcol_current_gradient', 'virtcol_current', 'col_current'],
}]

gradient_level が追加され、highlight_group も変わってますね。これが最初に書きました、グラデーション表示の設定です。

現在カーソルが文章中のどの辺りにあるのかを textwidth オプション の値から百分率で導出し、それを gradient_level として返しています。

カラースキームの追加

カラースキームファイルには、上記の highlight_group に対応した行が追加されています。

{
    "name": "Solarized Light",
    "groups": {

        ...

        "virtcol_current_gradient": { "fg": "GREEN_Orange_red", "bg": "royalblue5" },
        "col_current":              { "fg": "lightyellow", "bg": "darkgreencopper" }
    }
}

GREEN_Orange_redcolors.json に定義されています。

テーマファイルに追記する

最後に、テーマファイルへ追記します。

{
    "segment_data": {

        ...

        "col_current_virt": {
            "args": { "gradient": true }
        }
    },
    "segments": {
        "left": [

            ...

        ],
        "right": [

            ...

            {
                "name": "col_current_virt",
                "module": "ext.vim.segments.custom",
                "draw_soft_divider": false,
                "priority": 20,
                "before": ":",
                "width": 3,
                "align": "l"
            }
        ]
    }
}

args パラメータに引数と値の組を設定しています。ついでに他の項目も説明しておきましょう。

name
セグメント名
module
セグメントを定義したクラス。標準のセグメントの場合は不要。
draw_soft_divider
soft_divider(「>」みたいなの)を隣のセグメントとの間に表示するかどうか。
priority
優先度。画面幅が狭くなると、この数値の低い順に表示されなくなる。
before
セグメントの文字列の前に付加する。after もある。
width
最低限確保する表示幅。
align
表示場所。l は左寄せ、r は右寄せ、c なら中央。

終わりに

たかが 2 つのセグメントを追加するだけで大騒動ですね。ちょっと複雑すぎるような気もするんですが、vim の枠組みの中で、カスタマイズ性を追求するには仕方ないのかもしれません。これでも、vim-powerline の頃よりは簡単になったんですよ。

次回は、特定のウィンドウにだけ別のステータスラインを出すようにしてみます。

コメントを残す