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