diff --git a/lua/oil/adapters/files/permissions.lua b/lua/oil/adapters/files/permissions.lua index fcd9984..cf50b55 100644 --- a/lua/oil/adapters/files/permissions.lua +++ b/lua/oil/adapters/files/permissions.lua @@ -1,6 +1,6 @@ local M = {} ----@param exe_modifier nil|string +---@param exe_modifier nil|false|string ---@param num integer ---@return string local function perm_to_str(exe_modifier, num) @@ -36,9 +36,9 @@ M.mode_to_octal_str = function(mode) end ---@param str string String of 3 characters ----@return integer +---@return nil|integer local function str_to_mode(str) - local r, w, x = unpack(vim.split(str, "")) + local r, w, x = unpack(vim.split(str, "", {})) local mode = 0 if r == "r" then mode = bit.bor(mode, 4) diff --git a/lua/oil/loading.lua b/lua/oil/loading.lua index 639dc9c..c50ac1b 100644 --- a/lua/oil/loading.lua +++ b/lua/oil/loading.lua @@ -48,7 +48,7 @@ M.set_loading = function(bufnr, is_loading) return end local lines = { util.lpad("Loading", math.floor(width / 2) - 3), bar_iter() } - util.render_centered_text(bufnr, lines) + util.render_text(bufnr, lines) end) ) end diff --git a/lua/oil/mutator/init.lua b/lua/oil/mutator/init.lua index d57bb24..1142699 100644 --- a/lua/oil/mutator/init.lua +++ b/lua/oil/mutator/init.lua @@ -387,22 +387,33 @@ M.process_actions = function(actions, cb) local finished = false local progress = Progress.new() + local function finish(...) + if not finished then + finished = true + progress:close() + cb(...) + end + end + -- Defer showing the progress to avoid flicker for fast operations vim.defer_fn(function() if not finished then - progress:show() + progress:show({ + -- TODO some actions are actually cancelable. + -- We should stop them instead of stopping after the current action + cancel = function() + finish("Canceled") + end, + }) end end, 100) - local function finish(...) - finished = true - progress:close() - cb(...) - end - local idx = 1 local next_action next_action = function() + if finished then + return + end if idx > #actions then finish() return @@ -415,7 +426,10 @@ M.process_actions = function(actions, cb) return finish(adapter) end local callback = vim.schedule_wrap(function(err) - if err then + if finished then + -- This can happen if the user canceled out of the progress window + return + elseif err then finish(err) else cache.perform_action(action) diff --git a/lua/oil/mutator/preview.lua b/lua/oil/mutator/preview.lua index 0ecd7bd..7380c26 100644 --- a/lua/oil/mutator/preview.lua +++ b/lua/oil/mutator/preview.lua @@ -45,25 +45,11 @@ end ---@param bufnr integer ---@param lines string[] local function render_lines(winid, bufnr, lines) - -- Finish setting the last line and highlights on the buffer - local width = vim.api.nvim_win_get_width(winid) - local height = vim.api.nvim_win_get_height(winid) - while #lines < height do - table.insert(lines, "") - end - local last_line = "[O]k [C]ancel" - local padding = string.rep(" ", math.floor((width - last_line:len()) / 2)) - local p_len = padding:len() - local padded_last_line = padding .. last_line - lines[#lines] = padded_last_line - vim.bo[bufnr].modifiable = true - vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) - vim.bo[bufnr].modified = false - vim.bo[bufnr].modifiable = false - local ns = vim.api.nvim_create_namespace("Oil") - vim.api.nvim_buf_clear_namespace(bufnr, ns, #lines - 1, #lines) - vim.api.nvim_buf_add_highlight(bufnr, ns, "Special", #lines - 1, p_len, p_len + 3) - vim.api.nvim_buf_add_highlight(bufnr, ns, "Special", #lines - 1, p_len + 8, p_len + 11) + util.render_text( + bufnr, + lines, + { v_align = "top", h_align = "left", winid = winid, actions = { "[O]k", "[C]ancel" } } + ) end ---@param actions oil.Action[] @@ -170,11 +156,9 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb) end, }) ) - vim.keymap.set("n", "q", cancel, { buffer = bufnr }) - vim.keymap.set("n", "C", cancel, { buffer = bufnr }) - vim.keymap.set("n", "c", cancel, { buffer = bufnr }) - vim.keymap.set("n", "", cancel, { buffer = bufnr }) - vim.keymap.set("n", "", cancel, { buffer = bufnr }) + for _, cancel_key in ipairs({ "q", "C", "c", "", "" }) do + vim.keymap.set("n", cancel_key, cancel, { buffer = bufnr, nowait = true }) + end vim.keymap.set("n", "O", confirm, { buffer = bufnr }) vim.keymap.set("n", "o", confirm, { buffer = bufnr }) end) diff --git a/lua/oil/mutator/progress.lua b/lua/oil/mutator/progress.lua index f4e9497..4091a0d 100644 --- a/lua/oil/mutator/progress.lua +++ b/lua/oil/mutator/progress.lua @@ -11,7 +11,8 @@ function Progress.new() local bufnr = vim.api.nvim_create_buf(false, true) vim.bo[bufnr].bufhidden = "wipe" return setmetatable({ - lines = { "", "", "" }, + lines = { "", "" }, + count = "", bufnr = bufnr, autocmds = {}, }, { @@ -19,17 +20,21 @@ function Progress.new() }) end -function Progress:show() +---@param opts nil|table +--- cancel fun() +function Progress:show(opts) + opts = opts or {} if self.winid and vim.api.nvim_win_is_valid(self.winid) then return end + self.cancel = opts.cancel local loading_iter = loading.get_bar_iter() self.timer = vim.loop.new_timer() self.timer:start( 0, math.floor(1000 / FPS), vim.schedule_wrap(function() - self.lines[2] = loading_iter() + self.lines[2] = string.format("%s %s", self.count, loading_iter()) self:_render() end) ) @@ -56,10 +61,13 @@ function Progress:show() end, }) ) + local cancel = self.cancel or function() end + vim.keymap.set("n", "c", cancel, { buffer = self.bufnr, nowait = true }) + vim.keymap.set("n", "C", cancel, { buffer = self.bufnr, nowait = true }) end function Progress:_render() - util.render_centered_text(self.bufnr, self.lines) + util.render_text(self.bufnr, self.lines, { winid = self.winid, actions = { "[C]ancel" } }) end function Progress:_reposition() @@ -93,7 +101,7 @@ function Progress:set_action(action, idx, total) change_line = adapter.render_action(action) end self.lines[1] = change_line - self.lines[3] = string.format("[%d/%d]", idx, total) + self.count = string.format("%d/%d", idx, total) self:_reposition() self:_render() end diff --git a/lua/oil/util.lua b/lua/oil/util.lua index 581b560..2089fea 100644 --- a/lua/oil/util.lua +++ b/lua/oil/util.lua @@ -428,40 +428,104 @@ M.get_adapter_for_action = function(action) return adapter end +---@param str string +---@param align "left"|"right"|"center" +---@param width integer +---@return string +---@return integer +M.h_align = function(str, align, width) + if align == "center" then + local padding = math.floor((width - vim.api.nvim_strwidth(str)) / 2) + return string.rep(" ", padding) .. str, padding + elseif align == "right" then + local padding = width - vim.api.nvim_strwidth(str) + return string.rep(" ", padding) .. str, padding + else + return str, 0 + end +end + ---@param bufnr integer ---@param text string|string[] -M.render_centered_text = function(bufnr, text) +---@param opts nil|table +--- h_align nil|"left"|"right"|"center" +--- v_align nil|"top"|"bottom"|"center" +--- actions nil|string[] +--- winid nil|integer +M.render_text = function(bufnr, text, opts) + opts = vim.tbl_deep_extend("keep", opts or {}, { + h_align = "center", + v_align = "center", + }) if not vim.api.nvim_buf_is_valid(bufnr) then return end if type(text) == "string" then text = { text } end - local winid - for _, win in ipairs(vim.api.nvim_list_wins()) do - if vim.api.nvim_win_get_buf(win) == bufnr then - winid = win - break - end - end local height = 40 local width = 30 - if winid then - height = vim.api.nvim_win_get_height(winid) - width = vim.api.nvim_win_get_width(winid) + + -- If no winid passed in, find the first win that displays this buffer + if not opts.winid then + for _, winid in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_is_valid(winid) and vim.api.nvim_win_get_buf(winid) == bufnr then + opts.winid = winid + break + end + end + end + if opts.winid then + height = vim.api.nvim_win_get_height(opts.winid) + width = vim.api.nvim_win_get_width(opts.winid) end local lines = {} - for _ = 1, (height / 2) - (#text / 2) do - table.insert(lines, "") + + -- Add vertical spacing for vertical alignment + if opts.v_align == "center" then + for _ = 1, (height / 2) - (#text / 2) do + table.insert(lines, "") + end + elseif opts.v_align == "bottom" then + local num_lines = height + if opts.actions then + num_lines = num_lines - 2 + end + while #lines + #text < num_lines do + table.insert(lines, "") + end end + + -- Add the lines of text for _, line in ipairs(text) do - line = string.rep(" ", (width - vim.api.nvim_strwidth(line)) / 2) .. line + line = M.h_align(line, opts.h_align, width) table.insert(lines, line) end - vim.api.nvim_buf_set_option(bufnr, "modifiable", true) + + -- Render the actions (if any) at the bottom + local highlights = {} + if opts.actions then + while #lines < height - 1 do + table.insert(lines, "") + end + local last_line, padding = M.h_align(table.concat(opts.actions, " "), "center", width) + local col = padding + for _, action in ipairs(opts.actions) do + table.insert(highlights, { "Special", #lines, col, col + 3 }) + col = padding + action:len() + 4 + end + table.insert(lines, last_line) + end + + vim.bo[bufnr].modifiable = true pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines) - vim.api.nvim_buf_set_option(bufnr, "modifiable", false) + vim.bo[bufnr].modifiable = false vim.bo[bufnr].modified = false + local ns = vim.api.nvim_create_namespace("Oil") + vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + for _, hl in ipairs(highlights) do + vim.api.nvim_buf_add_highlight(bufnr, ns, unpack(hl)) + end end ---Run a function in the context of a full-editor window diff --git a/lua/oil/view.lua b/lua/oil/view.lua index 56993bd..23dc1ab 100644 --- a/lua/oil/view.lua +++ b/lua/oil/view.lua @@ -532,7 +532,7 @@ M.render_buffer_async = function(bufnr, opts, callback) if not preserve_undo then vim.bo[bufnr].undolevels = vim.api.nvim_get_option("undolevels") end - util.render_centered_text(bufnr, { "Error: " .. message }) + util.render_text(bufnr, { "Error: " .. message }) if callback then callback(message) else