Created
November 9, 2025 10:29
-
-
Save danielres/c2b6ef51e72b46ecbee57e2683a23bfe to your computer and use it in GitHub Desktop.
Productive elixir refactoring in lazyvim, using "mix igniter.refactor.rename_function"
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
| -- ~/.config/nvim/lua/plugins/elixir-igniter-rename.lua | |
| local function project_root() | |
| local ok, Util = pcall(require, "lazyvim.util") | |
| if ok and Util.root and Util.root.get then | |
| return Util.root.get() | |
| end | |
| local git = vim.fn.systemlist("git rev-parse --show-toplevel")[1] | |
| return (git and git ~= "") and git or vim.loop.cwd() | |
| end | |
| local function find_current_module(bufnr, line_nr) | |
| for l = line_nr, 1, -1 do | |
| local line = vim.api.nvim_buf_get_lines(bufnr, l - 1, l, false)[1] | |
| if line then | |
| local mod = line:match("^%s*defmodule%s+([%w_%.]+)") | |
| if mod then | |
| return mod | |
| end | |
| end | |
| end | |
| return nil | |
| end | |
| local function parse_def_head(line) | |
| local name, params = line:match("^%s*defp?%s+([%w_!%?]+)%s*%((.*)%)") | |
| if not name then | |
| return nil, nil | |
| end | |
| local function arity_of(param_str) | |
| local lvl, commas = 0, 0 | |
| for c in param_str:gmatch(".") do | |
| if c == "(" or c == "[" or c == "{" then | |
| lvl = lvl + 1 | |
| elseif c == ")" or c == "]" or c == "}" then | |
| lvl = lvl - 1 | |
| elseif c == "," and lvl == 0 then | |
| commas = commas + 1 | |
| end | |
| end | |
| if param_str:match("^%s*$") then | |
| return 0 | |
| end | |
| return commas + 1 | |
| end | |
| return name, arity_of(params) | |
| end | |
| local function detect_fun_at_cursor() | |
| local bufnr = vim.api.nvim_get_current_buf() | |
| local row = vim.api.nvim_win_get_cursor(0)[1] | |
| local line = vim.api.nvim_buf_get_lines(bufnr, row - 1, row, false)[1] or "" | |
| local fname, arity = parse_def_head(line) | |
| local mod = find_current_module(bufnr, row) | |
| if fname and mod then | |
| return { module = mod, name = fname, arity = arity } | |
| end | |
| local ident = vim.fn.expand("<cword>") | |
| local esc = vim.pesc or function(s) | |
| return (s:gsub("(%W)", "%%%1")) | |
| end | |
| local modprefix = ident and line:match("([%w_%.]+)%s*%.%s*" .. esc(ident) .. "%s*%(") or nil | |
| return { module = modprefix or mod, name = ident, arity = nil } | |
| end | |
| local function ask(prompt, default, cb) | |
| vim.ui.input( | |
| { prompt = default and (prompt .. " [" .. default .. "]: ") or (prompt .. ": "), default = default }, | |
| function(ans) | |
| cb((ans == nil or ans == "") and default or ans) | |
| end | |
| ) | |
| end | |
| local function choose(title, items, default_idx, cb) | |
| vim.ui.select(items, { | |
| prompt = title, | |
| format_item = function(x) | |
| return x | |
| end, | |
| }, function(choice, idx) | |
| cb(choice or items[default_idx], idx or default_idx) | |
| end) | |
| end | |
| local function get_result_buf() | |
| local name = "Igniter Rename Result" | |
| local bufnr = vim.fn.bufnr(name) | |
| if bufnr == -1 then | |
| bufnr = vim.api.nvim_create_buf(false, true) | |
| -- set name once; subsequent calls reuse the same buffer | |
| vim.api.nvim_buf_set_name(bufnr, name) | |
| vim.api.nvim_set_option_value("buftype", "nofile", { buf = bufnr }) | |
| vim.api.nvim_set_option_value("swapfile", false, { buf = bufnr }) | |
| end | |
| return bufnr | |
| end | |
| local function show_in_vsplit(bufnr) | |
| local win = vim.fn.bufwinnr(bufnr) | |
| if win == -1 then | |
| vim.cmd("vsplit") | |
| vim.api.nvim_win_set_buf(0, bufnr) | |
| else | |
| vim.cmd(tostring(win) .. "wincmd w") | |
| end | |
| end | |
| local function render_result(bufnr, cwd, cmd, stdout, stderr) | |
| vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) | |
| local lines = { | |
| "cwd: " .. cwd, | |
| "cmd: " .. table.concat(cmd, " "), | |
| "", | |
| "== stdout ==", | |
| } | |
| if stdout ~= "" then | |
| for s in (stdout .. "\n"):gmatch("([^\n]*)\n") do | |
| table.insert(lines, s) | |
| end | |
| end | |
| if stderr ~= "" then | |
| table.insert(lines, "") | |
| table.insert(lines, "== stderr ==") | |
| for s in (stderr .. "\n"):gmatch("([^\n]*)\n") do | |
| table.insert(lines, s) | |
| end | |
| end | |
| vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) | |
| vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) | |
| vim.api.nvim_set_option_value("filetype", "log", { buf = bufnr }) | |
| end | |
| local function run_mix(cmd, cwd) | |
| local res = vim.system(cmd, { cwd = cwd, text = true }):wait() | |
| local bufnr = get_result_buf() | |
| render_result(bufnr, cwd, cmd, res.stdout or "", res.stderr or "") | |
| show_in_vsplit(bufnr) | |
| if res.code ~= 0 then | |
| vim.notify("igniter rename failed (exit " .. tostring(res.code) .. ")", vim.log.levels.ERROR) | |
| else | |
| vim.notify("igniter rename finished", vim.log.levels.INFO) | |
| end | |
| end | |
| local function build_old_spec(ctx, with_arity) | |
| if not ctx.module or not ctx.name then | |
| return nil | |
| end | |
| if with_arity and ctx.arity ~= nil then | |
| return string.format("%s.%s/%d", ctx.module, ctx.name, ctx.arity) | |
| else | |
| return string.format("%s.%s", ctx.module, ctx.name) | |
| end | |
| end | |
| local function prompt_and_rename() | |
| local root = project_root() | |
| local ctx = detect_fun_at_cursor() | |
| if not ctx.module or not ctx.name then | |
| vim.notify("Could not detect module/function at cursor", vim.log.levels.WARN) | |
| return | |
| end | |
| local old_default = build_old_spec(ctx, true) or (ctx.module .. "." .. ctx.name) | |
| local new_default = ctx.module .. "." .. ctx.name .. "_new" | |
| choose("Deprecation mode", { "none", "soft", "hard" }, 1, function(dep) | |
| ask("Old (module.fun[/arity])", old_default, function(old_spec) | |
| ask("New (module.fun)", new_default, function(new_spec) | |
| ask("Extra args (optional)", "", function(extra) | |
| local cmd = { "mix", "igniter.refactor.rename_function", old_spec, new_spec } | |
| if dep and dep ~= "none" then | |
| table.insert(cmd, "--deprecate") | |
| table.insert(cmd, dep) | |
| end | |
| if extra and extra ~= "" then | |
| for tok in extra:gmatch("%S+") do | |
| table.insert(cmd, tok) | |
| end | |
| end | |
| table.insert(cmd, "--yes") | |
| run_mix(cmd, root) | |
| end) | |
| end) | |
| end) | |
| end) | |
| end | |
| return { | |
| { | |
| "LazyVim/LazyVim", | |
| keys = { | |
| { "<leader>ri", prompt_and_rename, desc = "Igniter: Rename Function" }, | |
| { | |
| "<leader>rI", | |
| function() | |
| local root = project_root() | |
| vim.ui.input({ prompt = "mix igniter.refactor.rename_function (full args): " }, function(line) | |
| if not line or line == "" then | |
| return | |
| end | |
| local args = {} | |
| for tok in line:gmatch("%S+") do | |
| table.insert(args, tok) | |
| end | |
| local cmd = { "mix", "igniter.refactor.rename_function" } | |
| vim.list_extend(cmd, args) | |
| local joined = " " .. table.concat(cmd, " ") .. " " | |
| if not joined:match("%s%-%-yes%s") and not joined:match("%s%-%-dry%-run%s") then | |
| table.insert(cmd, "--yes") | |
| end | |
| run_mix(cmd, root) | |
| end) | |
| end, | |
| desc = "Igniter: Raw Command", | |
| }, | |
| }, | |
| cmd = { "IgniterRename" }, | |
| init = function() | |
| vim.api.nvim_create_user_command("IgniterRename", function(opts) | |
| local root = project_root() | |
| local argv = {} | |
| for tok in (opts.args or ""):gmatch("%S+") do | |
| table.insert(argv, tok) | |
| end | |
| if #argv < 2 then | |
| vim.notify("Usage: :IgniterRename OldSpec NewSpec [--deprecate soft|hard ...]", vim.log.levels.WARN) | |
| return | |
| end | |
| local cmd = { "mix", "igniter.refactor.rename_function" } | |
| vim.list_extend(cmd, argv) | |
| local joined = " " .. table.concat(cmd, " ") .. " " | |
| if not joined:match("%s%-%-yes%s") and not joined:match("%s%-%-dry%-run%s") then | |
| table.insert(cmd, "--yes") | |
| end | |
| run_mix(cmd, root) | |
| end, { nargs = "+" }) | |
| end, | |
| }, | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment