-- luacheck: globals clink local CLINK_HOME = clink.get_env("LOCALAPPDATA") .. "/clink/" -- {{{ prompt local colors = { black = 0, red = 1, green = 2, yellow = 3, blue = 4, magenta = 5, cyan = 6, white = 7, reset = 9, } local function c(color, bright) bright = bright == nil and false or bright return "\x1b[" .. (bright and "9" or "3") .. tostring(colors[color] or 9) .. "m" end local function escape_gsub_find_arg(text) return text and text:gsub("([-+*?.%%()%[%]$^])", "%%%1") or "" end local function escape_gsub_replace_arg(text) return text and text:gsub("%%", "%%%%") or "" end local function gsub_plain(str, find, replace) return string.gsub(str, escape_gsub_find_arg(find), escape_gsub_replace_arg(replace)) end local function get_folder_name(path) local reversePath = string.reverse(path) local slashIndex = string.find(reversePath, "\\") if slashIndex == nil then return path end local folder = string.sub(path, string.len(path) - slashIndex + 2) if folder == "" then return path else return folder end end local old_prompt = "" local old = clink.promptfilter(0) function old:filter(prompt) old_prompt = prompt end local lambda = clink.promptfilter(10) function lambda:filter(prompt) local code = tonumber(os.geterrorlevel()) return " " .. c(code == 0 and "magenta" or "red") .. "λ" .. c("reset") .. " " end local cwd_prompt = clink.promptfilter(20) function cwd_prompt:filter(prompt) local cwd = old_prompt:match('.*(.:[^>]*)>') if cwd == nil then cwd = clink.get_cwd() end if string.find(cwd, clink.get_env("HOME")) then cwd = gsub_plain(cwd, clink.get_env("HOME"), "~") end cwd = get_folder_name(cwd) return prompt .. " " .. cwd .. " " end local function get_git_dir(path) -- return parent path for specified entry (either file or directory) local function pathname(path) -- luacheck: ignore 432 local prefix = "" local i = path:find("[\\/:][^\\/:]*$") if i then prefix = path:sub(1, i-1) end return prefix end -- Checks if provided directory contains git directory local function has_git_dir(dir) return clink.is_dir(dir..'/.git') and dir..'/.git' end local function has_git_file(dir) local gitfile = io.open(dir..'/.git') if not gitfile then return false end local line = gitfile:read() or '' local git_dir = line:match('gitdir: (.*)') gitfile:close() if os.isdir then -- only available in Clink v1.0.0 and higher if git_dir and os.isdir(git_dir) then return git_dir end end return git_dir and dir..'/'..git_dir end -- Set default path to current directory if not path or path == '.' then path = clink.get_cwd() end -- Calculate parent path now otherwise we won't be -- able to do that inside of logical operator local parent_path = pathname(path) return has_git_dir(path) or has_git_file(path) -- Otherwise go up one level and make a recursive call or (parent_path ~= path and get_git_dir(parent_path) or nil) end local function get_git_branch(git_dir) git_dir = git_dir or get_git_dir() -- If git directory not found then we're probably outside of repo -- or something went wrong. The same is when head_file is nil local head_file = git_dir and io.open(git_dir..'/HEAD') if not head_file then return end local HEAD = head_file:read() head_file:close() -- If HEAD is missing, something is wrong. if not HEAD then return end -- if HEAD matches branch expression, then we're on named branch -- otherwise it is a detached commit local branch_name = HEAD:match('ref: refs/heads/(.+)') return branch_name or 'HEAD detached at '..HEAD:sub(1, 7) end local io_popenyield local clink_promptcoroutine local cached_info = {} if clink.promptcoroutine and io.popenyield then io_popenyield = io.popenyield clink_promptcoroutine = clink.promptcoroutine else io_popenyield = io.popen clink_promptcoroutine = function (func) return func(false) end end local function get_git_status() local file = io_popenyield("git --no-optional-locks status --porcelain 2>nul") if not file then return {} end local conflict_found = false local is_status = true for line in file:lines() do local code = line:sub(1, 2) -- print (string.format("code: %s, line: %s", code, line)) if code == "DD" or code == "AU" or code == "UD" or code == "UA" or code == "DU" or code == "AA" or code == "UU" then -- luacheck: no max line length is_status = false conflict_found = true break -- unversioned files are ignored, comment out 'code ~= "!!"' to unignore them elseif code ~= "!!" and code ~= "??" then is_status = false end end file:close() return { status = is_status, conflict = conflict_found } end local function get_git_info_table() local info = clink_promptcoroutine(function () return get_git_status() end) if not info then info = cached_info.git_info or {} else cached_info.git_info = info end return info end local git_colors = { clean = c("green"), dirty = c("yellow"), conflict = c("red"), nostatus = c("white"), } local git = clink.promptfilter(30) function git:filter(prompt) local git_dir = get_git_dir() local color if git_dir then local branch = get_git_branch(git_dir) if branch then -- If in a different repo or branch than last time, discard cached info. if cached_info.git_dir ~= git_dir or cached_info.git_branch ~= branch then cached_info.git_info = nil cached_info.git_dir = git_dir cached_info.git_branch = branch end -- If we're inside of git repo then try to detect current branch -- Has branch => therefore it is a git folder, now figure out status local gitInfo = get_git_info_table() local gitStatus = gitInfo.status local gitConflict = gitInfo.conflict if gitStatus == nil then color = git_colors.nostatus elseif gitStatus then color = git_colors.clean else color = git_colors.dirty end if gitConflict then color = git_colors.conflict end return prompt .. " " .. c("cyan") .. branch .. " " .. color .. "▲" .. c("reset") .. " " end end return prompt end local clock = clink.promptfilter(40) function clock:filter(prompt) end function clock:rightfilter(prompt) return c("white") .. " " .. os.date("%H:%M:%S") .. " " end local last_run = os.clock() local function format_time(time) local out = "" if time >= 3600 then out = out .. math.floor(time / 3600) .. "h" end if time >= 60 then out = out .. math.floor(time % 3600 / 60) .. "m" end out = out .. math.floor(time % 60) .. "s" return out end local exectime = clink.promptfilter(50) function exectime:filter(prompt) end function exectime:rightfilter(prompt) local delta = os.clock() - last_run if delta < 3 then return prompt end return c("yellow") .. " " .. format_time(delta) .. " " .. prompt end local exitcode = clink.promptfilter(60) function exitcode:filter(prompt) end function exitcode:rightfilter(prompt) local code = tonumber(os.geterrorlevel()) if code == 0 then return prompt else return c("red") .. " " .. code .. " " .. prompt end end local finish = clink.promptfilter(100) function finish:filter(prompt) last_run = os.clock() return prompt .. " ", false end -- }}} -- {{{ completions local completions_dir = CLINK_HOME .. "clink-completions/" dofile(completions_dir .. ".init.lua") for _,lua_module in ipairs(clink.find_files(completions_dir.."*.lua")) do -- Skip files that starts with _. This could be useful if some files should be ignored if not string.match(lua_module, "^_.*") then local filename = completions_dir..lua_module -- use dofile instead of require because require caches loaded modules dofile(filename) end end -- }}}