Nushellのカスタムエラー・イベント処理など
カスタムエラー処理
Nushellではエラーメッセージ情報が豊富で、誤りがどこにあるかわかりやすく表示されます。
例えば、ストリームデータの型チェックを行っており、下記のようにどこのコマンドでどのようなエラーが発生したかわかりやすく表示されます。
❯ ls | decode utf8
Error: nu::shell::unsupported_input (link)
× Unsupported input
╭─[entry #23:1:1]
1 │ ls | decode utf8
· ───┬──
· ╰── non-binary input
╰────
これをカスタムコマンドで、エラー処理を実行するにはerror make
コマンドでカスタムエラーを作成できます。
下記はカスタムコマンドで引数チェックでカスタムエラーを表示したものです。ここでは引数のエラー箇所が表示されています。これを実現するため、metadata
コマンドを使いストリームデータ内の情報を取得します。
❯ s1 my-command 101
Error:
× なんてこった
╭─[entry #22:1:1]
1 │ s1 my-command 101
· ─┬─
· ╰── この引数が100を超えたぞ
╰────
具体的には、下記コードのようにまず引数からmetadata
コマンドでエラーメッセージで表示されるエラー箇所に表示すべき範囲を取得します。
# s1.nu
export def my-command [x:int] {
if $x > 100 {
let span = (metadata $x).span;
error make {
msg: "なんてこった",
label: {
text: "この引数が100を超えたぞ",
start: $span.start,
end: $span.end
}
}
}
}
範囲取得は下記部分で行い、error make
コマンドへ渡しています。
let span = (metadata $x).span;
カスタムイベント
カスタムイベントを定義すると、補完時などで独自処理を行うことができます。下記はCtr-t
でカレントディレクトリ以下のディレクトリ一覧をfzf
に渡して、その選択結果をcd
の引数にするようにしたものです。
{
name: change_dir_with_fzf
modifier: CONTROL
keycode: Char_t
mode: emacs
event:[
{ edit: Clear }
{ edit: InsertString,
value: "cd (ls | where type == dir | each { |it| $it.name} | str join (char nl) | fzf | decode utf-8 | str trim)"
}
{ send: Enter }
]
}
上記のkeywordを下記で説明します。
- name: $config.keybindings で簡単に参照できる、キーバインディングの一意の名前
- modifier: キーバインドのキー修飾子。オプションは次のとおりです。
none
control
alt
shift
control | alt
control | alt | shift
- keycode: これは、押されるキーを表します
コントロールキー使用のイベント一覧
すでにキーアサインのほとんどが使用済みですね。
❯ keybindings default | where modifier == CONTROL && code =~ 'Char\(\'\w\'\)' | reject mode
╭────┬──────────┬───────────┬──────────────────────────────────────────────────────────╮
│ # │ modifier │ code │ event │
├────┼──────────┼───────────┼──────────────────────────────────────────────────────────┤
│ 1 │ CONTROL │ Char('a') │ Edit([MoveToLineStart]) │
│ 2 │ CONTROL │ Char('b') │ UntilFound([MenuLeft, Left]) │
│ 3 │ CONTROL │ Char('c') │ CtrlC │
│ 4 │ CONTROL │ Char('d') │ CtrlD │
│ 5 │ CONTROL │ Char('e') │ UntilFound([HistoryHintComplete, Edit([MoveToLineEnd])]) │
│ 6 │ CONTROL │ Char('f') │ UntilFound([HistoryHintComplete, MenuRight, Right]) │
│ 7 │ CONTROL │ Char('g') │ Edit([Redo]) │
│ 8 │ CONTROL │ Char('h') │ Edit([Backspace]) │
│ 9 │ CONTROL │ Char('k') │ Edit([CutToEnd]) │
│ 10 │ CONTROL │ Char('l') │ ClearScreen │
│ 11 │ CONTROL │ Char('n') │ UntilFound([MenuDown, Down]) │
│ 12 │ CONTROL │ Char('o') │ OpenEditor │
│ 13 │ CONTROL │ Char('p') │ UntilFound([MenuUp, Up]) │
│ 14 │ CONTROL │ Char('r') │ SearchHistory │
│ 15 │ CONTROL │ Char('t') │ Edit([SwapGraphemes]) │
│ 16 │ CONTROL │ Char('u') │ Edit([CutFromStart]) │
│ 17 │ CONTROL │ Char('w') │ Edit([CutWordLeft]) │
│ 18 │ CONTROL │ Char('y') │ Edit([PasteCutBufferBefore]) │
│ 19 │ CONTROL │ Char('z') │ Edit([Undo]) │
│ 20 │ CONTROL │ Char('a') │ Edit([MoveToLineStart]) │
│ 21 │ CONTROL │ Char('c') │ CtrlC │
│ 22 │ CONTROL │ Char('d') │ CtrlD │
│ 23 │ CONTROL │ Char('e') │ UntilFound([HistoryHintComplete, Edit([MoveToLineEnd])]) │
│ 24 │ CONTROL │ Char('l') │ ClearScreen │
│ 25 │ CONTROL │ Char('n') │ UntilFound([MenuDown, Down]) │
│ 26 │ CONTROL │ Char('o') │ OpenEditor │
│ 27 │ CONTROL │ Char('p') │ UntilFound([MenuUp, Up]) │
│ 28 │ CONTROL │ Char('r') │ SearchHistory │
│ 29 │ CONTROL │ Char('a') │ Edit([MoveToLineStart]) │
│ 30 │ CONTROL │ Char('c') │ CtrlC │
│ 31 │ CONTROL │ Char('d') │ CtrlD │
│ 32 │ CONTROL │ Char('e') │ UntilFound([HistoryHintComplete, Edit([MoveToLineEnd])]) │
│ 33 │ CONTROL │ Char('h') │ Edit([Backspace]) │
│ 34 │ CONTROL │ Char('l') │ ClearScreen │
│ 35 │ CONTROL │ Char('n') │ UntilFound([MenuDown, Down]) │
│ 36 │ CONTROL │ Char('o') │ OpenEditor │
│ 37 │ CONTROL │ Char('p') │ UntilFound([MenuUp, Up]) │
│ 38 │ CONTROL │ Char('r') │ SearchHistory │
│ 39 │ CONTROL │ Char('w') │ Edit([BackspaceWord]) │
├────┼──────────┼───────────┼──────────────────────────────────────────────────────────┤
│ # │ modifier │ code │ event │
╰────┴──────────┴───────────┴──────────────────────────────────────────────────────────╯
- mode: emacs、vi_insert、vi_normal (単一の文字列またはリスト。例: [vi_insert vi_normal])
- event: キーバインドによって送信されるイベントのタイプ。オプションは次のとおりです。
send
edit
until
次のキーバインドは、エンジンに送信される一連のイベントの例です。最初にプロンプトをクリアし、文字列を挿入してからその値を入力します
{
name: change_dir_with_fzf
modifier: CONTROL
keycode: Char_t
mode: emacs
event:[
{ edit: Clear }
{ edit: InsertString,
value: "cd (ls | where type == dir | each { |it| $it.name} | str join (char nl) | fzf | decode utf-8 | str trim)"
}
{ send: Enter }
]
}
以前のキーバインドの欠点の 1 つは、挿入されたテキストがバリデーターによって処理され、履歴に保存されるため、キーバインドが少し遅くなり、コマンド履歴に同じコマンドが入力されることです。そのため、executehostcommand
タイプのイベントがあります。次の例は前の例と同じことをより簡単な方法で行い、単一のイベントをエンジンに送信します。
{
name: change_dir_with_fzf
modifier: CONTROL
keycode: Char_y
mode: emacs
event: {
send: executehostcommand,
cmd: "cd (ls | where type == dir | each { |it| $it.name} | str join (char nl) | fzf | decode utf-8 | str trim)"
}
}
par-each
each
のブロックをを並列実行するものです、複数スレッドで並行動作させるため高速化が望めます。
実例をいかに示します。CPU性能やディレクトリ数など環境の影響はあるものの、高速に実行できることがわかります。
❯ benchmark {ls | where type == dir | each { |it|
∙ { name: $it.name, len: (ls $it.name | length) }
∙ }
∙ }
90ms 656µs 150ns
❯ benchmark {ls | where type == dir | par-each { |it|
∙ { name: $it.name, len: (ls $it.name | length) }
∙ }
∙ }
11ms 593µs 827ns
Shells in shells
1 つのディレクトリで作業するのが一般的ですが、同時に複数の場所で作業すると便利な場合があります。このために、Nushell はシェル
の概念を提供します。名前が示すように、複数のシェルで、作業ディレクトリなどの間をすばやく移動できます。pushd
, popd
のようなものを実現します。
開始するには、enter
コマンドにディレクトリを入力しましょう。
/home/jonathant/Source/nushell(main)> enter ../book
/home/jonathant/Source/book(main)> ls
────┬────────────────────┬──────┬────────┬─────────────
# │ name │ type │ size │ modified
────┼────────────────────┼──────┼────────┼─────────────
0 │ 404.html │ File │ 429 B │ 2 hours ago
1 │ CONTRIBUTING.md │ File │ 955 B │ 2 hours ago
2 │ Gemfile │ File │ 1.1 KB │ 2 hours ago
3 │ Gemfile.lock │ File │ 6.9 KB │ 2 hours ago
コマンド実行後の状態は、ディレクトリの変更に似ています。ディレクトリを変更する代わりに、ディレクトリにジャンプしてその中で作業することができます。 shells
コマンドを使用して、有効なシェルの現在のディレクトリを一覧表示できます。
/home/jonathan/Source/book(main)> shells
───┬────────┬────────────┬─────────────────────────
# │ active │ name │ path
───┼────────┼────────────┼─────────────────────────
0 │ false │ filesystem │ /home/jt/Source/nushell
1 │ true │ filesystem │ /home/jt/Source/book
2 │ false │ filesystem │ /home/jt/Source/music
───┴────────┴────────────┴─────────────────────────
shells
コマンドは、現在アクティブな 3 つのシェルがあることを示しています。元の「nushell」ソース ディレクトリと、この新しい「book」ディレクトリです。
n
、p
、g
のショートカット ("next"、"previous"、"goto" の略) コマンドを使用して、これらのシェル間(ディレクトリ)をジャンプできます。
/home/jonathant/Source/book(main)> n
/home/jonathant/Source/nushell(main)> p
/home/jonathant/Source/book(main)> g 2
/home/jonathant/Source/music(main)>
ディレクトリが変更されていることがわかりますが、作業していた以前のディレクトリにいつでも戻ることができます。これにより、同じセッションで複数のディレクトリで作業できます。
exit
コマンドを使用して、入力したシェルを終了できます。これが最後に開いたシェルである場合、Nu は終了します。
--now
フラグを終了コマンドに渡すことで、複数のシェルがアクティブであっても、いつでも Nuを終了できます。
Discussion