From bd2d64230fc0fe931fa480f4c6a61f507fbbd2ca Mon Sep 17 00:00:00 2001
From: Folke Lemaitre <folke.lemaitre@gmail.com>
Date: Wed, 30 Nov 2022 23:38:45 +0100
Subject: [PATCH] feat: added config option for process timeout

---
 README.md                   |  2 +-
 lua/lazy/core/config.lua    |  1 +
 lua/lazy/manage/process.lua | 53 +++++++++++++++++++++++++++++--------
 3 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md
index d63b6e7..5e60622 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@
 
 - [ ] health checks: check merge conflicts async
 - [ ] allow setting up plugins through config
-- [ ] task timeout
+- [x] task timeout
 - [ ] log file
 - [ ] deal with resourcing init.lua. Check a global?
 - [x] incorrect when switching TN from opt to start
diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua
index dc5cfaf..85ac847 100644
--- a/lua/lazy/core/config.lua
+++ b/lua/lazy/core/config.lua
@@ -18,6 +18,7 @@ M.defaults = {
     -- defaults for `Lazy log`
     -- log = { "-10" }, -- last 10 commits
     log = { "--since=1 days ago" }, -- commits from the last 3 days
+    timeout = 120, -- processes taking over 2 minutes will be killed
   },
   -- Any plugin spec that contains one of the patterns will use your
   -- local repo in the projects folder instead of fetchig it from github
diff --git a/lua/lazy/manage/process.lua b/lua/lazy/manage/process.lua
index 1a45f91..9530178 100644
--- a/lua/lazy/manage/process.lua
+++ b/lua/lazy/manage/process.lua
@@ -1,37 +1,66 @@
+local Config = require("lazy.core.config")
+
 local M = {}
 
----@alias ProcessOpts {args: string[], cwd?: string, on_line?:fun(string), on_exit?: fun(ok:boolean, output:string)}
+---@diagnostic disable-next-line: no-unknown
+local uv = vim.loop
 
+---@class ProcessOpts
+---@field args string[]
+---@field cwd? string
+---@field on_line? fun(string)
+---@field on_exit? fun(ok:boolean, output:string)
+---@field timeout? number
+
+---@param opts? ProcessOpts
 function M.spawn(cmd, opts)
   opts = opts or {}
+  opts.timeout = opts.timeout or (Config.options.git.timeout * 1000)
+
   local env = {
     "GIT_TERMINAL_PROMPT=0",
     "GIT_SSH_COMMAND=ssh -oBatchMode=yes",
   }
 
   for key, value in
-    pairs(vim.loop.os_environ() --[[@as string[] ]])
+    pairs(uv.os_environ() --[[@as string[] ]])
   do
     table.insert(env, key .. "=" .. value)
   end
 
-  local stdout = vim.loop.new_pipe()
-  local stderr = vim.loop.new_pipe()
+  local stdout = uv.new_pipe()
+  local stderr = uv.new_pipe()
 
   local output = ""
   ---@type vim.loop.Process
   local handle = nil
 
-  handle = vim.loop.spawn(cmd, {
+  local timeout
+  local killed = false
+  if opts.timeout then
+    timeout = uv.new_timer()
+    timeout:start(opts.timeout, 0, function()
+      if handle and not handle:is_closing() then
+        killed = true
+        uv.process_kill(handle, "sigint")
+      end
+    end)
+  end
+
+  handle = uv.spawn(cmd, {
     stdio = { nil, stdout, stderr },
     args = opts.args,
     cwd = opts.cwd,
     env = env,
-  }, function(exit_code)
+  }, function(exit_code, signal)
+    if timeout then
+      timeout:stop()
+      timeout:close()
+    end
     handle:close()
     stdout:close()
     stderr:close()
-    local check = vim.loop.new_check()
+    local check = uv.new_check()
     check:start(function()
       if not stdout:is_closing() or not stderr:is_closing() then
         return
@@ -39,9 +68,12 @@ function M.spawn(cmd, opts)
       check:stop()
       if opts.on_exit then
         output = output:gsub("[^\r\n]+\r", "")
+        if killed then
+          output = output .. "\n" .. "Process was killed because it reached the timeout"
+        end
 
         vim.schedule(function()
-          opts.on_exit(exit_code == 0, output)
+          opts.on_exit(exit_code == 0 and signal == 0, output)
         end)
       end
     end)
@@ -51,7 +83,6 @@ function M.spawn(cmd, opts)
     if opts.on_exit then
       opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
     end
-
     return
   end
 
@@ -71,8 +102,8 @@ function M.spawn(cmd, opts)
     end
   end
 
-  vim.loop.read_start(stdout, on_output)
-  vim.loop.read_start(stderr, on_output)
+  uv.read_start(stdout, on_output)
+  uv.read_start(stderr, on_output)
 
   return handle
 end