Clinkの日本語パス問題ふたたび

Clinkの日本語パス問題を修正(3)のコメント欄

マルチバイト(日本語)を含むパス(%PATH%)に"aaa.bat"を置いて、clinkでaa[TAB]と入力しても補完が聞かない問題、もしお暇があれば見ていただけませんか?

というリクエストがありましたので、ふたたびコードを追っかけてみました。

この「補完」というのは、ClinkのSettings>exec_match_styleを0以上の値に指定した場合に有効になる「パスが通っているフォルダに対する実行可能ファイルとのマッチング」機能だという理解です。
で、確かに日本語を含むフォルダをパスに登録したうえで、当該フォルダにバッチファイルを置いても補完の対象になりません。

どうも組み込みのclink.find_filesがマルチバイトに対応してないみたいです。

というヒントをもらったので、まずはfind_filesの実体であるfind_files_impl()%lua.cから調べました。

すると、
[find_files_impl()%lua.c]

    i = 1;
    dir = opendir(mask); ←ここ
    while (entry = readdir(dir))
    {
        if (dirs_only && !(entry->attrib & _A_SUBDIR))
        {
            continue;
        }

        lua_pushstring(state, entry->d_name);
        lua_rawseti(state, -2, i++);
    }

において、当該日本語フォルダに存在するはずのバッチファイルを検索するときであってもopendir()が常にNULLを返しています。
その際の引数maskの値はたとえば"C:\Users\foo\AppData\Local\Temp\日本語フォルダ\aa*.BAT"のような値です。

というわけで、次はopendir()を調べます。実体はclink\readline\compat\dirent.cです。
[opendir()%dirent.c]

        if ((dir = (DIR *) malloc(sizeof *dir)) != 0 &&
           (dir->name = (wchar_t *) malloc(base_length)) != 0)
        {
            dir->name[0] = L'\0';

            if (volume_relative)
            {
                offset = get_volume_path(name, dir->name, (int)base_length);
                base_length -= offset;
                name += 2;
            }

            MultiByteToWideChar(
                CP_UTF8,
                0,
                name,
                -1,
                dir->name + offset,
                (int)(base_length / sizeof(wchar_t))
            );
            wcscat(dir->name, all);

            if ((dir->handle = (intptr_t) _wfindfirst64(dir->name, &dir->info)) != -1) ←ここ
            {
                dir->conv_buf = (char*)malloc(MAX_NAME_LEN);
                dir->result.d_name = 0;
            }
            else /* rollback */
            {
                free(dir->name);
                free(dir);
                dir = 0;
            }

_wfindfirst64()関数でファイル検索を行っていることがわかります。
_wfindfirst64()はmsdnの解説によると、第1引数const wchar_t *filespecにワイルドカードも指定できるということなので、マッチングはこの関数の機能に「おんぶにだっこ」な状態です。

で、const wchar_t *型を要求されているので、その直前でMultiByteToWideChar() Win32 APIを使って変換しているわけですが、ここで「あれ?」と思ったわけです。
さっき呼び出し元で引数maskの値を参照しているときはVisual Studioのウォッチ式で普通に日本語フォルダ名が見れました。ということは、maskの中身はMBCS(ShiftJIS)で格納されているわけです。
一方、MultiByteToWideChar()の第1引数(=変換元の文字コード)には"CP_UTF8"(UTF-8)が指定されています。
「もしかして、これ?」ということで、文字コードの指定を変えてみたら、

--- Temp/blue7b_dirent.c	Sat Dec 14 23:26:11 2013
+++ clink/readline/compat/dirent.c	Sat Dec 14 23:22:52 2013
@@ -125,11 +125,11 @@
                 base_length -= offset;
                 name += 2;
             }
 
             MultiByteToWideChar(
-                CP_UTF8,
+                CP_ACP,
                 0,
                 name,
                 -1,
                 dir->name + offset,
                 (int)(base_length / sizeof(wchar_t))

あっさりと補完が効くようになりました。
わかってしまえば「なあーんだ」なのですが、悩んだあげくにたった1か所の修正だなんて、こんなもんですよね、バグの原因なんて。

ただ、このバグを追っかけているうちに、別の気になる問題を見つけてしまったので、それはまた次回に。