mridgers / clink

Bash's powerful command line editing in cmd.exe
mridgers.github.io/clink
GNU General Public License v3.0
3.15k stars 286 forks source link

Using LuaJIT (FFI) #465

Open qwerty12 opened 6 years ago

qwerty12 commented 6 years ago

Hi,

I see the question has come up before in issue #111, which was closed, but I ask again not out of a desire for speed improvements but simply because LuaJIT's FFI module lets me address some small clink shortcomings and add small functionality without needing to recompile clink to add a new Lua-callable function.

For instance, clink.get_cwd() uses GetCurrentDirectoryA() which doesn't work well with umlauts (see #415) etc. I also like to have the Command Prompt's window title match the current directory I'm in, which saves me time when I have more than one cmd open. I know this can be done with doskey, a batch file and the built in title command, but I've found title requires certain paths to be wrapped in quotes -- which looks ugly. Meanwhile, SetConsoleTitleW just works without any hassle.

After switching out clink's Lua interpreter for LuaJIT-2.1.0-beta3, I have a working prompt script that does what I listed above (obtains the current directory path through GetCurrentDirectoryW() and sets the title):

clink_prompt.lua

local ffi = require("ffi") ffi.cdef[[ int MultiByteToWideChar(unsigned int CodePage, unsigned int dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); int SetConsoleTitleW(const wchar_t *lpConsoleTitle); unsigned int GetCurrentDirectoryW(unsigned int nBufferLength, wchar_t *lpBuffer); int WideCharToMultiByte(unsigned int CodePage, unsigned int dwFlags, const wchar_t *lpWideCharStr, int cchWideChar, char *lpMultiByteStr, int cbMultiByte, const char *lpDefaultChar, bool *lpUsedDefaultChar); ]] local C = ffi.C local clink_prompt_home = os.getenv("USERPROFILE") function set_title(title) local utf16_len = C.MultiByteToWideChar(65001, 0, title, -1, NULL, 0) --CP_UTF8 if utf16_len > 0 then --utf16_len = utf16_len + 1 local utf16_title = ffi.new("wchar_t[?]", utf16_len) if C.MultiByteToWideChar(65001, 0, title, -1, utf16_title, utf16_len) > 0 then C.SetConsoleTitleW(utf16_title) end end end function get_cwd_utf8() local path = ffi.new("wchar_t[?]", 260) --MAX_PATH if C.GetCurrentDirectoryW(260, path) ~= 0 then local utf8_size = C.WideCharToMultiByte(65001, 0, path, -1, NULL, 0, NULL, NULL) if utf8_size > 0 then local utf8_path = ffi.new("char[?]", utf8_size) local utf8_size = C.WideCharToMultiByte(65001, 0, path, -1, utf8_path, utf8_size, NULL, NULL) if utf8_size > 0 then return ffi.string(utf8_path, utf8_size) end end end return nil end function prompt() local new_prompt = nil local error_level = clink.get_env("=ExitCode") local cwd = get_cwd_utf8() if string.sub(cwd,1,string.len(clink_prompt_home))==clink_prompt_home then cwd = string.gsub(cwd, clink_prompt_home, '~') new_prompt = string.gsub(clink.prompt.value, clink_prompt_home, '~') end set_title(cwd) if error_level ~= "00000000" then new_prompt = "\x1b[1;31;40m" .. error_level:match("0*(%d+)") .. " " .. (new_prompt and new_prompt or clink.prompt.value) end if new_prompt ~= nil then clink.prompt.value = new_prompt return true end end clink.prompt.register_filter(prompt, 1)

If the excellent clink is picked up again, it would be great if I could use such scripts without needing to first compile clink myself.

Thanks for reading.

chrisant996 commented 3 years ago

The 1.0.0a1 version uses GetCurrentDirectoryW, and then converts to UTF8 and sends the UTF8 back to LUA. It behaves similarly for other OS Unicode APIs as well.

The 1.0.0a1 version isn't in a very functional state, but the chrisant996/clink fork is getting it into working order and adding a bunch of features.