From 137aabc3543ac4d0c9fb49c8f33eaa632adbbb18 Mon Sep 17 00:00:00 2001
From: Stel Abrego <stel@stel.codes>
Date: Sat, 3 Dec 2022 12:50:55 -0800
Subject: [PATCH] feat(fb_actions.trash): add trash action

This action attempts to find a common trash executable (currently
"trash" or "gio" in that order) and performs a blocking system call via
vim.fn.system to trash each file or directory selected in the file
browser. Like actions.remove, refuses to trash parent directory or
current working directory.
---
 .../_extensions/file_browser/actions.lua      | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/lua/telescope/_extensions/file_browser/actions.lua b/lua/telescope/_extensions/file_browser/actions.lua
index f814a3a8..0cb51af5 100644
--- a/lua/telescope/_extensions/file_browser/actions.lua
+++ b/lua/telescope/_extensions/file_browser/actions.lua
@@ -482,6 +482,94 @@ fb_actions.remove = function(prompt_bufnr)
   end)
 end
 
+--- Trash file or folders via a pre-installed trash utility for |telescope-file-browser.picker.file_browser|.<br>
+--- Note: Attempts to find "trash-put" or "gio" executable and performs a blocking system command.
+---@param prompt_bufnr number: The prompt bufnr
+fb_actions.trash = function(prompt_bufnr)
+  local current_picker = action_state.get_current_picker(prompt_bufnr)
+  local finder = current_picker.finder
+  local quiet = current_picker.finder.quiet
+  local trash_cmd = nil
+  if vim.fn.executable("trash") == 1 then
+    trash_cmd = "trash"
+  elseif vim.fn.executable("gio") == 1 then
+    trash_cmd = "gio"
+  end
+  if not trash_cmd then
+    fb_utils.notify("actions.trash", { msg = "Cannot locate a valid trash executable!", level = "WARN", quiet = quiet })
+    return
+  end
+  local selections = fb_utils.get_selected_files(prompt_bufnr, true)
+  if vim.tbl_isempty(selections) then
+    fb_utils.notify("actions.trash", { msg = "No selection to be trashed!", level = "WARN", quiet = quiet })
+    return
+  end
+
+  local files = vim.tbl_map(function(sel)
+    return sel.filename:sub(#sel:parent().filename + 2)
+  end, selections)
+
+  for _, sel in ipairs(selections) do
+    if sel:is_dir() then
+      local abs = sel:absolute()
+      local msg
+      if finder.files and Path:new(finder.path):parent():absolute() == abs then
+        msg = "Parent folder cannot be trashed!"
+      end
+      if not finder.files and Path:new(finder.cwd):absolute() == abs then
+        msg = "Current folder cannot be trashed!"
+      end
+      if msg then
+        fb_utils.notify("actions.trash", { msg = msg .. " Prematurely aborting.", level = "WARN", quiet = quiet })
+        return
+      end
+    end
+  end
+
+  local trashed = {}
+
+  local message = "Selections to be trashed: " .. table.concat(files, ", ")
+  fb_utils.notify("actions.trash", { msg = message, level = "INFO", quiet = quiet })
+  -- TODO fix default vim.ui.input and nvim-notify 'selections to be deleted' message
+  vim.ui.input({ prompt = "Trash selections [y/N]: " }, function(input)
+    vim.cmd [[ redraw ]] -- redraw to clear out vim.ui.prompt to avoid hit-enter prompt
+    if input and input:lower() == "y" then
+      for _, p in ipairs(selections) do
+        local is_dir = p:is_dir()
+        local cmd = nil
+        if trash_cmd == "gio" then
+          cmd = { "gio", "trash", "--", p:absolute() }
+        else
+          cmd = { trash_cmd, "--", p:absolute() }
+        end
+        vim.fn.system(cmd)
+        if vim.v.shell_error == 0 then
+          table.insert(trashed, p.filename:sub(#p:parent().filename + 2))
+          -- clean up opened buffers
+          if not is_dir then
+            fb_utils.delete_buf(p:absolute())
+          else
+            fb_utils.delete_dir_buf(p:absolute())
+          end
+        else
+          local msg = "Command failed: " .. table.concat(cmd, " ")
+          fb_utils.notify("actions.trash", { msg = msg, level = "WARN", quiet = quiet })
+        end
+      end
+      local msg = nil
+      if next(trashed) then
+        msg = "Trashed: " .. table.concat(trashed, ", ")
+      else
+        msg = "No selections were successfully trashed"
+      end
+      fb_utils.notify("actions.trash", { msg = msg, level = "INFO", quiet = quiet })
+      current_picker:refresh(current_picker.finder)
+    else
+      fb_utils.notify("actions.trash", { msg = "Trashing selections aborted!", level = "INFO", quiet = quiet })
+    end
+  end)
+end
+
 --- Toggle hidden files or folders for |telescope-file-browser.picker.file_browser|.
 ---@param prompt_bufnr number: The prompt bufnr
 fb_actions.toggle_hidden = function(prompt_bufnr)