Last active
May 26, 2023 10:51
-
-
Save haolian9/ac7fa319308e1e9ece18fb329fbf5711 to your computer and use it in GitHub Desktop.
interactive sudo for nvim
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
---@diagnostic disable: unused-local | |
--design choices/limits | |
--* sudo runs in a tty process | |
--* sudo should run in background | |
--* prompt user to enter his password when needed | |
--* singleton | |
--* sync but not blocking nvim | |
-- | |
--supported forms: | |
--* [ ] sudo mv src dest | |
--* [ ] echo hello | sudo tee dest 1>/dev/null | |
-- | |
--todo: | |
--* why sudo does not ask user to enter password every time in shell? but it does here. | |
local api = vim.api | |
local state = { term_width = nil, term_height = nil, bufnr = nil, term = nil, job = nil } | |
do | |
local cols, lines = vim.o.columns, vim.o.lines | |
state.term_width = math.min(cols, math.max(math.floor(cols * 0.8), 100)) | |
state.term_height = math.min(lines, math.max(math.floor(lines * 0.3), 5)) | |
end | |
state.bufnr = api.nvim_create_buf(false, true) | |
---@param output string[] | |
---@param gold string | |
---@return boolean | |
local function output_contains(output, gold) | |
for _, line in pairs(output) do | |
if string.find(line, gold, 1, true) then return true end | |
end | |
return false | |
end | |
local function show_prompt(winbar) | |
if not (state.win_id and api.nvim_win_is_valid(state.win_id)) then | |
local cols, lines = vim.o.columns, vim.o.lines | |
local width = state.term_width + 2 | |
local height = state.term_height + 2 | |
local x = math.floor((cols - width) / 2) | |
local y = lines - height | |
-- stylua: ignore | |
state.win_id = api.nvim_open_win(state.bufnr, true, { | |
relative = "editor", style = "minimal", | |
row = y, col = x, width = width, height = height, | |
}) | |
else | |
api.nvim_set_current_win(state.win_id) | |
end | |
vim.wo[state.win_id].winbar = winbar | |
vim.cmd.startinsert() | |
end | |
state.term = api.nvim_open_term(state.bufnr, { | |
on_input = function(_, term, bufnr, data) | |
vim.fn.chansend(state.job, data) | |
-- todo: can be called on_exit for better UX | |
-- if data == "\r" then vim.schedule(function() | |
-- api.nvim_win_close(state.win_id, false) | |
-- state.win_id = nil | |
-- end) end | |
end, | |
}) | |
local sudo_cmd = { "sudo", "vifm" } | |
state.job = vim.fn.jobstart(sudo_cmd, { | |
pty = true, | |
width = state.term_width, | |
height = state.term_height, | |
stdin = "pipe", | |
stdout_buffered = false, | |
stderr_buffered = false, | |
env = { LANG = "C" }, | |
on_exit = function(job_id, exit_code, event) | |
vim.fn.chanclose(state.term) | |
api.nvim_buf_delete(state.bufnr, { force = false }) | |
end, | |
on_stdout = function(job_id, data, event) | |
vim.fn.chansend(state.term, data) | |
if output_contains(data, "[sudo] password for") then show_prompt(string.format("%s - stdout", table.concat(sudo_cmd, " "))) end | |
end, | |
on_stderr = function(job_id, data, event) | |
if output_contains(data, "[sudo] password for") then show_prompt(string.format("%s - stderr", table.concat(sudo_cmd, " "))) end | |
vim.fn.chansend(state.term, data) | |
end, | |
}) |
interesting relevant things:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks for pointing out it! but sadly it's not an equilevant to
openpty(3)
.oh maybe i can access openpty via ffi, and then reuse the pty between
spawn(sudo)
?dont know if it can work eventually, but it's worth a try!