標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

Windows のコンソールと、Unicode のサロゲートペアとゼロ幅文字

(2019.03.20 追記:Windows7Chrome では Unicode の合字が表示されないので、合字を画像に変えました)

👨‍👩 という文字を nyagos に貼り付けると、文字化けするだけでなく、カーソル位置もおかしくなるという問題

これは2つ問題がある

  • 👨‍👩 という文字が合字である
    • 1コードポイントではなくて、「👨(U+1F468)」+ゼロ幅接合子(U+200D)+ 「👩(U+1F469)」という3コードポイントからなっている
    • nyagos は合字を想定していない
    • ゼロ幅接合子の単体の桁数は0桁である点は、go-runewidth で把握できている
  • 👨と👩各文字ともサロゲートペアで表現される
    • Windowsコマンドプロンプトサロゲートペアな Unicode の出力をサポートしていない
      • そもそもコンソールバッファのひとマスが16bitしか確保されていないんだよな
    • 実際に出力すると1文字なのに2文字と認識されたり、カーソル位置がおかしくなってしまう。
    • フォントが足りる・足りないの問題ではない

ということで「サロゲートペアな文字」も「ゼロ幅文字」もきちんと表示するのは無理とわかったので、nyagos の一行入力では <UUUUU> みたいな形で表現するようにした。^I とかと同じ扱いで、表示上は5桁になっているがバックスペース1回で削除できるし、コマンドに出力する時は元の文字に直して出力される (一応、この動作は --output-surrogate-pair というオプションで抑制する(=そのまま表示するようにする)ことが出来るようにはしておいた)

将来的にこういった文字もコマンドプロンプトで表示できるといいんですが。絵文字が使えないというのはさびしいので…(というか、将来、実用上も困るシーンが出てきそう)

本件、ご協力いただいた tsuyoshicho さん、ありがとうございました。

nyagos と文字の幅問題

文字の幅については nyagos において頭が痛い問題です。

mattn/go-runewidth でカーソルを戻す時に発行するバックスペースの個数を算出しています。ですが、Windows のコンソールでの実際の文字幅は Unicode の規格のものと微妙に違うので、文字によってはカーソル位置が狂ってしまうという問題が発生します。

以前は文字が出力する度にカーソル位置を計測して文字幅を記録するような対応をしていたのですが、速度的に不利であるので、現在は無効にしています。(仮想マシン内ではコンソールがすごく遅くなる時があるので、速度を下げる要因を少しでも減らしたかったんですね。これをやると出力のバッファリングもやりにくくなるので)

また、Linux 対応に伴って、せっかく入力は mattn/go-tty 、出力は mattn/go-colorable と I/O を外出しているわけですので、あまり直接コンソールを触るのははばかられるというのもありました。 (まぁ、場合分けすればいいだけの話なのですが)

ということで、理想的な解決案不在のまま、文字幅問題はずっと先送りしているという状況です。Windowsのコンソールでの文字幅の決定ルールが明確に分かれば、問題は綺麗に片付くのですがね…マイクロソフトの人、教えてくれないかなぁ…

ホームディレクトリ以下にある空白入りのファイル名の補完は難儀だなぁ

~\Share\Program Files というパスを補完する場合を考えよう。

bash だと ~/Share/Program\ Files と補完する。 UNIX だとワード単位で独立した引数としてコマンドに引き渡されるので「/home/USERNAME/Share/Program Files」をそのまま引き渡すことができる。

だが、Windows の場合、以下のような問題があるため、UNIX のようなことはできない

  • \ディレクトリ区切り記号と認識されるので、エスケープ記号として使うことはできない
  • Windows では全引数が1文字列として引き渡されるので、空白をそのままわたすと、パラメータ区切りと誤認されてしまう

結果、引数全体を二重引用符で囲むのが妥当となる。

が、ここで ~ が問題となってくる。~ を二重引用符の中に入れるとまずいのだ。~ が %USERPROFILE% への置換対象外になってしまうのだ。

というわけで、現在の nyagos では苦肉の策として ~"\Share\Program Files" へ変換している(nyaos 3000 では ~\"Share\Program Files" だったが、\" だと \ が二重引用符のエスケープと読めてしまうので、やめたのだ)

なんだか、かっこわるいなぁ。

一案として、Windowsエスケープ文字である ^ を使って ~/Share/Program^ Files とするのも考えたが… すべてのコマンドが^ を理解してくれるとも限らんのよね。くまったなぁ

Lua のかわりに anko を組み込んだ anko-nyagos を作ってみました。

nyagos は lua53.dll → GopherLua への切り替えの際、組み込み言語のインターフェイスをある分離して、切り替えられるようにしたのですが、それを利用して、mattn 先生が開発された、Go製スクリプト言語 anko を組み込んだ anko-nyagos を試験的に作成してみました。

今のところできるのは下記だけです。

  • 起動時に、anko-nyagos.exe と同じフォルダーの nyagos.ank を実行する
  • コマンドとしては以下を使えるようにする
    • alias("エイリアス名",実行関数)エイリアスを anko の関数で定義する
    • alias("エイリアス名","コマンド定義")エイリアスの置換テキストを定義する
    • サンプルにあったのと同じ println()

エイリアス用関数は、こんな感じ

func f(args){
    for s in args{
        println(s)
    }
}

alias("foo",f)

これを nyagos.ank に記すと、「foo 1 2 3」で「1」「2」「3」と表示されます。

さて、この anko-nyagos.exe 、エイリアスしか実装していないのに、ファイルサイズが 4,807,168 バイトと結構なサイズになってしまいました。オリジナル nyagos.exe は 4,818,944 バイトです。重複していたり、使っていないコードが多いからというのもありますが、まだ Lua・anko 両方とも組み込みとかは考えない方がよいかもしれません。

(追記)

anko からコールバック関数(エイリアス関数本体)を Go 言語側に引き渡す時、どういう型で来るか、anko のドキュメントやソースを見ても分からなかったのですが、安直に

func ankoAlias(name string, f interface{}) {
    switch code := f.(type) {
    case string:
        alias.Table[name] = alias.New(code)
    default:
        println(reflect.TypeOf(f).String())
    }
}

とやって、型を表示させてみたらわかりました。vm.Func でした。これを anko のソースで検索してみたところ、次のような型のようです。

type Func func(args ...reflect.Value) (reflect.Value, error)

というわけで、現在はこんな感じになっています。

func ankoAlias(name string, f interface{}) {
    switch code := f.(type) {
    case string:
        alias.Table[name] = alias.New(code)
    case vm.Func:
        alias.Table[name] = &ankoFunc{f: code}
    default:
        println(reflect.TypeOf(f).String())
    }
}

以上

最近(4.4)の nyagos の方向性

  • ビルドバッチ(make.cmd)でかすぎ。簡略化する
  • ソース、どこから読んだらいいか分からん。整理する
  • 残イシュー多すぎ。はよ片付けろ
  • CMD.EXE依存エイリアス撲滅(mklinkren)

  • セマンティックバージョニング対応は 4.5 以降から。具体的には 4.5.x_y を v5.x.y に格上げする

  • Lisp対応は迷ってる。サブパッケージを共用する、別パッケージにした方がよいかもしれない

  • GitHubのMSアカウントがひそかに作ってる Go製Lua5.3 は注目してるけど、まだExperimentalなんで様子見。だけど Lua が 5.1 なのは不満なので、あげられるならあげたい

  • 情報発信大切。本件ブログに転載する。github だと英語も併記することにしてるので、どうしてもフットワークが下がる

C:\Program Files へファイルをコピーするバッチファイルの作り方

一般ユーザでは C:\Program Files 以下へファイルをコピーすることはできないので、管理者権限で自分自身を起動しなおすようにする。

make.cmd

setlocal
set "EXE=%~dp0\bin\Release\HogeHoge.exe"
call :"%1"
endlocal
exit /b

:"install"
    powershell Start-Process "%~dpnx0 install_" -verb runas
    exit /b

:"install_"
    copy "%EXE%" "C:\Program Files\nyaosorg\."
    copy "%EXE:.exe=.ini%" "C:\Program Files\nyaosorg\."
    pause
    exit /b

make install とタイプすると、UACダイアログが表示されるので、はい(Y)を選ぶと、管理者権限で目的のファイルを C:\Program Files\nyaosorg 以下へコピーしてくれる。

%~dp0 とか %EXE:exe=.ini% などは環境変数の置換で、これらは cmd /hcmd /c set /?cmd /c for /? などでガイドが出てくるので、そちらを参照いただきたい(つきはなし)

nyagos のソースフォルダー構成をもうちょっと簡単に

あまりにソースのサブフォルダーが多くて、改造しようという人が迷いそうなので、ちょっとだけ整理した(でも、まだ多い)

  • Cmd/ … ビルド結果置き場
  • Doc/ … ドキュメント
  • Etc/ … ビルド時に参照する設定ファイル置き場など(旧Misc/)
  • alias/ … エイリアス機能関連ソース(shell/ に依存)
  • commands/ … 内蔵コマンドの実装(shell/ に依存)
  • completion/ … 補完機能(readline/ に依存する)
  • defined/ … ifdef 的な機能を提供するパッケージ
  • dos/ … syscall 的なツールパッケージ。Windows に依存する
  • frame/ … メインルーチンの中で Lua に依存しないソース
  • functions/ … Lua向け関数だが、interface機能で Lua に依存しないようになっている
  • history/ … ヒストリ機能関連
  • mains/ … 真のメインルーチン。vanilla.go 以外は Lua に依存する
  • nodos/ … syscall 的なツールパッケージだが、Windows/Linux 双方で利用できるもの
  • nyagos.d/ … Lua スクリプト
  • readline/ … 一行入力パッケージ
  • shell/ … 一行実行パッケージ
  • t/ … テスト用の Lua スクリプト
  • texts/ … 文字列操作用ツールパッケージ

今回、削除したのは ngs/ だけ。ここをカレントディレクトリにして go build すれば ngs.exe という Lua を使わない nyagos をビルドできていた。が、今回、tag に vanilla を与えてビルド(make.cmd vanillaでOk)すれば Lua を参照しないようにすることで廃止した。

基本的な構造としては、shell(一行実行)と readline(一行入力) という二本柱がある。mains パッケージでこれらが用意している「フック」に対して、alias、completion、commands などの機能の関数をつっこんで呼び出しているという感じですわ。