diff --git a/.luacheckrc b/.luacheckrc index 4de34b6..b87a48f 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -6,5 +6,7 @@ globals = { "HarpoonConfig", "Harpoon_bufh", "Harpoon_win_id", + "Harpoon_cmd_win_id", + "Harpoon_cmd_bufh", } read_globals = { "vim" } diff --git a/lua/harpoon/cmd-ui.lua b/lua/harpoon/cmd-ui.lua new file mode 100644 index 0000000..7a03cd0 --- /dev/null +++ b/lua/harpoon/cmd-ui.lua @@ -0,0 +1,136 @@ +local harpoon = require("harpoon") +local popup = require("popup") +local log = require("harpoon.dev").log +local term = require("harpoon.term") + +local M = {} + +Harpoon_cmd_win_id = nil +Harpoon_cmd_bufh = nil + +local function close_menu(force_save) + force_save = force_save or false + local global_config = harpoon.get_global_settings() + + if global_config.save_on_toggle or force_save then + require("harpoon.cmd-ui").on_menu_save() + end + + vim.api.nvim_win_close(Harpoon_cmd_win_id, true) + + Harpoon_cmd_win_id = nil + Harpoon_cmd_bufh = nil +end + +local function create_window() + log.trace("_create_window()") + local config = harpoon.get_menu_config() + local width = config.width or 60 + local height = config.height or 10 + local borderchars = config.borderchars + or { "─", "│", "─", "│", "╭", "╮", "╯", "╰" } + local bufnr = vim.api.nvim_create_buf(false, false) + + local Harpoon_cmd_win_id, win = popup.create(bufnr, { + title = "Harpoon Commands", + highlight = "HarpoonWindow", + line = math.floor(((vim.o.lines - height) / 2) - 1), + col = math.floor((vim.o.columns - width) / 2), + minwidth = width, + minheight = height, + borderchars = borderchars, + }) + + vim.api.nvim_win_set_option( + win.border.win_id, + "winhl", + "Normal:HarpoonBorder" + ) + + return { + bufnr = bufnr, + win_id = Harpoon_cmd_win_id, + } +end + +local function is_white_space(str) + return str:gsub("%s", "") == "" +end + +local function get_menu_items() + log.trace("_get_menu_items()") + local lines = vim.api.nvim_buf_get_lines(Harpoon_cmd_bufh, 0, -1, true) + local indices = {} + + for _, line in pairs(lines) do + if not is_white_space(line) then + table.insert(indices, line) + end + end + + return indices +end + +M.toggle_quick_menu = function() + log.trace("cmd-ui#toggle_quick_menu()") + if + Harpoon_cmd_win_id ~= nil + and vim.api.nvim_win_is_valid(Harpoon_cmd_win_id) + then + close_menu() + return + end + + local win_info = create_window() + local contents = {} + local global_config = harpoon.get_global_settings() + + Harpoon_cmd_win_id = win_info.win_id + Harpoon_cmd_bufh = win_info.bufnr + + for idx, cmd in pairs(harpoon.get_term_config().cmds) do + contents[idx] = cmd + end + + vim.api.nvim_win_set_option(Harpoon_cmd_win_id, "number", true) + vim.api.nvim_buf_set_name(Harpoon_cmd_bufh, "harpoon-cmd-menu") + vim.api.nvim_buf_set_lines(Harpoon_cmd_bufh, 0, #contents, false, contents) + vim.api.nvim_buf_set_option(Harpoon_cmd_bufh, "filetype", "harpoon") + vim.api.nvim_buf_set_option(Harpoon_cmd_bufh, "buftype", "acwrite") + vim.api.nvim_buf_set_option(Harpoon_cmd_bufh, "bufhidden", "delete") + -- TODO: maybe vim.fn.input() can be used to implement some select_menu_item + -- vim.api.nvim_buf_set_keymap( + -- Harpoon_cmd_bufh, + -- "n", + -- "", + -- ":lua require('harpoon.cmd-ui').select_menu_item()", + -- {} + -- ) + vim.cmd( + string.format( + "autocmd BufWriteCmd :lua require('harpoon.cmd-ui').on_menu_save()", + Harpoon_cmd_bufh + ) + ) + if global_config.save_on_change then + vim.cmd( + string.format( + "autocmd TextChanged,TextChangedI :lua require('harpoon.cmd-ui').on_menu_save()", + Harpoon_cmd_bufh + ) + ) + end + vim.cmd( + string.format( + "autocmd BufModifiedSet set nomodified", + Harpoon_cmd_bufh + ) + ) +end + +M.on_menu_save = function() + log.trace("cmd-ui#on_menu_save()") + term.set_cmd_list(get_menu_items()) +end + +return M diff --git a/lua/harpoon/term.lua b/lua/harpoon/term.lua index 0c63699..768991c 100644 --- a/lua/harpoon/term.lua +++ b/lua/harpoon/term.lua @@ -51,6 +51,16 @@ local function find_terminal(args) return term_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("gotoTerminal(): Terminal:", idx) local term_handle = find_terminal(idx) @@ -80,4 +90,51 @@ M.clear_all = function() terminals = {} 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 diff --git a/lua/harpoon/test/manage_cmd_spec.lua b/lua/harpoon/test/manage_cmd_spec.lua new file mode 100644 index 0000000..f63ad51 --- /dev/null +++ b/lua/harpoon/test/manage_cmd_spec.lua @@ -0,0 +1,141 @@ +local harpoon = require("harpoon") +local term = require("harpoon.term") + +local function assert_table_equals(tbl1, tbl2) + if #tbl1 ~= #tbl2 then + assert(false, "" .. #tbl1 .. " != " .. #tbl2) + end + for i = 1, #tbl1 do + if tbl1[i] ~= tbl2[i] then + assert.equals(tbl1[i], tbl2[i]) + end + end +end + +describe("basic functionalities", function() + local emitted + local cmds + + before_each(function() + emitted = false + cmds = {} + harpoon.get_term_config = function() + return { + cmds = cmds, + } + end + term.emit_changed = function() + emitted = true + end + end) + + it("add_cmd for empty", function() + term.add_cmd("cmake ..") + local expected_result = { + "cmake ..", + } + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + assert.equals(emitted, true) + end) + + it("add_cmd for non_empty", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + local expected_result = { + "cmake ..", + "make", + "ninja", + } + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + assert.equals(emitted, true) + end) + + it("rm_cmd: removing a valid element", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + term.rm_cmd(2) + local expected_result = { + "cmake ..", + "ninja", + } + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + assert.equals(emitted, true) + end) + + it("rm_cmd: remove first element", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + term.rm_cmd(1) + local expected_result = { + "make", + "ninja", + } + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + assert.equals(emitted, true) + end) + + it("rm_cmd: remove last element", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + term.rm_cmd(3) + local expected_result = { + "cmake ..", + "make", + } + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + assert.equals(emitted, true) + end) + + it("rm_cmd: trying to remove invalid element", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + term.rm_cmd(5) + local expected_result = { + "cmake ..", + "make", + "ninja", + } + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + assert.equals(emitted, true) + term.rm_cmd(0) + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + term.rm_cmd(-1) + assert_table_equals(harpoon.get_term_config().cmds, expected_result) + end) + + it("get_length", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + assert.equals(term.get_length(), 3) + end) + + it("valid_index", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + assert(term.valid_index(1)) + assert(term.valid_index(2)) + assert(term.valid_index(3)) + assert(not term.valid_index(0)) + assert(not term.valid_index(-1)) + assert(not term.valid_index(4)) + end) + + it("set_cmd_list", function() + term.add_cmd("cmake ..") + term.add_cmd("make") + term.add_cmd("ninja") + term.set_cmd_list({ "make uninstall", "make install" }) + local expected_result = { + "make uninstall", + "make install", + } + assert_table_equals(expected_result, harpoon.get_term_config().cmds) + end) +end)