From 5305ebd4a6679ef2fe75dbb6fcaaecdc274a4d0f Mon Sep 17 00:00:00 2001 From: Chris Antos Date: Mon, 31 May 2021 21:29:19 -0700 Subject: [PATCH 1/4] Use Clink async prompt filtering for git. `git status` and `git diff` can be slow in large repos. Clink v1.2.10 and higher support using Lua coroutines to do expensive parts of prompt filtering in the background. When the expensive parts complete, the prompt gets refreshed. This means even large repos can have fast prompts PLUS git status all the time! This change should be backward/forward compatible with both older and newer versions of Clink (of course only newer versions will gain the benefit). --- vendor/clink.lua | 63 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/vendor/clink.lua b/vendor/clink.lua index 2110548..7fbf55a 100644 --- a/vendor/clink.lua +++ b/vendor/clink.lua @@ -63,6 +63,28 @@ local function get_folder_name(path) end +--- +-- Forward/backward compatibility for Clink asynchronous prompt filtering. +-- With Clink v1.2.10 and higher this lets git status run in the background and +-- refresh the prompt when it finishes, to eliminate waits in large git repos. +--- +local io_popenyield +local clink_promptcoroutine +local cached_info = {} +if clink.promptcoroutine and io.popenyield then + io_popenyield = io.popenyield + clink_promptcoroutine = clink.promptcoroutine + -- Uncommenting this will override the cmderGitStatusOptIn and always show + -- git status when Clink is able to run it in the background. + --cmderForceAsyncGitStatus = true +else + io_popenyield = io.popen + clink_promptcoroutine = function (func) + return func(false) + end +end + + --- -- Setting the prompt in clink means that commands which rewrite the prompt do -- not destroy our own prompt. It also means that started cmds (or batch files @@ -308,7 +330,7 @@ end -- @return {bool} --- local function get_git_status() - local file = io.popen("git --no-optional-locks status --porcelain 2>nul") + local file = io_popenyield("git --no-optional-locks status --porcelain 2>nul") for line in file:lines() do file:close() return false @@ -323,7 +345,7 @@ end -- @return {bool} indicating true for conflict, false for no conflicts --- function get_git_conflict() - local file = io.popen("git diff --name-only --diff-filter=U 2>nul") + local file = io_popenyield("git diff --name-only --diff-filter=U 2>nul") for line in file:lines() do file:close() return true; @@ -363,6 +385,22 @@ local function get_svn_status() return true end +--- +-- Use a prompt coroutine to get git status in the background. +-- Cache the info so we can reuse it next time to reduce flicker. +--- +local function get_git_info_table() + local info = clink_promptcoroutine(function () + return { status=get_git_status(), conflict=get_git_conflict() } + end) + if not info then + info = cached_info.git_info or {} + else + cached_info.git_info = info + end + return info +end + --- -- Get the status of working dir -- @return {bool} @@ -402,19 +440,30 @@ local function git_prompt_filter() local git_dir = get_git_dir() local color - cmderGitStatusOptIn = get_git_status_setting() + cmderGitStatusOptIn = cmderForceAsyncGitStatus or get_git_status_setting() 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 + -- Use git status if allowed. if cmderGitStatusOptIn then -- 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 gitStatus = get_git_status() - local gitConflict = get_git_conflict() + local gitInfo = get_git_info_table() + local gitStatus = gitInfo.status + local gitConflict = gitInfo.conflict - color = colors.dirty - if gitStatus then + if gitStatus == nil then + color = colors.nostatus + elseif gitStatus then color = colors.clean + else + color = colors.dirty end if gitConflict then From 91aabe75af71e8621cba50dc5d1f64bff127acfd Mon Sep 17 00:00:00 2001 From: Chris Antos Date: Thu, 10 Jun 2021 18:49:39 -0700 Subject: [PATCH 2/4] Add prompt config variable for async git prompt. Setting `prompt_overrideGitStatusOptIn = true` will override the `cmder.status` and `cmder.cmdstatus` git config settings and run the git prompt status commands in the background. But it only takes effect when using Clink v1.2.10, since that's required in order to run prompt update commands in the background. --- vendor/clink.lua | 6 +++--- vendor/cmder_prompt_config.lua.default | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/vendor/clink.lua b/vendor/clink.lua index 7fbf55a..3bd5b72 100644 --- a/vendor/clink.lua +++ b/vendor/clink.lua @@ -74,9 +74,9 @@ local cached_info = {} if clink.promptcoroutine and io.popenyield then io_popenyield = io.popenyield clink_promptcoroutine = clink.promptcoroutine - -- Uncommenting this will override the cmderGitStatusOptIn and always show - -- git status when Clink is able to run it in the background. - --cmderForceAsyncGitStatus = true + if prompt_overrideGitStatusOptIn then + cmderForceAsyncGitStatus = true + end else io_popenyield = io.popen clink_promptcoroutine = function (func) diff --git a/vendor/cmder_prompt_config.lua.default b/vendor/cmder_prompt_config.lua.default index e5ae224..045a0d7 100644 --- a/vendor/cmder_prompt_config.lua.default +++ b/vendor/cmder_prompt_config.lua.default @@ -29,6 +29,11 @@ prompt_useUserAtHost = false -- default is false prompt_singleLine = false +-- OPTIONAL. If true then always ignore the cmder.status and cmder.cmdstatus git config settings and run the git prompt commands in the background. + -- default is false + -- NOTE: This only takes effect if using Clink v1.2.10 or higher. +prompt_overrideGitStatusOptIn = false + -- Prompt Attributes -- -- Colors From f6c2657b23204e4c0ba5d6f875280b3cee287ab3 Mon Sep 17 00:00:00 2001 From: Chris Antos Date: Fri, 11 Jun 2021 01:01:35 -0700 Subject: [PATCH 3/4] Allow scripts to disable part of the Cmder prompt. The Cmder prompt normally includes version control info, which involves running some potentially expensive commands. The cmder-powerline-prompt project (and maybe other projects) replaces the Cmder prompt and runs the same potentially expensive commands -- so expensive commands get run twice! This change makes it possible for the user and/or other scripts to disable the version control part of the built-in Cmder prompt. https://github.com/chrisant996/cmder-powerline-prompt --- vendor/clink.lua | 34 +++++++++++++++++++++++++- vendor/cmder_prompt_config.lua.default | 4 +++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/vendor/clink.lua b/vendor/clink.lua index 3bd5b72..5f3b628 100644 --- a/vendor/clink.lua +++ b/vendor/clink.lua @@ -85,6 +85,13 @@ else end +--- +-- Global variable so other Lua scripts can detect whether they're in a Cmder +-- shell session. +--- +CMDER_SESSION = true + + --- -- Setting the prompt in clink means that commands which rewrite the prompt do -- not destroy our own prompt. It also means that started cmds (or batch files @@ -135,6 +142,10 @@ local function set_prompt_filter() prompt_singleLine = false end + if prompt_includeVersionControl == nil then + prompt_includeVersionControl = true + end + if prompt_type == 'folder' then cwd = get_folder_name(cwd) end @@ -155,7 +166,12 @@ local function set_prompt_filter() if env ~= nil then env = "("..env..") " else env = "" end - prompt = get_uah_color() .. "{uah}" .. get_cwd_color() .. "{cwd}{git}{hg}{svn}" .. get_lamb_color() .. cr .. "{lamb} \x1b[0m" + if uah ~= '' then uah = get_uah_color() .. uah end + if cwd ~= '' then cwd = get_cwd_color() .. cwd end + + local version_control = prompt_includeVersionControl and "{git}{hg}{svn}" or "" + + prompt = "{uah}{cwd}" .. version_control .. get_lamb_color() .. cr .. "{lamb} \x1b[0m" prompt = string.gsub(prompt, "{uah}", uah) prompt = string.gsub(prompt, "{cwd}", cwd) prompt = string.gsub(prompt, "{env}", env) @@ -430,6 +446,11 @@ end local function git_prompt_filter() + -- Don't do any git processing if the prompt doesn't want to show git info. + if not clink.prompt.value:find("{git}") then + return false + end + -- Colors for git status local colors = { clean = get_clean_color(), @@ -484,6 +505,11 @@ end local function hg_prompt_filter() + -- Don't do any hg processing if the prompt doesn't want to show hg info. + if not clink.prompt.value:find("{hg}") then + return false + end + local result = "" local hg_dir = get_hg_dir() @@ -523,6 +549,12 @@ local function hg_prompt_filter() end local function svn_prompt_filter() + + -- Don't do any svn processing if the prompt doesn't want to show svn info. + if not clink.prompt.value:find("{svn}") then + return false + end + -- Colors for svn status local colors = { clean = get_clean_color(), diff --git a/vendor/cmder_prompt_config.lua.default b/vendor/cmder_prompt_config.lua.default index 045a0d7..6847f25 100644 --- a/vendor/cmder_prompt_config.lua.default +++ b/vendor/cmder_prompt_config.lua.default @@ -34,6 +34,10 @@ prompt_singleLine = false -- NOTE: This only takes effect if using Clink v1.2.10 or higher. prompt_overrideGitStatusOptIn = false +-- OPTIONAL. If true then Cmder includes git, mercurial, and subversion status in the prompt. + -- default is true +prompt_includeVersionControl = true + -- Prompt Attributes -- -- Colors From e7a6e6447384921a49cf707225177a5f55a3d6ed Mon Sep 17 00:00:00 2001 From: Chris Antos Date: Sat, 12 Jun 2021 18:56:37 -0700 Subject: [PATCH 4/4] Rewrote how `prompt_overrideGitStatusOptIn` works. It was less clear before, and it also accidentally took effect when the `prompt.async` Clink setting was off. --- vendor/clink.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/vendor/clink.lua b/vendor/clink.lua index 5f3b628..f7931ad 100644 --- a/vendor/clink.lua +++ b/vendor/clink.lua @@ -74,9 +74,6 @@ local cached_info = {} if clink.promptcoroutine and io.popenyield then io_popenyield = io.popenyield clink_promptcoroutine = clink.promptcoroutine - if prompt_overrideGitStatusOptIn then - cmderForceAsyncGitStatus = true - end else io_popenyield = io.popen clink_promptcoroutine = function (func) @@ -422,6 +419,15 @@ end -- @return {bool} --- local function get_git_status_setting() + -- When async prompt filtering is available, check the + -- prompt_overrideGitStatusOptIn config setting for whether to ignore the + -- cmder.status and cmder.cmdstatus git config opt-in settings. + if clink.promptcoroutine and io.popenyield and settings.get("prompt.async") then + if prompt_overrideGitStatusOptIn then + return true + end + end + local gitStatusConfig = io.popen("git --no-pager config cmder.status 2>nul") for line in gitStatusConfig:lines() do @@ -461,7 +467,7 @@ local function git_prompt_filter() local git_dir = get_git_dir() local color - cmderGitStatusOptIn = cmderForceAsyncGitStatus or get_git_status_setting() + cmderGitStatusOptIn = get_git_status_setting() if git_dir then local branch = get_git_branch(git_dir) if branch then