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

前回の「気になる問題」というのは、MCBS(ShiftJIS)でパスを扱うときの注意事項である「ShiftJISの2バイト目に現れる"\"(0x5c)への対応」なのですが、前回も調べたfind_files_impl()%lua.cには

        slash = strrchr(buffer, '\\');
        slash = slash ? slash : strrchr(buffer, '/');
        slash = slash ? slash + 1 : buffer;

なんていう部分があるので、このままではNGです。
ですが、これを「修正」しようとして「文字列先頭から検索してShiftJISの1バイト目だったら次の1バイトをスキップ」みたいなコードを書いたら、
Sorry, Japanese ONLY
になってしまうので、それもよろしくありません。

さらに深く調査すると、検索パスではなく補完される実行ファイル名に日本語を含む場合、具体的には"実行ファイル.BAT"みたいなものを補完すべく

実行[TAB]

と入力したときのopendir()%dirent.cへの引数は、「実行」に相当する部分がUTF-8で格納されていることを確認しました。
前回パッチを当てた個所も元はCP_UTF8が指定されていることと合わせて判断すると、この補完処理全体としてはUTF-8で処理しているつもりなので、そうなるように修正するのが正しいということになります。
UTF-8はShiftJISでの反省をふまえ、2バイト目以降に"\"のような文字コードが出現しないように定義されている)

というわけで、今度はfind_files_impl()%lua.cを呼び出している側を調査します。呼び出し元はclink.match_files%clink\clink\dll\clink.luaでした。

function clink.match_files(pattern, full_path, find_func)
    -- Fill out default values
    if type(find_func) ~= "function" then
        find_func = clink.find_files
    end

    if full_path == nil then
        full_path = true
    end

    if pattern == nil then
        pattern = "*"
    end

    -- Glob files.
    pattern = pattern:gsub("/", "\\")
    local glob = find_func(pattern, true)

    -- Get glob's base.
    local base = ""
    local i = pattern:find("[\\:][^\\:]*$")
    if i and full_path then
        base = pattern:sub(1, i)
    end

    -- Match them.
    local count = clink.match_count()

    for _, i in ipairs(glob) do
        local full = base..i
        clink.add_match(full)
    end

    return clink.match_count() - count
end

もはやC++でのソースではなくなっていますが、これはただの下請け関数で、実際の補完処理はさらに上位関数のexec_match_generator()%clink\clink\dll\exec.luaが行っています。

local function exec_match_generator(text, first, last)
...
    -- Strip off any path components that may be on text
    local prefix = ""
    local i = text:find("[\\/:][^\\/:]*$")
    if i then
        prefix = text:sub(1, i)
    end

    local suffices = clink.split(clink.get_env("pathext"), ";")
    for i = 1, #suffices, 1 do
        suffices[i] = text.."*"..suffices[i]
    end

    -- First step is to match executables in the environment's path.
    if not text:find("[\\/:]") then
        local paths = get_environment_paths()
        for _, suffix in ipairs(suffices) do
            for _, path in ipairs(paths) do
                clink.match_files(path..suffix, false)
            end
        end
...

実行ファイルとして補完すべき拡張子群をclink.get_env("pathext")で取得し、それをget_environment_paths()で取得したパス群に対して順次検索をおこなっています。
つまりget_environment_paths()の返り値の時点で文字コードUTF-8である必要があるわけです。

local function get_environment_paths()
    local paths = clink.split(clink.get_env("PATH"), ";")

    -- We're expecting absolute paths and as ';' is a valid path character
    -- there maybe unneccessary splits. Here we resolve them.
    local paths_merged = { paths[1] }
    for i = 2, #paths, 1 do
        if not paths[i]:find("^[a-zA-Z]:") then
            local t = paths_merged[#paths_merged];
            paths_merged[#paths_merged] = t..paths[i]
        else
            table.insert(paths_merged, paths[i])
        end
    end

    -- Append slashes.
    for i = 1, #paths_merged, 1 do
        table.insert(paths, paths_merged[i].."\\")
    end

    return paths
end

後処理はいろいろありますが、基本的にはclink.get_env("PATH")で取得したPATH環境変数の値をそのまま使っています。ここにUTF-8への変換処理を入れられればよいのですが、見ての通りC++ではありません。
ではこれは何?ということなのですが、どうやら"Lua"というスクリプト言語だそうです。Clinkのドキュメントにも"Scriptable completion with Lua. "という記述があります。

今の自分の力量ではLuaUTF-8変換処理が書けそうにないので、clink.get_env()の時点でUTF-8を返すようにすることにします。
…予想以上に長くなったので、実際の修正パッチはまた次回に。