Created
July 25, 2025 06:19
-
-
Save Dima-369/ec1fc3a523a80aa456ef10070781c2e3 to your computer and use it in GitHub Desktop.
Automatically rename terminal buffers to running command
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
| local M = {} | |
| -- Configuration | |
| local config = { | |
| update_interval = 2000, -- Update every 2 seconds | |
| enable_auto_rename = true, | |
| } | |
| local group = vim.api.nvim_create_augroup('DynamicTermName', { clear = true }) | |
| local timers = {} -- Store timers for each terminal buffer | |
| -- Function to get the currently running command in a terminal | |
| local function get_running_command(bufnr, callback) | |
| if not vim.api.nvim_buf_is_valid(bufnr) or vim.bo[bufnr].buftype ~= 'terminal' then | |
| callback(nil) | |
| return | |
| end | |
| -- First try to get info from b:term_title (more responsive) | |
| local ok, term_title = pcall(vim.api.nvim_buf_get_var, bufnr, 'term_title') | |
| if ok and term_title and term_title ~= "" then | |
| -- Extract command from terminal title | |
| local cmd_from_title = term_title:match("([^:]+)$") -- Get part after last colon | |
| if cmd_from_title and cmd_from_title ~= "" and | |
| not cmd_from_title:match("^[~/]") and -- Not a path | |
| cmd_from_title ~= "fish" and cmd_from_title ~= "bash" and cmd_from_title ~= "zsh" then | |
| callback(cmd_from_title:gsub("^%s+", ""):gsub("%s+$", "")) -- Trim whitespace | |
| return | |
| end | |
| end | |
| -- Fallback to process inspection | |
| local terminal_job_id = vim.b[bufnr].terminal_job_id | |
| local job_pid = terminal_job_id and vim.fn.jobpid(terminal_job_id) | |
| if not job_pid or job_pid <= 0 then | |
| callback(nil) | |
| return | |
| end | |
| -- Check if terminal job is still running | |
| local channel = vim.api.nvim_buf_get_option(bufnr, 'channel') | |
| if channel and channel ~= 0 then | |
| local running = vim.fn.jobwait({channel}, 0)[1] == -1 | |
| if not running then | |
| callback("exited") | |
| return | |
| end | |
| end | |
| -- Get child processes of the shell | |
| local cmd = string.format("pgrep -P %d 2>/dev/null | head -1", job_pid) | |
| vim.fn.jobstart(cmd, { | |
| on_stdout = function(_, data) | |
| local child_pid = nil | |
| for _, line in ipairs(data) do | |
| if line and line ~= "" then | |
| child_pid = vim.trim(line) | |
| break | |
| end | |
| end | |
| if child_pid then | |
| -- Get the full command line of the child process | |
| local ps_cmd = string.format("ps -o args= -p %s 2>/dev/null", child_pid) | |
| vim.fn.jobstart(ps_cmd, { | |
| on_stdout = function(_, ps_data) | |
| local full_command = nil | |
| for _, line in ipairs(ps_data) do | |
| if line and line ~= "" then | |
| full_command = vim.trim(line) | |
| break | |
| end | |
| end | |
| if full_command then | |
| -- Extract just the command name and first few args | |
| local parts = vim.split(full_command, " ") | |
| local cmd_name = vim.fn.fnamemodify(parts[1], ":t") | |
| -- Include first argument if it's not a flag | |
| if #parts > 1 and not parts[2]:match("^%-") then | |
| callback(cmd_name .. " " .. parts[2]) | |
| else | |
| callback(cmd_name) | |
| end | |
| else | |
| callback(nil) | |
| end | |
| end, | |
| on_exit = function(_, exit_code) | |
| if exit_code ~= 0 then | |
| callback(nil) | |
| end | |
| end, | |
| }) | |
| else | |
| callback(nil) | |
| end | |
| end, | |
| on_exit = function(_, exit_code) | |
| if exit_code ~= 0 then | |
| callback(nil) | |
| end | |
| end, | |
| }) | |
| end | |
| -- Function to update terminal buffer name | |
| local function update_terminal_name(bufnr) | |
| if not vim.api.nvim_buf_is_valid(bufnr) or vim.bo[bufnr].buftype ~= 'terminal' then | |
| return | |
| end | |
| get_running_command(bufnr, function(command) | |
| -- Ensure buffer is still valid | |
| if not vim.api.nvim_buf_is_valid(bufnr) or not vim.b[bufnr].terminal_job_id then | |
| return | |
| end | |
| local display_name | |
| if command == "exited" then | |
| display_name = "[TERM] exited" | |
| elseif command and command ~= "" and command ~= "fish" and command ~= "bash" and command ~= "zsh" then | |
| display_name = "[TERM] " .. command | |
| else | |
| -- Show shell name when no specific command is running | |
| local shell = vim.fn.fnamemodify(vim.o.shell, ':t') | |
| display_name = "[TERM] " .. shell | |
| end | |
| -- Get current buffer name to avoid unnecessary updates | |
| local current_name = vim.api.nvim_buf_get_name(bufnr) | |
| local current_display = vim.fn.fnamemodify(current_name, ':t') | |
| if current_display ~= display_name then | |
| -- Create unique name by including buffer number | |
| local unique_name = display_name .. "-" .. bufnr | |
| -- Use pcall to handle naming conflicts gracefully | |
| local success = pcall(vim.api.nvim_buf_set_name, bufnr, unique_name) | |
| if not success then | |
| -- Fallback with timestamp if naming still fails | |
| local timestamp = os.time() | |
| local fallback_name = display_name .. "-" .. bufnr .. "-" .. timestamp | |
| pcall(vim.api.nvim_buf_set_name, bufnr, fallback_name) | |
| end | |
| end | |
| end) | |
| end | |
| -- Function to start monitoring a terminal buffer | |
| local function start_monitoring(bufnr) | |
| if timers[bufnr] then | |
| timers[bufnr]:stop() | |
| end | |
| timers[bufnr] = vim.loop.new_timer() | |
| timers[bufnr]:start(0, config.update_interval, vim.schedule_wrap(function() | |
| update_terminal_name(bufnr) | |
| end)) | |
| end | |
| -- Function to stop monitoring a terminal buffer | |
| local function stop_monitoring(bufnr) | |
| if timers[bufnr] then | |
| timers[bufnr]:stop() | |
| timers[bufnr]:close() | |
| timers[bufnr] = nil | |
| end | |
| end | |
| -- Setup function | |
| function M.setup(opts) | |
| if opts then | |
| config = vim.tbl_extend("force", config, opts) | |
| end | |
| if not config.enable_auto_rename then | |
| return | |
| end | |
| -- Start monitoring when terminal opens | |
| vim.api.nvim_create_autocmd('TermOpen', { | |
| group = group, | |
| callback = function() | |
| local bufnr = vim.api.nvim_get_current_buf() | |
| vim.defer_fn(function() | |
| start_monitoring(bufnr) | |
| end, 500) -- Small delay to let terminal initialize | |
| end, | |
| }) | |
| -- Monitor terminal title changes for immediate updates (more responsive) | |
| vim.api.nvim_create_autocmd('TermRequest', { | |
| group = group, | |
| callback = function(args) | |
| -- Update name when terminal sends title update sequences | |
| if args.data and args.data.sequence then | |
| local seq = args.data.sequence | |
| -- OSC 0, 1, 2 are window/icon/title setting sequences | |
| if seq:match('\027]0;') or seq:match('\027]1;') or seq:match('\027]2;') then | |
| vim.defer_fn(function() | |
| update_terminal_name(args.buf) | |
| end, 50) | |
| end | |
| end | |
| end, | |
| }) | |
| -- Stop monitoring when terminal closes | |
| vim.api.nvim_create_autocmd('BufDelete', { | |
| group = group, | |
| callback = function() | |
| local bufnr = tonumber(vim.fn.expand('<abuf>')) | |
| if bufnr then | |
| stop_monitoring(bufnr) | |
| end | |
| end, | |
| }) | |
| -- Also monitor when entering terminal buffers | |
| vim.api.nvim_create_autocmd('BufEnter', { | |
| pattern = 'term://*', | |
| group = group, | |
| callback = function() | |
| local bufnr = vim.api.nvim_get_current_buf() | |
| if not timers[bufnr] then | |
| start_monitoring(bufnr) | |
| end | |
| end, | |
| }) | |
| end | |
| -- Manual rename function | |
| function M.rename_current_terminal() | |
| local bufnr = vim.api.nvim_get_current_buf() | |
| if vim.bo[bufnr].buftype == 'terminal' then | |
| update_terminal_name(bufnr) | |
| vim.notify("Terminal buffer renamed", vim.log.levels.INFO) | |
| else | |
| vim.notify("Not in a terminal buffer", vim.log.levels.WARN) | |
| end | |
| end | |
| -- Function to rename all terminal buffers | |
| function M.rename_all_terminals() | |
| local count = 0 | |
| for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do | |
| if vim.api.nvim_buf_is_valid(bufnr) and | |
| vim.api.nvim_get_option_value("buftype", { buf = bufnr }) == "terminal" then | |
| update_terminal_name(bufnr) | |
| count = count + 1 | |
| end | |
| end | |
| vim.notify("Renamed " .. count .. " terminal buffers", vim.log.levels.INFO) | |
| end | |
| return M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment