Unyablog.

のにれんのブログ

systemd-nspawn 235 のコンテナで mlock を行う

systemd-nspwan で Elasticsearch の運用をしている。親のホストは ArchLinux なので新しい systemd が入ってくる。

ある日、 pacman -Syu して systemd を 234 から 235 に上げたら Elasticsearch がエラーで落ちるようになった。

[2017-10-19T07:10:13,955][WARN ][o.e.b.JNANatives         ] Unable to lock JVM Memory: error=1, reason=Operation not permitted
[2017-10-19T07:10:13,957][WARN ][o.e.b.JNANatives         ] This can result in part of the JVM being swapped out.
...
ERROR: [1] bootstrap checks failed
[1]: memory locking requested for elasticsearch process but memory is not locked

ソースコードを読むと、mlockall できてなくて落ちていた。

調べた結果、systemd 235 では systemd-nspawn の SystemCallFilter がホワイトリストになり、 mlockall ができなくなった のが原因だった。

解決法

コンテナに CAP_IPC_LOCK を与えるか、@memlock グループを許可する。

例えば、 foo というコンテナであれば、 /etc/systemd/nspawn/foo.nspawn

[Exec]
SystemCallFilter=@memlock # システムコールのみを許可する場合
Capability=CAP_IPC_LOCK # CAP_IPC_LOCK ごと許可する場合

を追加すれば良い。CAP_IPC_LOCK を与えた場合、 @memlock は自動で許可される。

詳細

systemd には SystemCallFilter というのがあって、呼び出せる system call を制限できる。

https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=

systemd-nspawn では、デフォルトでこの制限がかかっており、もともとはブラックリストで制限されていた。

systemd 235 からこのリストのデフォルトがホワイトリスト形式になり、CAP_IPC_LOCK がない場合は @memlock が許可されないようになった。

    * systemd-nspawn gained support for a new --system-call-filter= command
      line option for adding and removing entries in the default system
      call filter it applies. Moreover systemd-nspawn has been changed to
      implement a system call whitelist instead of a blacklist.

https://github.com/systemd/systemd/blob/c1719d8bc924ed59448616bd748671c5c7a66d93/NEWS

当該PRは以下*1

https://github.com/systemd/systemd/pull/6818/files#diff-524fbe88b6a5bf2879b598646b22ed25R70

Elasticsearch では bootstrap.memory_lock: true とした場合に mlockall を内部で実行するのだが、これが権限不足で失敗していた。

これを解決するには、capability を足すか @memlock を直接許可すれば良い。sytemd.nspawn 内でどちらも設定できる(「解決法」を参照)。

CAP_IPC_LOCK を付加すると、自動的に @memlock が付加される*2

Note that the applied system call filter is also altered implicitly if additional capabilities are passed using the --capabilities=.

https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html#--system-call-filter=

@memlock のみを許可した場合、コンテナに CAP_IPC_LOCK は与えられないので、 RLIMIT_MEMLOCK の範囲内で mlock できるようになる。

In Linux 2.6.8 and earlier, a process must be privileged (CAP_IPC_LOCK) in order to lock memory and the RLIMIT_MEMLOCK soft resource limit defines a limit on how much memory the process may lock.
Since Linux 2.6.9, no limits are placed on the amount of memory that a privileged process can lock and the RLIMIT_MEMLOCK soft resource limit instead defines a limit on how much memory an unprivileged process may lock.

https://linux.die.net/man/2/mlockall

*1:そもそも @memlock が作られたのはこの pr である

*2:capsh --print で持っている capability が見える

ldap: "additional info: objectClass: value #0 invalid per syntax"

slapd のセットアップで、ドキュメントに基づいて

# cat ~/admin.ldif
dn: dc=example,dc=com
objectClass: organization
objectClass: dcObject
dc: example
o: example

dn: cn=admin,dc=example,dc=com
objectClass: organizationalRole
cn: admin

ldapadd しようとしたら

# ldapadd -Y EXTERNAL -H ldapi:// -f ~/admin.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
adding new entry "dc=example,dc=com"
ldap_add: Invalid syntax (21)
        additional info: objectClass: value #0 invalid per syntax

というエラーが。

どうやらこれは objectClass が定義されていない時に出るエラーらしいが、 organizationdcObject も core.schema にあるし普通に定義されている。

おかしいな〜と思ってググってると同じような症状の人が。

https://www.openldap.org/lists/openldap-software/200110/msg00255.html

