From 42028811e4ce1cb48ebaae7bd0aa6a87562531f0 Mon Sep 17 00:00:00 2001 From: XuJiawei <59369014+Waitzz@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:58:10 +0800 Subject: [PATCH] refine: replace vim.fn.system with vim.system (#2863) --- README.md | 2 +- README_zh.md | 2 +- lua/avante/llm_tools/grep.lua | 82 ++++++++++++++++++++++---------- lua/avante/llm_tools/helpers.lua | 18 +++---- lua/avante/llm_tools/init.lua | 48 ++++++++++--------- lua/avante/providers/copilot.lua | 7 +-- lua/avante/rag_service.lua | 65 ++++++++++++------------- lua/avante/utils/init.lua | 20 ++++---- 8 files changed, 138 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 3993c12..e7bf4d1 100644 --- a/README.md +++ b/README.md @@ -1425,7 +1425,7 @@ Avante allows you to define custom tools that can be used by the AI during code }, func = function(params, on_log, on_complete) -- Custom function to execute local target = params.target or "./..." - return vim.fn.system(string.format("go test -v %s", target)) + return vim.system({ "go", "test", "-v", target }, { text = true }):wait().stdout end, }, }, diff --git a/README_zh.md b/README_zh.md index 4aa64ff..f0f3c2b 100644 --- a/README_zh.md +++ b/README_zh.md @@ -950,7 +950,7 @@ Avante 允许您定义自定义工具,AI 可以在代码生成和分析期间 }, func = function(params, on_log, on_complete) -- 要执行的自定义函数 local target = params.target or "./..." - return vim.fn.system(string.format("go test -v %s", target)) + return vim.system({ "go", "test", "-v", target }, { text = true }):wait().stdout end, }, }, diff --git a/lua/avante/llm_tools/grep.lua b/lua/avante/llm_tools/grep.lua index db7d220..8e461f6 100644 --- a/lua/avante/llm_tools/grep.lua +++ b/lua/avante/llm_tools/grep.lua @@ -84,40 +84,72 @@ function M.func(input, opts) if search_cmd == "" then return "", "No search command found" end ---execute the search command - local cmd = "" + local cmd = {} if search_cmd:find("rg") then - cmd = string.format("%s --files-with-matches --hidden", search_cmd) + cmd = { search_cmd, "--files-with-matches", "--hidden" } if input.case_sensitive then - cmd = string.format("%s --case-sensitive", cmd) + table.insert(cmd, "--case-sensitive") else - cmd = string.format("%s --ignore-case", cmd) + table.insert(cmd, "--ignore-case") end - if input.include_pattern then cmd = string.format("%s --glob '%s'", cmd, input.include_pattern) end - if input.exclude_pattern then cmd = string.format("%s --glob '!%s'", cmd, input.exclude_pattern) end - cmd = string.format("%s '%s' %s", cmd, input.query, abs_path) + if input.include_pattern then + table.insert(cmd, "--glob") + table.insert(cmd, input.include_pattern) + end + if input.exclude_pattern then + table.insert(cmd, "--glob") + table.insert(cmd, "!" .. input.exclude_pattern) + end + table.insert(cmd, input.query) + table.insert(cmd, abs_path) elseif search_cmd:find("ag") then - cmd = string.format("%s --nocolor --nogroup --hidden", search_cmd) - if input.case_sensitive then cmd = string.format("%s --case-sensitive", cmd) end - if input.include_pattern then cmd = string.format("%s --ignore '!%s'", cmd, input.include_pattern) end - if input.exclude_pattern then cmd = string.format("%s --ignore '%s'", cmd, input.exclude_pattern) end - cmd = string.format("%s '%s' %s", cmd, input.query, abs_path) + cmd = { search_cmd, "--nocolor", "--nogroup", "--hidden" } + if input.case_sensitive then table.insert(cmd, "--case-sensitive") end + if input.include_pattern then + table.insert(cmd, "--ignore") + table.insert(cmd, "!" .. input.include_pattern) + end + if input.exclude_pattern then + table.insert(cmd, "--ignore") + table.insert(cmd, input.exclude_pattern) + end + table.insert(cmd, input.query) + table.insert(cmd, abs_path) elseif search_cmd:find("ack") then - cmd = string.format("%s --nocolor --nogroup --hidden", search_cmd) - if input.case_sensitive then cmd = string.format("%s --smart-case", cmd) end - if input.exclude_pattern then cmd = string.format("%s --ignore-dir '%s'", cmd, input.exclude_pattern) end - cmd = string.format("%s '%s' %s", cmd, input.query, abs_path) + cmd = { search_cmd, "--nocolor", "--nogroup", "--hidden" } + if input.case_sensitive then table.insert(cmd, "--smart-case") end + if input.exclude_pattern then + table.insert(cmd, "--ignore-dir") + table.insert(cmd, input.exclude_pattern) + end + table.insert(cmd, input.query) + table.insert(cmd, abs_path) elseif search_cmd:find("grep") then - cmd = string.format("cd %s && git ls-files -co --exclude-standard | xargs %s -rH", abs_path, search_cmd, abs_path) - if not input.case_sensitive then cmd = string.format("%s -i", cmd) end - if input.include_pattern then cmd = string.format("%s --include '%s'", cmd, input.include_pattern) end - if input.exclude_pattern then cmd = string.format("%s --exclude '%s'", cmd, input.exclude_pattern) end - cmd = string.format("%s '%s'", cmd, input.query) + local files = + vim.system({ "git", "-C", abs_path, "ls-files", "-co", "--exclude-standard" }, { text = true }):wait().stdout + cmd = { "grep", "-rH" } + if not input.case_sensitive then table.insert(cmd, "-i") end + if input.include_pattern then + table.insert(cmd, "--include") + table.insert(cmd, input.include_pattern) + end + if input.exclude_pattern then + table.insert(cmd, "--exclude") + table.insert(cmd, input.exclude_pattern) + end + table.insert(cmd, input.query) + if files ~= "" then + for _, path in ipairs(vim.split(files, "\n")) do + if not path:match("^%s*$") then table.insert(cmd, vim.fs.joinpath(abs_path, path)) end + end + else + table.insert(cmd, abs_path) + end end - Utils.debug("cmd", cmd) - if on_log then on_log("Running command: " .. cmd) end - local result = vim.fn.system(cmd) - + Utils.debug("cmd", table.concat(cmd, " ")) + if on_log then on_log("Running command: " .. table.concat(cmd, " ")) end + local result = vim.system(cmd, { text = true }):wait().stdout or "" local filepaths = vim.split(result, "\n") return vim.json.encode(filepaths), nil diff --git a/lua/avante/llm_tools/helpers.lua b/lua/avante/llm_tools/helpers.lua index aeffd2a..2a3b4e1 100644 --- a/lua/avante/llm_tools/helpers.lua +++ b/lua/avante/llm_tools/helpers.lua @@ -129,21 +129,17 @@ end ---@return boolean function M.is_ignored(abs_path) local project_root = Utils.get_project_root() - local cmd = - string.format("git -C %s check-ignore %s", vim.fn.shellescape(project_root), vim.fn.shellescape(abs_path)) + local exit_code = vim + .system({ "git", "-C", vim.fn.shellescape(project_root), "check-ignore", vim.fn.shellescape(abs_path) }, { text = true }) + :wait().code - local result = vim.fn.system(cmd) - local exit_code = vim.v.shell_error - - -- If command failed or git is not available, fall back to old method + -- If command failed or git is not available or not a git repository, fall back to old method if exit_code ~= 0 and exit_code ~= 1 then return old_is_ignored(abs_path) end - -- Check if result indicates this is not a git repository - if result:sub(1, 26) == "fatal: not a git repository" then return old_is_ignored(abs_path) end - -- git check-ignore returns: - -- - exit code 0 and outputs the path if the file is ignored - -- - exit code 1 and no output if the file is not ignored + -- - exit code 0 and outputs the path to stdout if the file is ignored + -- - exit code 1 and no output to stdout if the file is not ignored + -- - exit code 128 and outputs the error info to stderr not stdout return exit_code == 0 end diff --git a/lua/avante/llm_tools/init.lua b/lua/avante/llm_tools/init.lua index dfde368..c42f741 100644 --- a/lua/avante/llm_tools/init.lua +++ b/lua/avante/llm_tools/init.lua @@ -389,20 +389,20 @@ function M.git_diff(input, opts) if not project_root then return nil, "Not in a git repository" end -- Check if we're in a git repository - local git_dir = vim.fn.system("git rev-parse --git-dir"):gsub("\n", "") + local git_dir = vim.system({ "git", "rev-parse", "--git-dir" }, { text = true }):wait().stdout:gsub("\n", "") if git_dir == "" then return nil, "Not a git repository" end -- Get the diff local scope = input.scope or "" - local cmd = string.format("git diff --cached %s", scope) - if on_log then on_log("Running command: " .. cmd) end - local diff = vim.fn.system(cmd) + local cmd = { "git", "diff", "--cached", scope } + if on_log then on_log("Running command: " .. table.concat(cmd, " ")) end + local diff = vim.system(cmd, { text = true }):wait().stdout if diff == "" then -- If there's no staged changes, get unstaged changes - cmd = string.format("git diff %s", scope) - if on_log then on_log("No staged changes. Running command: " .. cmd) end - diff = vim.fn.system(cmd) + cmd = { "git", "diff", scope } + if on_log then on_log("No staged changes. Running command: " .. table.concat(cmd, " ")) end + diff = vim.system(cmd, { text = true }):wait().stdout end if diff == "" then return nil, "No changes detected" end @@ -421,20 +421,23 @@ function M.git_commit(input, opts) if not project_root then return false, "Not in a git repository" end -- Check if we're in a git repository - local git_dir = vim.fn.system("git rev-parse --git-dir"):gsub("\n", "") + local git_dir = vim.system({ "git", "rev-parse", "--git-dir" }, { text = true }):wait().stdout:gsub("\n", "") if git_dir == "" then return false, "Not a git repository" end -- First check if there are any changes to commit - local status = vim.fn.system("git status --porcelain") + local status = vim.system({ "git", "status", "--porcelain" }, { text = true }):wait().stdout if status == "" then return false, "No changes to commit" end -- Get git user name and email - local git_user = vim.fn.system("git config user.name"):gsub("\n", "") - local git_email = vim.fn.system("git config user.email"):gsub("\n", "") + local git_user = vim.system({ "git", "config", "user.name" }, { text = true }):wait().stdout:gsub("\n", "") + local git_email = vim.system({ "git", "config", "user.email" }, { text = true }):wait().stdout:gsub("\n", "") -- Check if GPG signing is available and configured local has_gpg = false - local signing_key = vim.fn.system("git config --get user.signingkey"):gsub("\n", "") + local signing_key = vim + .system({ "git", "config", "--get", "user.signingkey" }, { text = true }) + :wait().stdout + :gsub("\n", "") if signing_key ~= "" then -- Try to find gpg executable based on OS @@ -449,8 +452,7 @@ function M.git_commit(input, opts) if gpg_cmd ~= "" then -- Verify GPG is working - local _ = vim.fn.system(string.format('"%s" --version', gpg_cmd)) - has_gpg = vim.v.shell_error == 0 + has_gpg = vim.system({ gpg_cmd, "--version" }, { text = true }):wait().code == 0 end end @@ -486,11 +488,11 @@ function M.git_commit(input, opts) end -- Stage changes if scope is provided if input.scope then - local stage_cmd = string.format("git add %s", input.scope) - if on_log then on_log("Staging files: " .. stage_cmd) end - local stage_result = vim.fn.system(stage_cmd) - if vim.v.shell_error ~= 0 then - on_complete(false, "Failed to stage files: " .. stage_result) + local stage_cmd = { "git", "add", input.scope } + if on_log then on_log("Staging files: " .. table.concat(stage_cmd, " ")) end + local stage_result = vim.system(stage_cmd, { text = true }):wait() + if stage_result.code ~= 0 then + on_complete(false, "Failed to stage files: " .. stage_result.stderr) return end end @@ -501,16 +503,16 @@ function M.git_commit(input, opts) if has_gpg then table.insert(cmd_parts, "-S") end for _, line in ipairs(commit_msg_lines) do table.insert(cmd_parts, "-m") - table.insert(cmd_parts, '"' .. line .. '"') + table.insert(cmd_parts, line) end local cmd = table.concat(cmd_parts, " ") -- Execute git commit if on_log then on_log("Running command: " .. cmd) end - local result = vim.fn.system(cmd) + local result = vim.system(cmd_parts, { text = true }):wait() - if vim.v.shell_error ~= 0 then - on_complete(false, "Failed to commit: " .. result) + if result.code ~= 0 then + on_complete(false, "Failed to commit: " .. result.stderr) return end diff --git a/lua/avante/providers/copilot.lua b/lua/avante/providers/copilot.lua index b1eccff..f91a43c 100644 --- a/lua/avante/providers/copilot.lua +++ b/lua/avante/providers/copilot.lua @@ -42,10 +42,11 @@ local lockfile_path = vim.fn.stdpath("data") .. "/avante/copilot-timer.lock" -- Lockfile management local function is_process_running(pid) - if vim.fn.has("win32") == 1 then - return vim.fn.system('tasklist /FI "PID eq ' .. pid .. '" 2>NUL | find /I "' .. pid .. '"') ~= "" + local result = vim.uv.kill(pid, 0) + if result ~= nil and result == 0 then + return true else - return vim.fn.system("ps -p " .. pid .. " > /dev/null 2>&1; echo $?") == "0\n" + return false end end diff --git a/lua/avante/rag_service.lua b/lua/avante/rag_service.lua index 1ccfbb9..9fb02df 100644 --- a/lua/avante/rag_service.lua +++ b/lua/avante/rag_service.lua @@ -21,14 +21,10 @@ function M.get_data_path() end function M.get_current_image() - local cmd = string.format("docker inspect %s | grep Image | grep %s", container_name, container_name) - local result = vim.fn.system(cmd) - if result == "" then return nil end - local exit_code = vim.v.shell_error - if exit_code ~= 0 then return nil end - local image = result:match('"Image":%s*"(.*)"') - if image == nil then return nil end - return image + local cmd = { "docker", "inspect", "--format", "{{.Config.Image}}", container_name } + local result = vim.system(cmd, { text = true }):wait() + if result.code ~= 0 or result.stdout == "" then return nil end + return result.stdout end function M.get_rag_service_runner() return (Config.rag_service and Config.rag_service.runner) or "docker" end @@ -80,9 +76,12 @@ function M.launch_rag_service(cb) if M.get_rag_service_runner() == "docker" then local image = M.get_rag_service_image() local data_path = M.get_data_path() - local cmd = string.format("docker ps | grep '%s'", container_name) - local result = vim.fn.system(cmd) - if result ~= "" then + local cmd = { "docker", "inspect", "--format", "{{.State.Status}}", container_name } + local result = vim.system(cmd, { text = true }):wait() + if result.code ~= 0 then Utils.debug(string.format("cmd: %s execution error", table.concat(cmd, " "))) end + if result.stdout == "" then + Utils.debug(string.format("container %s not found, starting...", container_name)) + elseif result.stdout == "running" then Utils.debug(string.format("container %s already running", container_name)) local current_image = M.get_current_image() if current_image == image then @@ -98,11 +97,8 @@ function M.launch_rag_service(cb) ) ) M.stop_rag_service() - else - Utils.debug(string.format("container %s not found, starting...", container_name)) end - result = vim.fn.system(string.format("docker ps -a | grep '%s'", container_name)) - if result ~= "" then + if result.stdout ~= "running" then Utils.info(string.format("container %s already started but not running, stopping...", container_name)) M.stop_rag_service() end @@ -139,8 +135,8 @@ function M.launch_rag_service(cb) }) elseif M.get_rag_service_runner() == "nix" then -- Check if service is already running - local check_cmd = string.format("pgrep -f '%s'", service_path) - local check_result = vim.fn.system(check_cmd) + local check_cmd = { "pgrep", "-f", service_path } + local check_result = vim.system(check_cmd, { text = true }):wait().stdout if check_result ~= "" then Utils.debug(string.format("RAG service already running at %s", service_path)) cb() @@ -187,28 +183,30 @@ end function M.stop_rag_service() if M.get_rag_service_runner() == "docker" then - local cmd = string.format("docker ps -a | grep '%s'", container_name) - local result = vim.fn.system(cmd) - if result ~= "" then vim.fn.system(string.format("docker rm -fv %s", container_name)) end + local cmd = { "docker", "inspect", "--format", "{{.State.Status}}", container_name } + local result = vim.system(cmd, { text = true }):wait().stdout + if result ~= "" then vim.system({ "docker", "rm", "-fv", container_name }):wait() end else - local cmd = string.format("pgrep -f '%s' | xargs -r kill -9", service_path) - vim.fn.system(cmd) - Utils.debug(string.format("Attempted to kill processes related to %s", service_path)) + local pid = vim.system({ "pgrep", "-f", service_path }, { text = true }):wait().stdout + if pid ~= "" then + vim.system({ "kill", "-9", pid }):wait() + Utils.debug(string.format("Attempted to kill processes related to %s", service_path)) + end end end function M.get_rag_service_status() if M.get_rag_service_runner() == "docker" then - local cmd = string.format("docker ps -a | grep '%s'", container_name) - local result = vim.fn.system(cmd) - if result == "" then + local cmd = { "docker", "inspect", "--format", "{{.State.Status}}", container_name } + local result = vim.system(cmd, { text = true }):wait().stdout + if result ~= "running" then return "stopped" else return "running" end elseif M.get_rag_service_runner() == "nix" then - local cmd = string.format("pgrep -f '%s'", service_path) - local result = vim.fn.system(cmd) + local cmd = { "pgrep", "-f", service_path } + local result = vim.system(cmd, { text = true }):wait().stdout if result == "" then return "stopped" else @@ -250,13 +248,12 @@ function M.to_local_uri(uri) end function M.is_ready() - vim.fn.system( - string.format( - "curl -s -o /dev/null -w '%%{http_code}' %s", - string.format("%s%s", M.get_rag_service_url(), "/api/health") + return vim + .system( + { "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", M.get_rag_service_url() .. "/api/health" }, + { text = true } ) - ) - return vim.v.shell_error == 0 + :wait().code == 0 end ---@class AvanteRagServiceAddResourceResponse diff --git a/lua/avante/utils/init.lua b/lua/avante/utils/init.lua index ee89d28..65b3b5d 100644 --- a/lua/avante/utils/init.lua +++ b/lua/avante/utils/init.lua @@ -85,17 +85,22 @@ end ---@param shell_cmd string? local function get_cmd_for_shell(input_cmd, shell_cmd) local shell = vim.o.shell:lower() - local cmd ---@type string + local cmd = {} -- powershell then we can just run the cmd - if shell:match("powershell") or shell:match("pwsh") then - cmd = input_cmd + if shell:match("powershell") then + cmd = { "powershell.exe", "-NoProfile", "-Command", input_cmd:gsub('"', "'") } + elseif shell:match("pwsh") then + cmd = { "pwsh.exe", "-NoProfile", "-Command", input_cmd:gsub('"', "'") } elseif fn.has("win32") > 0 then - cmd = 'powershell.exe -NoProfile -Command "' .. input_cmd:gsub('"', "'") .. '"' + cmd = { "powershell.exe", "-NoProfile", "-Command", input_cmd:gsub('"', "'") } else -- linux and macos we will just do sh -c shell_cmd = shell_cmd or "sh -c" - cmd = shell_cmd .. " " .. fn.shellescape(input_cmd) + for _, cmd_part in ipairs(vim.split(shell_cmd, " ")) do + table.insert(cmd, cmd_part) + end + table.insert(cmd, input_cmd) end return cmd @@ -108,10 +113,9 @@ end function M.shell_run(input_cmd, shell_cmd) local cmd = get_cmd_for_shell(input_cmd, shell_cmd) - local output = fn.system(cmd) - local code = vim.v.shell_error + local result = vim.system(cmd, { text = true }):wait() - return { stdout = output, code = code } + return { stdout = result.stdout, code = result.code } end ---@param input_cmd string