From 45a888ee8a8268d4ccd29dc952c40d06d5533b1e Mon Sep 17 00:00:00 2001 From: Pranav Rao <56097527+pranavrao145@users.noreply.github.com> Date: Thu, 21 Oct 2021 14:38:37 -0400 Subject: [PATCH 1/6] feat(tmux): added option to use tmux terminals instead of nvim terminals This commit introduces a new module of harpoon, "harpoon.tmux", which provides several functions for using tmux windows for harpoon terminals. So far, all the basic features are supported, such as creating and moving to a new terminal, deleting all terminals, and sending commands to terminals etc. --- lua/harpoon/init.lua | 4 +- lua/harpoon/term.lua | 10 +-- lua/harpoon/tmux.lua | 142 ++++++++++++++++++++++++++++++++++++++++++ lua/harpoon/utils.lua | 29 +++++++++ 4 files changed, 178 insertions(+), 7 deletions(-) create mode 100755 lua/harpoon/tmux.lua diff --git a/lua/harpoon/init.lua b/lua/harpoon/init.lua index ae184ad..48fb8bd 100644 --- a/lua/harpoon/init.lua +++ b/lua/harpoon/init.lua @@ -17,12 +17,12 @@ local M = {} term = { cmds = { } - ... is there antyhnig that could be options? + ... is there anything that could be options? }, mark = { marks = { } - ... is there antyhnig that could be options? + ... is there anything that could be options? } } }, diff --git a/lua/harpoon/term.lua b/lua/harpoon/term.lua index fef47ec..e0a8c5d 100644 --- a/lua/harpoon/term.lua +++ b/lua/harpoon/term.lua @@ -9,7 +9,7 @@ local function create_terminal(create_with) if not create_with then create_with = ":terminal" end - log.trace("_create_terminal(): Init:", create_with) + log.trace("term: _create_terminal(): Init:", create_with) local current_id = vim.api.nvim_get_current_buf() vim.cmd(create_with) @@ -32,7 +32,7 @@ local function create_terminal(create_with) end local function find_terminal(args) - log.trace("_find_terminal(): Terminal:", args) + log.trace("term: _find_terminal(): Terminal:", args) if type(args) == "number" then args = { idx = args } end @@ -63,14 +63,14 @@ local function get_first_empty_slot() end M.gotoTerminal = function(idx) - log.trace("gotoTerminal(): Terminal:", idx) + log.trace("term: gotoTerminal(): Terminal:", idx) local term_handle = find_terminal(idx) vim.api.nvim_set_current_buf(term_handle.buf_id) end M.sendCommand = function(idx, cmd, ...) - log.trace("sendCommand(): Terminal:", idx) + log.trace("term: sendCommand(): Terminal:", idx) local term_handle = find_terminal(idx) if type(cmd) == "number" then @@ -88,7 +88,7 @@ M.sendCommand = function(idx, cmd, ...) end M.clear_all = function() - log.trace("clear_all(): Clearing all terminals.") + log.trace("term: clear_all(): Clearing all terminals.") for _, term in ipairs(terminals) do vim.api.nvim_buf_delete(term.buf_id, { force = true }) end diff --git a/lua/harpoon/tmux.lua b/lua/harpoon/tmux.lua new file mode 100755 index 0000000..661e6d5 --- /dev/null +++ b/lua/harpoon/tmux.lua @@ -0,0 +1,142 @@ +local harpoon = require("harpoon") +local log = require("harpoon.dev").log +local global_config = harpoon.get_global_settings() +local utils = require("harpoon.utils") + +local M = {} +local tmux_windows = {} + +local function create_terminal() + log.trace("tmux: _create_terminal())") + + local window_id + + -- Create a new tmux window and store the window id + local out, ret, _ = utils.get_os_command_output({ + "tmux", + "new-window", + "-P", + "-F", + "#{pane_id}", + }, vim.loop.cwd()) + + if ret == 0 then + window_id = out[1]:sub(2) + end + + if window_id == nil then + log.error("tmux: _create_terminal(): window_id is nil") + return nil + end + + return window_id +end + +-- Checks if the tmux window with the given window id exists +local function terminal_exists(window_id) + log.trace("_terminal_exists(): Window:", window_id) + + local exists = false + + local window_list, _, _ = utils.get_os_command_output({ + "tmux", + "list-windows", + }, vim.loop.cwd()) + + for _, line in pairs(window_list) do + local window_info = utils.split_string(line, "@")[1] + + if string.find(window_info, window_id) then + exists = true + end + end + + return exists +end + +local function find_terminal(args) + log.trace("tmux: _find_terminal(): Window:", args) + + if type(args) == "number" then + args = { idx = args } + end + + local window_handle = tmux_windows[args.idx] + local window_exists + + if window_handle then + window_exists = terminal_exists(window_handle.window_id) + end + + if not window_handle or not window_exists then + local window_id = create_terminal() + + if window_id == nil then + return + end + + window_handle = { + window_id = window_id, + } + + tmux_windows[args.idx] = window_handle + end + + return window_handle +end + +M.gotoTerminal = function(idx) + log.trace("tmux: gotoTerminal(): Window:", idx) + local window_handle = find_terminal(idx) + + local _, _, _ = utils.get_os_command_output({ + "tmux", + "select-window", + "-t", + "%" .. window_handle.window_id, + }, vim.loop.cwd()) +end + +M.sendCommand = function(idx, cmd, ...) + log.trace("tmux: sendCommand(): Window:", idx) + local window_handle = find_terminal(idx) + + if type(cmd) == "number" then + cmd = harpoon.get_term_config().cmds[cmd] + end + + if global_config.enter_on_sendcmd then + cmd = cmd .. "\n" + end + + if cmd then + log.debug("sendCommand:", cmd) + + -- Send the command to the given tmux window (creates it if it doesn't exist) + local _, _, _ = utils.get_os_command_output({ + "tmux", + "send-keys", + "-t", + "%" .. window_handle.window_id, + string.format(cmd, ...), + }, vim.loop.cwd()) + end +end + +M.clear_all = function() + log.trace("tmux: clear_all(): Clearing all tmux windows.") + + for _, window in ipairs(tmux_windows) do + -- Delete the current tmux window + local _, _, _ = utils.get_os_command_output({ + "tmux", + "kill-window", + "-t", + "%" .. window.window_id, + }, vim.loop.cwd()) + end + + tmux_windows = {} +end + +return M diff --git a/lua/harpoon/utils.lua b/lua/harpoon/utils.lua index 11161df..56d813e 100644 --- a/lua/harpoon/utils.lua +++ b/lua/harpoon/utils.lua @@ -1,11 +1,40 @@ local Path = require("plenary.path") local data_path = vim.fn.stdpath("data") +local Job = require("plenary.job") local M = { data_path = data_path, normalize_path = function(item) return Path:new(item):make_relative(vim.loop.cwd()) end, + get_os_command_output = function(cmd, cwd) + if type(cmd) ~= "table" then + print("Harpoon: [get_os_command_output]: cmd has to be a table") + return {} + end + local command = table.remove(cmd, 1) + local stderr = {} + local stdout, ret = Job + :new({ + command = command, + args = cmd, + cwd = cwd, + on_stderr = function(_, data) + table.insert(stderr, data) + end, + }) + :sync() + return stdout, ret, stderr + end, + split_string = function(str, delimiter) + local result = {} + + for match in (str .. delimiter):gmatch("(.-)" .. delimiter) do + table.insert(result, match) + end + + return result + end, } return M From 20590f151204e9e50334682b752c0dba2a3d77ac Mon Sep 17 00:00:00 2001 From: Pranav Rao <56097527+pranavrao145@users.noreply.github.com> Date: Fri, 22 Oct 2021 09:07:44 -0400 Subject: [PATCH 2/6] feat(tmux): added extra term cmd manipulation functions to tmux module In the future, these should probably be refactored out into the utils file beacuse they are the exact same functions as the one in the term module. --- lua/harpoon/tmux.lua | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lua/harpoon/tmux.lua b/lua/harpoon/tmux.lua index 661e6d5..fecfe29 100755 --- a/lua/harpoon/tmux.lua +++ b/lua/harpoon/tmux.lua @@ -85,6 +85,16 @@ local function find_terminal(args) return window_handle end +local function get_first_empty_slot() + log.trace("_get_first_empty_slot()") + for idx, cmd in pairs(harpoon.get_term_config().cmds) do + if cmd == "" then + return idx + end + end + return M.get_length() + 1 +end + M.gotoTerminal = function(idx) log.trace("tmux: gotoTerminal(): Window:", idx) local window_handle = find_terminal(idx) @@ -139,4 +149,51 @@ M.clear_all = function() tmux_windows = {} end +M.get_length = function() + log.trace("_get_length()") + return table.maxn(harpoon.get_term_config().cmds) +end + +M.valid_index = function(idx) + if idx == nil or idx > M.get_length() or idx <= 0 then + return false + end + return true +end + +M.emit_changed = function() + log.trace("_emit_changed()") + if harpoon.get_global_settings().save_on_change then + harpoon.save() + end +end + +M.add_cmd = function(cmd) + log.trace("add_cmd()") + local found_idx = get_first_empty_slot() + harpoon.get_term_config().cmds[found_idx] = cmd + M.emit_changed() +end + +M.rm_cmd = function(idx) + log.trace("rm_cmd()") + if not M.valid_index(idx) then + log.debug("rm_cmd(): no cmd exists for index", idx) + return + end + table.remove(harpoon.get_term_config().cmds, idx) + M.emit_changed() +end + +M.set_cmd_list = function(new_list) + log.trace("set_cmd_list(): New list:", new_list) + for k in pairs(harpoon.get_term_config().cmds) do + harpoon.get_term_config().cmds[k] = nil + end + for k, v in pairs(new_list) do + harpoon.get_term_config().cmds[k] = v + end + M.emit_changed() +end + return M From decdcd6971672e72fe4dc0e0655a8ce275375d4d Mon Sep 17 00:00:00 2001 From: Pranav Rao <56097527+pranavrao145@users.noreply.github.com> Date: Sat, 23 Oct 2021 16:41:42 -0400 Subject: [PATCH 3/6] feat(tmux): added configuration option for autoclosing terminals This commit adds a config option so that if the user wants to close all tmux terminals opened by harpoon on Vim exit, they can. --- lua/harpoon/init.lua | 1 + lua/harpoon/tmux.lua | 8 ++++++++ 2 files changed, 9 insertions(+) mode change 100755 => 100644 lua/harpoon/tmux.lua diff --git a/lua/harpoon/init.lua b/lua/harpoon/init.lua index 48fb8bd..94667a7 100644 --- a/lua/harpoon/init.lua +++ b/lua/harpoon/init.lua @@ -157,6 +157,7 @@ M.setup = function(config) ["save_on_toggle"] = false, ["save_on_change"] = true, ["enter_on_sendcmd"] = false, + ["tmux_autoclose_windows"] = false, }, }, expand_dir( c_config diff --git a/lua/harpoon/tmux.lua b/lua/harpoon/tmux.lua old mode 100755 new mode 100644 index fecfe29..c343269 --- a/lua/harpoon/tmux.lua +++ b/lua/harpoon/tmux.lua @@ -6,6 +6,14 @@ local utils = require("harpoon.utils") local M = {} local tmux_windows = {} +if global_config.tmux_autoclose_windows then + vim.cmd([[ + augroup HARPOON_TMUX + autocmd! + autocmd VimLeave * :lua require('harpoon.tmux').clear_all() + ]]) +end + local function create_terminal() log.trace("tmux: _create_terminal())") From 51d68e2ec67a0a86e5c2fd9e82f7cca0e5d4b413 Mon Sep 17 00:00:00 2001 From: Pranav Rao <56097527+pranavrao145@users.noreply.github.com> Date: Thu, 11 Nov 2021 18:40:12 -0500 Subject: [PATCH 4/6] fix(tmux): fixed tmux.clear_all function Using ipairs instead of pairs in this function caused it to stop working for some use cases. --- lua/harpoon/tmux.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/harpoon/tmux.lua b/lua/harpoon/tmux.lua index c343269..1229acf 100644 --- a/lua/harpoon/tmux.lua +++ b/lua/harpoon/tmux.lua @@ -144,7 +144,7 @@ end M.clear_all = function() log.trace("tmux: clear_all(): Clearing all tmux windows.") - for _, window in ipairs(tmux_windows) do + for _, window in pairs(tmux_windows) do -- Delete the current tmux window local _, _, _ = utils.get_os_command_output({ "tmux", From 02e91845bb7a9e88447e6ab2c1c187dca3d4e2ac Mon Sep 17 00:00:00 2001 From: Pranav Rao <56097527+pranavrao145@users.noreply.github.com> Date: Tue, 23 Nov 2021 14:42:42 -0500 Subject: [PATCH 5/6] refactor(cleanup): cleaned up some code in term and tmux modules Also styled init.lua. --- lua/harpoon/init.lua | 8 +------- lua/harpoon/term.lua | 1 + lua/harpoon/tmux.lua | 7 +++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lua/harpoon/init.lua b/lua/harpoon/init.lua index 9af279d..735e294 100644 --- a/lua/harpoon/init.lua +++ b/lua/harpoon/init.lua @@ -160,13 +160,7 @@ M.setup = function(config) ["tmux_autoclose_windows"] = false, ["excluded_filetypes"] = { "harpoon" }, }, - }, expand_dir( - c_config - ), expand_dir( - u_config - ), expand_dir( - config - )) + }, expand_dir(c_config), expand_dir(u_config), expand_dir(config)) -- There was this issue where the vim.loop.cwd() didn't have marks or term, but had -- an object for vim.loop.cwd() diff --git a/lua/harpoon/term.lua b/lua/harpoon/term.lua index e0a8c5d..0a0681c 100644 --- a/lua/harpoon/term.lua +++ b/lua/harpoon/term.lua @@ -40,6 +40,7 @@ local function find_terminal(args) if not term_handle or not vim.api.nvim_buf_is_valid(term_handle.buf_id) then local buf_id, term_id = create_terminal(args.create_with) if buf_id == nil then + error("Failed to find and create terminal.") return end diff --git a/lua/harpoon/tmux.lua b/lua/harpoon/tmux.lua index 1229acf..06c168e 100644 --- a/lua/harpoon/tmux.lua +++ b/lua/harpoon/tmux.lua @@ -51,6 +51,8 @@ local function terminal_exists(window_id) "list-windows", }, vim.loop.cwd()) + -- This has to be done this way because tmux has-session does not give + -- updated results for _, line in pairs(window_list) do local window_info = utils.split_string(line, "@")[1] @@ -80,6 +82,7 @@ local function find_terminal(args) local window_id = create_terminal() if window_id == nil then + error("Failed to find and create tmux window.") return end @@ -107,7 +110,7 @@ M.gotoTerminal = function(idx) log.trace("tmux: gotoTerminal(): Window:", idx) local window_handle = find_terminal(idx) - local _, _, _ = utils.get_os_command_output({ + utils.get_os_command_output({ "tmux", "select-window", "-t", @@ -146,7 +149,7 @@ M.clear_all = function() for _, window in pairs(tmux_windows) do -- Delete the current tmux window - local _, _, _ = utils.get_os_command_output({ + utils.get_os_command_output({ "tmux", "kill-window", "-t", From 9e5634ff53b5b8668de7dc0b3658eb00776bc7c8 Mon Sep 17 00:00:00 2001 From: Pranav Rao <56097527+pranavrao145@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:02:19 -0500 Subject: [PATCH 6/6] docs(README): added documentation for tmux module --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 1e0fbd0..8efbc9f 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,22 @@ Just call the following function to edit commands inside the list lua require('harpoon.cmd-ui').toggle_quick_menu() ``` +#### Tmux Integration +Harpoon also supports all terminal operations (see above) with tmux terminals. +The configuration for using tmux is exactly the same as the config for using nvim +terminals. To use tmux terminals instead of nvim terminals, simply replace +`harpoon.term` with `harpoon.tmux` in your require statement. + +For example: + +```lua +-- goes to the first tmux window +lua require("harpoon.tmux").gotoTerminal(1) + +-- sends a command to the first tmux window +lua require("harpoon.tmux").sendCommand(1, "ls -la") +``` + ### Setup Setup should be called once. @@ -146,6 +162,7 @@ require("harpoon").setup({ save_on_toggle = false, save_on_change = true, enter_on_sendcmd = false, + tmux_autoclose_windows = false, excluded_filetypes = { "harpoon" } }, ... your other configs ... @@ -160,6 +177,8 @@ require("harpoon").setup({ what I have found). * `enter_on_sendcmd` will set harpoon to run the command immediately as it's passed to the terminal when calling `sendCommand`. +* `tmux_autoclose_windows` will close any tmux windows harpoon that harpoon creates + when you close Neovim. * `excluded_filetypes` filetypes that you want to prevent from adding to the harpoon list menu. #### Preconfigured Terminal Commands