なるほど最後のスペース…と思って自分のものを見なおしてみたら含まれていた!!!!

どうやら openldap のドキュメントからそのままコピーしたらスペースが含まれてしまうらしい。気をつけましょう…。

↑の人はこれで2日潰れたらしい。2日は潰しませんでしたが2時間は潰しました…。

OCaml 入門途中の感想

最近京大の五十嵐先生が公開しているテキストを使って OCaml に入門していた。

このテキストは object まで行かないので入門したとは言えそうにないが、一区切り付いたので初心を記録する意味で雑に感想を書いておく。

主に Haskell を考えながら書いています、が Haskell もそんなに知らないです。

  • 型推論強くて良い
  • ref は便利ですね
    • 実用的!
  • type 、簡単に型推論してくれるのは良い
    • 変数名被ったらダメなのはつらい
  • IO 周りが楽なので良い
  • 対話環境は ghci の方が便利
  • セミコロン難しい
    • セミコロンの数で意味が変わる
    • リストよくカンマと間違える
    • 全体的に他の言語と違う作法の使い方をしているので引っかかりやすい
      • 考えられた結果なのか対抗心なのかは知らない
  • 括弧が面倒
    • Haskell における $ がほしい
    • 最近?入ったらしいけどコミュニティでは使われているのかな
    • OCaml でこんなことを言っているし LISP だとどうなることやら
  • 全体的な結合順に違和感がある
    • とりあえず括弧つければ直るが
    • 慣れの問題ではありそう
  • 遅延評価じゃないのは分かりやすい
    • ただ無限リストなどを作るのは大変になる

Haskell のほうが記号が多くて便利だし、楽しさはあると思う 。ラムダ式などはシュッと書きたい。

でもシンプルな構文により初見で分かりやすいのも長所である。どっちもどっちだ(まだ途中なので本当にシンプルなのか分からないけど)。

構文はあまり好きではない(嫌いでもない)けど、諸概念は良いし納得できるものも多いので難しいところだなあという感じ。

とりあえず Haskell の入門を再開したくなった。

予防線張りすぎでは?

まだ途中だからね!

その他

最近とにかく強い静的型付け言語を使いたい気持ちが高まっていて、python のような軽い文法で無いかなあと探していたら nim があった。また使いたい。

他の現代的な言語として Kotlin や Swift は最高だと思っているけど、エディタでサクッと書くには向いてない気がするんですよね。。

pipenv を使ってみる

Python における bundler といえば (超ざっくり言えば) virtualenv になるのですが*1、イマイチ使いにくいなと思っていたら pipenv というものが登場していた。ちょっと使ってみたのでメモ。

何するやつ

github.com

  • Pipfile(.lock)を元に良い感じに virtualenv 作成 + pip install してくれるやつ
    • Pipfile が使える便利 virtualenv wrapper という感じ
  • requests と同じ作者なので流行りそう

インストール

$ pip3 install pipenv

pipenv 環境作成

--three と指定すると Python3 だけの環境が作られる。--two も同様。

$ pipenv --three

Creating a Pipfile for this project...
Creating a virtualenv for this project...
⠋Already using interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/nonylene/.local/share/virtualenvs/pipenv-test-Iy4gNUM5/bin/python3
Also creating executable in /home/nonylene/.local/share/virtualenvs/pipenv-test-Iy4gNUM5/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /home/nonylene/.local/share/virtualenvs/pipenv-test-Iy4gNUM5

home 以下に virtualenv を作成されたくない場合は環境変数 PIPENV_VENV_IN_PROJECT を 1 にすれば .venv/ に作成される。この辺 --path で指定したいですね。

Pipfile

PipfileGemfile と同じ要領で使えるもの。toml で記述する。

[[source]]
verify_ssl = true
url = "https://pypi.python.org/simple"

pip 2.0 に向けて Pipfile の仮実装があり、これを使っているらしい。

Pipfile を主に実装した人は pipenv 作った人と同じ人だった。実装は落ち着いているけど pip に取り入れられるのはいつになるのか…。

github.com

パッケージ追加

pipenv install [package] でインストールするとパッケージが virtualenv にインストールされ、 Pipfile も置き換えてくれる。

$ pipenv install requests

Installing requests...
Collecting requests
  Downloading requests-2.13.0-py2.py3-none-any.whl (584kB)
Installing collected packages: requests
Successfully installed requests-2.13.0

Adding requests to Pipfile's [packages]...
P.S. You have excellent taste! ✨ 🍰 ✨

最後の行は作者のライブラリを入れた時に表示される。良い話。

$ cat Pipfile

[[source]]
verify_ssl = true
url = "https://pypi.python.org/simple"

[packages]
requests = "*"

lock

Pipfile.lock の生成。

$ pipenv lock

Locking [dev-packages] dependencies...
⠹Locking [packages] dependencies...
⠹Updated Pipfile.lock!

Pipfile.lock が生成される。 json らしい。人間が触るものではない。

$ cat Pipfile.lock

{
    "_meta": {
        "hash": {
            "sha256": "da2810af0c3b5333e0de2fce9bea2a228812e2014e5f5fe3b1c533badc6c24e4"
        },
        "requires": {},
        "sources": [
            {
                "url": "https://pypi.python.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "requests": {
            "hash": "sha256:1a720e8862a41aa22e339373b526f508ef0c8988baf48b84d3fc891a8e237efb",
            "version": "==2.13.0"
        }
    },
    "develop": {}
}%

activate virtualenv

$ pipenv shell を用いることで、virtualenv の activate された shell が起動する。

$ pipenv shell

Launching subshell in virtual environment. Type 'exit' or 'Ctrl+D' to return.

$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> requests.get("http://example.com")
<Response [200]>

run

$ pipenv run を使えば bundle exec っぽいことができる。これが便利。

$ pipenv run python3 -c "import requests; print(requests.get('http://example.com').text)"
<! doctype html>
...

ただ spawn していて新しいプロセス立ててるので pipe が上手くいってなく、パイプやリダイレクションができない。*2

追記: マージされたので今後は大丈夫になるでしょう。

github.com

その他

  • pipenv install をすると Pipfile.lock || Pipfile にもとづいてインストール・ virtualenv 構築してくれる。
  • その他アップデートコマンドなどもある。 ( README.md にだいたい書いてある )
  • 個人的にはインストールパスを記憶させてほしいけどまだできなさそう

Remember where the virtual environment was created · Issue #252 · kennethreitz/pipenv · GitHub

まとめ

Pipfile をお先に扱える + virtualenv の便利 wrapper として bundler 感覚で使えるのが良いと思いました。積極的に使っていこうと思います。

*1:ざっくり言い過ぎ

*2: exec してくれ~という issue たてた。

vim-keymaps でキーマップを簡単に切り替える

Vim のキーマップを簡単に切り替えられるプラグイン vim-keymaps を作りました。

github.com

下のように、複数のキーマップを設定することができ、簡単に切り替えることが出来ます。下では <C-k> でキーマップを切り替えています。

f:id:nonylene:20170322005931g:plain

今すぐダウンロード!

使い方

各キーマップを示すディクショナリを入れた配列を g:keymaps に設定し、後はキーマップを変更するためのキーを適当に割り当てる or コマンドを打つと使えます。詳しくは README.md で。

初めは接続されているキーボードでなんとか判別できないかなと考えましたが、 ssh 先でキーボードの配列を読むのは不可能なので手動で変更するようにしました。

経緯

私は普段、コーディングするアプリケーション上では数字と記号を入れ替えて使っています。例えば 1! になり、 !1 になります。

瞬時に括弧などが打てるので非常に便利に使っていたのですが、キーボードによって数字と記号の関係は異なるので、JIS 配列に合わせたマッピングを書いていると US キーボードでは崩壊してしまいます。記号だけではなく数字も入力できなくなるので非常に困る。

しかし、いろいろあって先月から US 配列の mac を使うことになりました。

mac のキーボードのみ使うなら .vimrc を書き直して US にマッピングし直せば良いのですが、普段接続して使っている HHKB はもちろん JIS 配列。本体キーボードで使う時だけ paste モードで使う運用をしていました。

しかし paste モードのままでは tab で(空白ではなく) tab が入力されたり、数字記号以外の各種マッピングが効かないなど困ります。

これはもうキーマップをキー配列ごとに変更するしかない。 せっかくの機会なのでプラグインを自分で作りました。

作成

vimプラグインを書くことは初めてでした。まともに vimscript も書いたこと無かったし。

書くときは下のサイトを参考にしたり、有名なプラグインのコードを見たりしながら書きました。

mattn.kaoriya.net

thinca.hatenablog.com

vim のドキュメントも結構読みました。読みやすい英語だったし、充実していて良かった。

vimscript, はじめは変数のスコープ定義が特殊だったりしてとっつきにくいですが、書けてくるとなかなか楽しいです*1

内部の話

設定ファイル

設定ファイルをどのように記述しようか迷って、結局 vimscript の配列を使うことにしました。ただ、 vimscript で配列や辞書型を複数行に記述しようとすると割りと面倒で、行の最初に \ を書くことで行の継続としなければなりません。あくまで行の継続なので、途中にコメントを書くことは出来ません…。( vim は一行ごとのコメントしかできない ) README を書いてる時にその事実に気づいて修正しました…。

マッピングの数は結構多いので、.vimrc に長大な配列を記述することになります。以下は自分の設定 を半分ぐらい省略したもの です。

let g:keymaps =  [
      \  {
      \    'name': 'JP',
      \    'keymap': {
      \      'noremap!': {
      \        '1': '!',
      \        '3': '#',
      \        '4': '$',
      \        '5': '%',
      \        '6': '&',
      \        '!': '1',
      \        '"': '2',
      \        '#': '3',
      \        '$': '4',
      \        '%': '5',
      \        '&': '6',
      \        "'": '7',
      \        '(': '8',
      \        ')': '9',
      \      },
      \      'imap': {
      \        '2': '<Plug>delimitMate"',
      \        '7': "<Plug>delimitMate'",
      \        '8': '<Plug>delimitMate(',
      \        '9': '<Plug>delimitMate)',
      \      },
      \      'cnoremap': {
      \        '2': '"',
      \        '7': "'",
      \        '8': '(',
      \        '9': ')',
      \      },
      \    },
      \  },
      \  {
      \    'name': 'PASTE',
      \    'paste': 1
      \  },
      \]

dein のように toml や json に分けれればなあと思ったのですが、 vimscript では標準ライブラリでシュッとパースすることはできません*2Python バインドでも使えば良いのですが、標準では入っていないことが多いので諦めました*3

map 方法

この g:keymap からマッピングするのは非常に素朴で、コマンド、左辺、右辺をスペースでつなげて放り込んでいます。なのでオプションを適当に入れても動くし、他のコマンドも打てちゃいます(キーマップ削除する時にエラー出ると思いますが)。

execute(l:cmd . ' ' . pair[0] . ' ' . pair[1])

特に立派な API は無かったのでこうしました。スペース等も自分で書くのと同じようにエスケープすれば動きます。

unmap 方法

デフォルト設定ではキーマップ変更時に以前のマッピングを削除しています。よって、マッピングと対になるアンマッピングコマンドを生成する必要があります。

map - Vim日本語ドキュメント

これも素朴に1文字目と2文字目をパースしています。

function! s:create_unmap(origin)
  let l:first = a:origin[0]
  if l:first is 'n'
    if a:origin[1] is 'o'
      " noremap
      let l:cmd = 'unmap'
    else
      let l:cmd = 'nunmap'
    endif
  elseif l:first is 'm'
    " unmap
    let l:cmd = 'unmap'
  else
    let l:cmd = l:first . 'unmap'
  endif

  if a:origin =~ '!'
    let l:cmd .= '!'
  endif

  return l:cmd
endfunction

paste モード関連

このプラグインは paste モードをサポートしているのですが、paste モードに入ってしまうとマッピングが全て外れるのでキーマップの切り替えができません。

set pastetoggle=<同じキー>

とすることで vim の機能によって paste モードを抜けることができるのですが、vim の機能で抜けているので次のキーマップには移動されず paste 用キーマップのままになってしまいます。

結局、autocmd を使うことによって paste モードの変更を検知して、フック上でキーマップを変更することにしました。

augroup keymaps
  autocmd!
  autocmd OptionSet paste call s:on_change_paste()
augroup END

function! s:on_change_paste()
  " sometimes v:option_new not working
  if g:keymaps_paste_auto_rotate && !&paste
        \ && get(keymaps#get_current_keymap(), 'paste', 0)
    " exit paste mode -> rotate
    call keymaps#rotate_keymap()
  endif
endfunction

lightline との連携

keymaps#get_current_keymap_name で現在のキーマップ名を取れるようにしたので、これを lightline に表示すれば現在のキーマップを簡単に見れます。最初に示した gif もそうしています。

paste モード変更時にもうまくするには lightline も paste を検知できるように autocmd を新しく設定する必要があります。

augroup local
  autocmd!
  autocmd OptionSet paste call lightline#update()
augroup END

その他

  • <SID> をキーマップに使っているとその <SID> は実行時のスクリプトに依存するのでプラグイン用のスコープになってしまう。
    • 同じような理由から、 <expr> をキーマップに使って関数を実行したい場合、呼び出す関数はグローバルでなければならなかったりする。
  • autoload/ に多くを分割したのだけど、最初のキーマップを設定するために起動時に読み込まれるので逆効果になっているかもしれない。
    • まあ重いスクリプトではないので大丈夫だと思うけど。
  • vimscript 、 endforend にできるが endfunctionend に省略できなかったりして面白い。
    • 結局全部フルで書いた。
  • l: をちゃんと書いてたけど実は暗黙的に設定されるっぽい。
  • vimscript の文法、だいぶ知れたのでこれからは .vimrc サクサク書けそう。
  • US キーボード、' が一瞬で打てるのが良いですね。でも数字と記号を入れ替えていると 0 に記号が割り当てられてしまって不便だったりする。

まとめ

これで今まで通り記号が簡単に入力できるようになりました。 「paste モードではないが記号のマップは削除したキーマップ」なども設定できて快適です。

Vim 最高!みんなも今すぐプラグイン書こう!!

*1:perl っぽいですね(?)

*2:雑に eval すると大体動くとは思う

*3:実は go とか使うとよかったりするのかな

zsh の色付き vcs_info を一行上の右端に出す

書いておかないとあとで分からなくなるのでメモ。

zsh 便利ですよね。vcs_info も便利ですよね。

vcs_info は名前の通り VCS の情報を出してくれる君です。

zshのターミナルにリポジトリの情報を表示してみる · けんごのお屋敷

本題

最近 zsh のプロンプトを以下のように変更しました。

f:id:nonylene:20170308024523p:plain

f:id:nonylene:20170308034716p:plain

ポイントは左にあった vcs_info が右に移動していることです。

RPROMPT 問題

vcs_info を右に移動するには vcs_info を単純に RPROMPT に移動させれば良いのでは?と思いますが、 RPROMPT はコマンド入力と同じ場所に表示されるため、パスやコマンドが長いと消えてしまいます。

f:id:nonylene:20170308025835p:plain

なら上に表示すれば良いのではと思って PROMPTRPOMPT を二行にしてみると、今度は表示されなくなってしまいます。

RPOMPTPROMPT が複数行に渡っても最終行と同じ行に表示され、改行ができません。

f:id:nonylene:20170308025857p:plain

一行上の右端に表示するには、 precmd 内でプロンプト復帰前に printf コマンドを用いて表示する必要がありました。

printf 問題

色がない場合、 printf で右端に表示するには以下のように行なえば良いです。

# プロンプト復帰前に呼ばれる
precmd() {
  # $prompt_right -> 右端に表示する文字列
  # $prompt_left -> 左端に表示する文字列
  printf "%s%*s" "$prompt_left" "$((${COLUMNS} - ${#prompt_left}))" "$prompt_right"
}

%*s ( * で幅を指定する) で長さに "$((${COLUMNS}-${#prompt_left}))" (全体の横の長さから $prompt_left の長さを引いたもの) を指定することによって、右寄せを実現しています。

しかし、これを色付きで行うと崩れてしまいます。

f:id:nonylene:20170308031636p:plain

なぜなら、ANSI エスケープシーケンスを含めた時、 $prompt_right 等にエスケープシーケンスが入って文字の長さが長くなり、 printf の幅空けや ${#prompt_left} による文字長の取得が狂うためです…!!!!😇

この時、ちゃんとシーケンスの長さを考慮してあげるとうまく表示できるようになります。

# 長さは適当に 10 にしている
printf "%s%*s" "$prompt_left" "$((${COLUMNS} - ${#prompt_left} + 10" "$prompt_right"

vcs_info の action 問題

vcs_info には actionformats という機能があり、コンフリクト解決やリベース時などにその action を表示することが出来ます。

自分は、acrion の中身は目立つように赤色にしています。なので action があると、シーケンスの文字数が増えて再び描画が崩れてしまいます。

f:id:nonylene:20170308032729p:plain

action があるかどうかは precmd 内部では分かりません。どうしようかなと調べていると、 vcs_info にはフック関数を設定できることが分かりました。

zsh.sourceforge.net

これを使って、フック関数の中で action があるかどうかを見れば、あとは変数にセットすることで precmd 内でも分かります。

最終的に以下の様になりました。

# tput でエスケープシーケンスを出力する
tput_normal=$(tput sgr0)
tput_yellow=$(tput setaf 3)
tput_red=$(tput setaf 1)
tput_cyan=$(tput setaf 6)

# vcs_info の設定
zstyle ':vcs_info:*' enable git hg
zstyle ':vcs_info:*:*' formats "[${tput_yellow}%r: %b${tput_normal}]"
zstyle ':vcs_info:*:*' actionformats "[${tput_yellow}%r: %b${tput_normal} (${tput_red}%a${tput_normal})]"

# hook 関数の設定
# メッセージが設定される直前
zstyle ':vcs_info:*+set-message:*' hooks vcs_info_hook
# VCS が存在しないとき
zstyle ':vcs_info:*+no-vcs:*' hooks no_vcs_hook

# VCS がないときのエスケープシーケンス長
novcs_color_len=$((${#tput_cyan} + ${#tput_normal}))
# VCS があるが action がないとき
noaction_color_len=$((${novcs_color_len} + ${#tput_yellow} + ${#tput_normal}))
# VCS があり、action もあるとき
action_color_len=$((${noaction_color_len} + ${#tput_red} + ${#tput_normal}))

# $prompt_color_len にシーケンス長を設定する
# VCS があるときの hook
function +vi-vcs_info_hook() {
  # action があるかどうかは hook_com[action] を見れば良い
  if [ -z ${hook_com[action]} ]; then
    prompt_color_len=$noaction_color_len
  else
    prompt_color_len=$action_color_len
  fi
}

# VCS がないときの hook
function +vi-no_vcs_hook() {
  prompt_color_len=$novcs_color_len
}

precmd() {
  vcs_info
  # 左寄せする文字列
  prompt_left="${tput_cyan}${USER}@${HOST}${tput_normal}"

  # 各情報を表示
  printf "\n%s%*s\n" "$prompt_left" \
    "$((${COLUMNS}-${#prompt_left}+${prompt_color_len}-1))" \
    "${vcs_info_msg_0_}"
}

# このプロンプトは printf 後に表示される (パスの部分)
PROMPT='%~ %(!. !root! #.>) '

途中で -1 しているのは、右寄せの最後に一文字空けるためです (wrap っぽく判別されるのを防ぐ)。

これで大体いい感じになりました。

f:id:nonylene:20170308034641p:plain

リサイズ問題

ウィンドウのサイズを変更した時、 RPROMPT では良い感じに右寄せ文字が移動してくれるのですが precmdprintf した文字列は再描画されないので残念ながらこのままではズレてしまいます。

f:id:nonylene:20170308135044p:plain

ここで、 TRAPWINCH を用います。

TRAPWINCH はサイズが変更された時に呼ばれる関数です。しかし、単純にここで printf すると、プロンプトの下に追加されてしまうので思った通りになりません。

f:id:nonylene:20170308135638p:plain

そこで、 printf していたのを PROMPT に変数として組み込み、TRAPWINCH 内で zle reset-prompt することによって PROMPT ごと再描画するようにします。

# $prompt_header を生成する
function generate_promopt_header() {
  prompt_header=$(printf "\n%s%*s" "$prompt_left" \
    "$((${COLUMNS}-${#prompt_left}+${prompt_color_len}-1))" \
    "${vcs_info_msg_0_}")
}

precmd() {
  vcs_info
  prompt_left="${tput_cyan}${USER}@${HOST}${tput_normal}"
  # printf をやめて $prompt_header を生成するのみとする
  generate_promopt_header
}

TRAPWINCH() {
  # 長さが変わったので $prompt_header を再生成   
  generate_promopt_header
  # プロンプト再描画
  zle reset-prompt
}

# $prompt_header を改行前に含める
PROMPT='${prompt_header}
%~ %(!. !root! #.>) '

こうして、幅が変わっても無事に再描画されるようになりました。

f:id:nonylene:20170308140603g:plain

残る問題

実はまだ問題が残っていて、ブランチ名等が日本語等のときにカラム幅が合わなくなり、はみ出してしまいます。

f:id:nonylene:20170308140749p:plain

wc -c 等を使ってもバイト数だから上手くいかないし、どうしたものか…。

まあ日本語で git ブランチや git 用のディレクトリ作ることはそうそう無さそうなので今回は放置。

全体的な zshrc はこちらです。(大したことやってない)

dotfiles/.zshrc at master · nonylene/dotfiles · GitHub