From c41ffb700aea245a9fe15146aa4597bd52fe6fca Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 19:49:36 +0200 Subject: [PATCH 01/16] init.lua: add types, add config, setup and handler function --- lua/menu/init.lua | 273 ++++++++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 105 deletions(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index d141cdc..76cb187 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -1,110 +1,173 @@ local M = {} -local api = vim.api -local state = require "menu.state" -local layout = require "menu.layout" -local ns = api.nvim_create_namespace "NvMenu" -local volt = require "volt" -local volt_events = require "volt.events" -local mappings = require "menu.mappings" - +local state = require("menu.state") +local layout = require("menu.layout") +local ns = vim.api.nvim_create_namespace("NvMenu") +local volt = require("volt") +local volt_events = require("volt.events") +local mappings = require("menu.mappings") +local utils = require("menu.utils") + +---@class MenuItem +---@field name string +---@field cmd string|fun():nil? +---@field items MenuItem[]? +---@field rtxt string + +---@class MenuOpenOpts +---@field mouse boolean? +---@field nested boolean? +---@field item_gap integer? +---@field border boolean? + +---@param items MenuItem[] +---@param opts MenuOpenOpts M.open = function(items, opts) - opts = opts or {} - - local cur_buf = api.nvim_get_current_buf() - - if vim.bo[cur_buf].ft ~= "NvMenu" then - state.old_data = { - buf = api.nvim_get_current_buf(), - win = api.nvim_get_current_win(), - cursor = api.nvim_win_get_cursor(0), - } - end - - items = type(items) == "table" and items or require("menus." .. items) - - if not state.config then - state.config = opts - end - - local config = state.config - - local buf = api.nvim_create_buf(false, true) - state.bufs[buf] = { items = items, item_gap = opts.item_gap or 5 } - table.insert(state.bufids, buf) - - local h = #items - local bufv = state.bufs[buf] - bufv.w = require("menu.utils").get_width(items) - bufv.w = bufv.w + bufv.item_gap - - local win_opts = { - relative = config.mouse and "mouse" or "cursor", - width = bufv.w, - height = h, - row = 1, - col = 0, - border = "single", - style = "minimal", - zindex = 99 + #state.bufids, - } - - if opts.nested then - win_opts.relative = "win" - - if config.mouse then - local pos = vim.fn.getmousepos() - win_opts.win = pos.winid - win_opts.col = api.nvim_win_get_width(pos.winid) + 2 - win_opts.row = pos.winrow - 2 - else - win_opts.win = api.nvim_get_current_win() - win_opts.col = api.nvim_win_get_width(win_opts.win) + 2 - win_opts.row = api.nvim_win_get_cursor(win_opts.win)[1] - 1 - end - end - - local win = api.nvim_open_win(buf, not config.mouse, win_opts) - - volt.gen_data { - { buf = buf, ns = ns, layout = layout }, - } - - if config.border then - vim.wo[win].winhl = "Normal:Normal,FloatBorder:LineNr" - else - vim.wo[win].winhl = "Normal:ExBlack2Bg,FloatBorder:ExBlack2Border" - end - - volt.run(buf, { h = h, w = bufv.w }) - vim.bo[buf].filetype = "NvMenu" - - volt_events.add(buf) - - local close_post = function() - state.bufs = {} - state.config = nil - - if api.nvim_win_is_valid(state.old_data.win) then - api.nvim_set_current_win(state.old_data.win) - vim.schedule(function() - local cursor_line = math.max(1,state.old_data.cursor[1]) - local cursor_col = math.max(0, state.old_data.cursor[2]) - - api.nvim_win_set_cursor(state.old_data.win, { cursor_line, cursor_col }) - end) - end - - state.bufids = {} - end - - volt.mappings { bufs = vim.tbl_keys(state.bufs), after_close = close_post } - - if not config.mouse then - mappings.nav_win() - mappings.actions(items, buf) - else - mappings.auto_close() - end + opts = opts or {} + + local cur_buf = vim.api.nvim_get_current_buf() + + if vim.bo[cur_buf].ft ~= "NvMenu" then + state.old_data = { + buf = vim.api.nvim_get_current_buf(), + win = vim.api.nvim_get_current_win(), + cursor = vim.api.nvim_win_get_cursor(0), + } + end + + items = type(items) == "table" and items or require("menus." .. items) + + if not state.config then + state.config = opts + end + + local config = state.config + + local buf = vim.api.nvim_create_buf(false, true) + state.bufs[buf] = { items = items, item_gap = opts.item_gap or 5 } + table.insert(state.bufids, buf) + + local h = #items + local bufv = state.bufs[buf] + bufv.w = utils.get_width(items) + bufv.w = bufv.w + bufv.item_gap + + local win_opts = { + relative = config.mouse and "mouse" or "cursor", + width = bufv.w, + height = h, + row = 1, + col = 0, + border = "single", + style = "minimal", + zindex = 99 + #state.bufids, + } + + if opts.nested then + win_opts.relative = "win" + + if config.mouse then + local pos = vim.fn.getmousepos() + win_opts.win = pos.winid + win_opts.col = vim.api.nvim_win_get_width(pos.winid) + 2 + win_opts.row = pos.winrow - 2 + else + win_opts.win = vim.api.nvim_get_current_win() + win_opts.col = vim.api.nvim_win_get_width(win_opts.win) + 2 + win_opts.row = vim.api.nvim_win_get_cursor(win_opts.win)[1] - 1 + end + end + + local win = vim.api.nvim_open_win(buf, not config.mouse, win_opts) + + volt.gen_data({ + { buf = buf, ns = ns, layout = layout }, + }) + + if config.border then + vim.wo[win].winhl = "Normal:Normal,FloatBorder:LineNr" + else + vim.wo[win].winhl = "Normal:ExBlack2Bg,FloatBorder:ExBlack2Border" + end + + volt.run(buf, { h = h, w = bufv.w }) + vim.bo[buf].filetype = "NvMenu" + + volt_events.add(buf) + + local close_post = function() + state.bufs = {} + state.config = nil + + if vim.api.nvim_win_is_valid(state.old_data.win) then + vim.api.nvim_set_current_win(state.old_data.win) + vim.schedule(function() + local cursor_line = math.max(1, state.old_data.cursor[1]) + local cursor_col = math.max(0, state.old_data.cursor[2]) + + vim.api.nvim_win_set_cursor(state.old_data.win, { cursor_line, cursor_col }) + end) + end + + state.bufids = {} + end + + volt.mappings({ bufs = vim.tbl_keys(state.bufs), after_close = close_post }) + + if not config.mouse then + mappings.nav_win() + mappings.actions(items, buf) + else + mappings.auto_close() + end +end + +M.delete_old_menus = utils.delete_old_menus + +---@class MenuConfig +---@field ft {string: string|MenuItem} +---@field default_menu string|MenuItem +---@field default_mappings boolean + +---@type MenuConfig +M.config = { + ft = { + NvimTree = "nvimtree", + ["neo-tree"] = "neo-tree", + }, + default_menu = "default", + default_mappings = false, +} + +---@param args MenuConfig +M.setup = function(args) + M.config = vim.tbl_deep_extend("force", M.config, args or {}) + if M.config.default_mappings then + vim.keymap.set("n", "", function() + M.handler({ mouse = false }) + end) + vim.keymap.set({ "n", "v" }, "", function() + M.handler({ mouse = true }) + end) + end +end + +---@param opts MenuOpenOpts +M.handler = function(opts) + opts = opts or {} + if opts.mouse then + -- On second mouse click remove current manu and reopen it. + require("menu.utils").delete_old_menus() + vim.cmd.exec('"normal! \\"') + else + if #require("menu.state").bufids > 0 then + -- if a menu is already open, close it. + require("menu.utils").delete_old_menus() + return + end + end + local buf = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) + local items = M.config.ft[vim.bo[buf]] or M.config.default_menu + require("menu").open(items, opts) end return M From f3d619e05bfb21769033a2347ba84b28dedd4386 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 20:29:02 +0200 Subject: [PATCH 02/16] allow items to be a function --- lua/menu/init.lua | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index 76cb187..5299d6f 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -9,17 +9,17 @@ local utils = require("menu.utils") ---@class MenuItem ---@field name string ----@field cmd string|fun():nil? ----@field items MenuItem[]? ----@field rtxt string +---@field cmd? string|fun():nil +---@field items? MenuItem[]|fun():MenuItem[] +---@field rtxt? string ---@class MenuOpenOpts ----@field mouse boolean? ----@field nested boolean? ----@field item_gap integer? ----@field border boolean? +---@field mouse? boolean +---@field nested? boolean +---@field item_gap? integer +---@field border? boolean ----@param items MenuItem[] +---@param items string|MenuItem[]|fun():MenuItem[] ---@param opts MenuOpenOpts M.open = function(items, opts) opts = opts or {} @@ -34,7 +34,9 @@ M.open = function(items, opts) } end + items = type(items) == "function" and items() or items items = type(items) == "table" and items or require("menus." .. items) + items = type(items) == "function" and items() or items if not state.config then state.config = opts @@ -124,9 +126,9 @@ end M.delete_old_menus = utils.delete_old_menus ---@class MenuConfig ----@field ft {string: string|MenuItem} ----@field default_menu string|MenuItem ----@field default_mappings boolean +---@field ft? {string: string|MenuItem|fun():MenuItem} +---@field default_menu? string|MenuItem +---@field default_mappings? boolean ---@type MenuConfig M.config = { @@ -167,7 +169,7 @@ M.handler = function(opts) end local buf = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) local items = M.config.ft[vim.bo[buf]] or M.config.default_menu - require("menu").open(items, opts) + M.open(items, opts) end return M From e9c3c66b701a868fc2305e18faa8d2e2bd51900c Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 20:29:33 +0200 Subject: [PATCH 03/16] add which-key --- lua/menus/which-key.lua | 81 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 lua/menus/which-key.lua diff --git a/lua/menus/which-key.lua b/lua/menus/which-key.lua new file mode 100644 index 0000000..0e13b53 --- /dev/null +++ b/lua/menus/which-key.lua @@ -0,0 +1,81 @@ +function string_startswith(self, start) + ---@diagnostic disable-next-line: param-type-mismatch + return self:sub(1, #start) == start +end + +local walk + +local node_to_item = function(node, prefix) + -- Create the name. + local name = node.keymap and (node.keymap.desc or node.keymap.rhs) or node.keys + -- Create key string that will be triggered in normal mode by this shortcut. + local keys = node.keys:gsub("", " "):gsub("^ ", "1 ") + -- Create element to add. Elements with children have items. + if next(node._children) == nil then + -- If item has no children, execute a normal command as part of it. + return { name = name, cmd = function() vim.cmd("normal! " .. keys) end, rtxt = keys:sub(-1) } + else + -- If item has children, descend to them. + return { name = name, items = function() walk(node, prefix) end, rtxt = keys:sub(-1) } + end +end + +---@param node wk.Node +---@param prefix string +walk = function(node, prefix) + local items = {} + local children = node._children + assert(children ~= nil, vim.inspect(node)) + for _, child in pairs(children) do + if string_startswith(child.keys, prefix) then table.insert(items, node_to_item(child, prefix)) end + end + return items +end + +local menu = walk(require("which-key.buf").get({ mode = "n" }).tree.root, "") +print(vim.inspect(menu)) +return menu + +-- ---@param node wk.Node +-- ---@param prefix string +-- local function walker(node, prefix) +-- -- Only filter on the prefix. +-- if not string_startswith(node.keys, prefix) then return end +-- -- Extract parents +-- local parents = {} +-- if true then +-- local parent = node.parent +-- while parent do +-- table.insert(parents, 1, parent) +-- parent = parent.parent +-- end +-- end +-- -- Find the current node to add to. +-- local cur = root +-- if true then +-- local prev = nil +-- for i, key in ipairs(node.path) do +-- -- Ignore last - we will add element for it +-- if i == #node.path then break end +-- -- Iterate over elements in cur to find if it matches. +-- local found = nil +-- for _, j in ipairs(cur) do +-- if j.rtxt == key then +-- found = cur +-- break +-- end +-- end +-- assert(found, vim.inspect(node) .. vim.inspect(cur)) +-- cur = found.items +-- end +-- end +-- -- +-- table.insert(cur, add) +-- -- +-- return add +-- end +-- +-- require("which-key.buf").get({ mode = "n" }).tree:walk(function(node) return walker(node, "") end) +-- +-- print(vim.inspect(root)) +-- From cc3df4f9a433c42fcb8b6d0e36e018ecc75429c0 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:06:06 +0200 Subject: [PATCH 04/16] implement filetype detection with require --- lua/menu/init.lua | 22 +++++++++++++++------ lua/menus/{nvimtree.lua => ft/NvimTree.lua} | 0 lua/menus/{ => ft}/neo-tree.lua | 0 3 files changed, 16 insertions(+), 6 deletions(-) rename lua/menus/{nvimtree.lua => ft/NvimTree.lua} (100%) rename lua/menus/{ => ft}/neo-tree.lua (100%) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index 5299d6f..9869df8 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -132,10 +132,7 @@ M.delete_old_menus = utils.delete_old_menus ---@type MenuConfig M.config = { - ft = { - NvimTree = "nvimtree", - ["neo-tree"] = "neo-tree", - }, + ft = {}, default_menu = "default", default_mappings = false, } @@ -156,10 +153,12 @@ end ---@param opts MenuOpenOpts M.handler = function(opts) opts = opts or {} + local window = 0 if opts.mouse then -- On second mouse click remove current manu and reopen it. require("menu.utils").delete_old_menus() vim.cmd.exec('"normal! \\"') + window = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) else if #require("menu.state").bufids > 0 then -- if a menu is already open, close it. @@ -167,8 +166,19 @@ M.handler = function(opts) return end end - local buf = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) - local items = M.config.ft[vim.bo[buf]] or M.config.default_menu + local ft = vim.bo[vim.api.nvim_win_get_buf(window)].ft + -- First try user filetype overwrites. + local items = M.config.ft[ft] + if not items then + -- Then try filetype specific menus. + local ok, mod = pcall(require, "menus.ft." .. ft) + if ok then + items = mod + else + -- Fallback to defaults. + items = M.config.default_menu or "default" + end + end M.open(items, opts) end diff --git a/lua/menus/nvimtree.lua b/lua/menus/ft/NvimTree.lua similarity index 100% rename from lua/menus/nvimtree.lua rename to lua/menus/ft/NvimTree.lua diff --git a/lua/menus/neo-tree.lua b/lua/menus/ft/neo-tree.lua similarity index 100% rename from lua/menus/neo-tree.lua rename to lua/menus/ft/neo-tree.lua From e61287d789f8a37860c029ff8fc5f57f46929081 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:06:53 +0200 Subject: [PATCH 05/16] which-key: properly walk on prefix, remove first stage, add sorting --- lua/menus/which-key.lua | 116 +++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 68 deletions(-) diff --git a/lua/menus/which-key.lua b/lua/menus/which-key.lua index 0e13b53..4214b1c 100644 --- a/lua/menus/which-key.lua +++ b/lua/menus/which-key.lua @@ -1,81 +1,61 @@ -function string_startswith(self, start) - ---@diagnostic disable-next-line: param-type-mismatch - return self:sub(1, #start) == start +---@param self string +---@param start string +local function string_startswith(self, start) + ---@diagnostic disable-next-line: param-type-mismatch + return self:sub(1, #start) == start end local walk +---@param node wk.Node +---@param prefix string +---@return MenuItem[] local node_to_item = function(node, prefix) - -- Create the name. - local name = node.keymap and (node.keymap.desc or node.keymap.rhs) or node.keys - -- Create key string that will be triggered in normal mode by this shortcut. - local keys = node.keys:gsub("", " "):gsub("^ ", "1 ") - -- Create element to add. Elements with children have items. - if next(node._children) == nil then - -- If item has no children, execute a normal command as part of it. - return { name = name, cmd = function() vim.cmd("normal! " .. keys) end, rtxt = keys:sub(-1) } - else - -- If item has children, descend to them. - return { name = name, items = function() walk(node, prefix) end, rtxt = keys:sub(-1) } - end + -- Create the name. + local name = node.mapping and node.mapping.desc + or node.keymap and (node.keymap.desc or node.keymap.rhs) + or node.keys + -- Create element to add. Elements with children have items. + if next(node._children) then + -- If item has children, descend to them. + return { + name = name .. " " .. node.path[#node.path], + items = walk(node, prefix), + rtxt = node.path[#node.path] + } + else + -- If keymap is a callback, we can just straight call it, which is more reliable and easier. + -- If key is , it has to start with 1 for normal! command to pick it up properly. + local cmd = node.keymap and node.keymap.callback or "normal! " .. node.keys:gsub("", " "):gsub("^ ", "1 ") + -- If item has no children, execute a normal command as part of it. + return { name = name, cmd = cmd, rtxt = node.path[#node.path] } + end end ---@param node wk.Node ---@param prefix string +---@return MenuItem[]? walk = function(node, prefix) - local items = {} - local children = node._children - assert(children ~= nil, vim.inspect(node)) - for _, child in pairs(children) do - if string_startswith(child.keys, prefix) then table.insert(items, node_to_item(child, prefix)) end - end - return items + ---@type MenuItem[] + local items = nil + local children = node._children + if children then + items = {} + for _, child in pairs(children) do + if string_startswith(child.keys, prefix) then + table.insert(items, node_to_item(child, prefix)) + end + end + table.sort(items, function(a, b) + return a.name < b.name + end) + end + return items end -local menu = walk(require("which-key.buf").get({ mode = "n" }).tree.root, "") -print(vim.inspect(menu)) -return menu - --- ---@param node wk.Node --- ---@param prefix string --- local function walker(node, prefix) --- -- Only filter on the prefix. --- if not string_startswith(node.keys, prefix) then return end --- -- Extract parents --- local parents = {} --- if true then --- local parent = node.parent --- while parent do --- table.insert(parents, 1, parent) --- parent = parent.parent --- end --- end --- -- Find the current node to add to. --- local cur = root --- if true then --- local prev = nil --- for i, key in ipairs(node.path) do --- -- Ignore last - we will add element for it --- if i == #node.path then break end --- -- Iterate over elements in cur to find if it matches. --- local found = nil --- for _, j in ipairs(cur) do --- if j.rtxt == key then --- found = cur --- break --- end --- end --- assert(found, vim.inspect(node) .. vim.inspect(cur)) --- cur = found.items --- end --- end --- -- --- table.insert(cur, add) --- -- --- return add --- end --- --- require("which-key.buf").get({ mode = "n" }).tree:walk(function(node) return walker(node, "") end) --- +local root = require("which-key.buf").get({ mode = "n" }).tree.root -- print(vim.inspect(root)) --- +local prefix = vim.g.mapleader:gsub(" ", "") +local menu = walk(root, prefix) +-- print(vim.inspect(menu)) +return menu and next(menu) and menu[1].items or nil From e93682caaf7d5ad1d23324ee409775ec672f76ec Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:07:19 +0200 Subject: [PATCH 06/16] init.lua: better handle errors in parsing items --- lua/menu/init.lua | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index 9869df8..df9517d 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -34,9 +34,29 @@ M.open = function(items, opts) } end - items = type(items) == "function" and items() or items - items = type(items) == "table" and items or require("menus." .. items) - items = type(items) == "function" and items() or items + local items_was = type(items) + if type(items) == "function" then + items = items() + end + if type(items) == "string" then + items = require("menus." .. items) + if type(items) == "function" then + items = items() + end + end + assert( + type(items) == "table", + "Items has to be a table." + .. " items_was=" + .. items_was + .. " type(items)=" + .. type(items) + .. " vim.inspect(items)=" + .. vim.inspect(items) + .. " vim.inspect(opts)=" + .. vim.inspect(opts) + .. ". Most probably provided menus configuration is invalid." + ) if not state.config then state.config = opts From 69e21f590f3e4b9ea57e018856c09010db9fc055 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:07:28 +0200 Subject: [PATCH 07/16] init.lua: improve config --- lua/menu/init.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index df9517d..c87be1b 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -9,15 +9,14 @@ local utils = require("menu.utils") ---@class MenuItem ---@field name string ----@field cmd? string|fun():nil +---@field cmd? string|fun():any ---@field items? MenuItem[]|fun():MenuItem[] ---@field rtxt? string +---@field hl? string ---@class MenuOpenOpts ---@field mouse? boolean ---@field nested? boolean ----@field item_gap? integer ----@field border? boolean ---@param items string|MenuItem[]|fun():MenuItem[] ---@param opts MenuOpenOpts @@ -65,7 +64,7 @@ M.open = function(items, opts) local config = state.config local buf = vim.api.nvim_create_buf(false, true) - state.bufs[buf] = { items = items, item_gap = opts.item_gap or 5 } + state.bufs[buf] = { items = items, item_gap = M.config.item_gap or 5 } table.insert(state.bufids, buf) local h = #items @@ -105,7 +104,7 @@ M.open = function(items, opts) { buf = buf, ns = ns, layout = layout }, }) - if config.border then + if M.config.border then vim.wo[win].winhl = "Normal:Normal,FloatBorder:LineNr" else vim.wo[win].winhl = "Normal:ExBlack2Bg,FloatBorder:ExBlack2Border" @@ -149,12 +148,16 @@ M.delete_old_menus = utils.delete_old_menus ---@field ft? {string: string|MenuItem|fun():MenuItem} ---@field default_menu? string|MenuItem ---@field default_mappings? boolean +---@field border? boolean +---@field item_gap? integer ---@type MenuConfig M.config = { ft = {}, default_menu = "default", default_mappings = false, + border = false, + item_gap = 5, } ---@param args MenuConfig From 1cc69c106edca8d03ff4e51f065891347d488ef4 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:07:40 +0200 Subject: [PATCH 08/16] default.lua: add lazy loaded which-key entry if which-key is available --- lua/menus/default.lua | 195 ++++++++++++++++++++++++------------------ 1 file changed, 111 insertions(+), 84 deletions(-) diff --git a/lua/menus/default.lua b/lua/menus/default.lua index c5cc1ca..232aa48 100644 --- a/lua/menus/default.lua +++ b/lua/menus/default.lua @@ -1,85 +1,112 @@ -return { - - { - name = "Format Buffer", - cmd = function() - local ok, conform = pcall(require, "conform") - - if ok then - conform.format { lsp_fallback = true } - else - vim.lsp.buf.format() - end - end, - rtxt = "fm", - }, - - { - name = "Code Actions", - cmd = vim.lsp.buf.code_action, - rtxt = "ca", - }, - - { name = "separator" }, - - { - name = " Lsp Actions", - hl = "Exblue", - items = "lsp", - }, - - { name = "separator" }, - - { - name = "Edit Config", - cmd = function() - vim.cmd "tabnew" - local conf = vim.fn.stdpath "config" - vim.cmd("tcd " .. conf .. " | e init.lua") - end, - rtxt = "ed", - }, - - { - name = "Copy Content", - cmd = "%y+", - rtxt = "", - }, - - { - name = "Delete Content", - cmd = "%d", - rtxt = "dc", - }, - - { name = "separator" }, - - { - name = " Open in terminal", - hl = "ExRed", - cmd = function() - local old_buf = require("menu.state").old_data.buf - local old_bufname = vim.api.nvim_buf_get_name(old_buf) - local old_buf_dir = vim.fn.fnamemodify(old_bufname, ":h") - - local cmd = "cd " .. old_buf_dir - - -- base46_cache var is an indicator of nvui user! - if vim.g.base46_cache then - require("nvchad.term").new { cmd = cmd, pos = "sp" } - else - vim.cmd "enew" - vim.fn.termopen { vim.o.shell, "-c", cmd .. " ; " .. vim.o.shell } - end - end, - }, - - { name = "separator" }, - - { - name = " Color Picker", - cmd = function() - require("minty.huefy").open() - end, - }, +local function insertafter(arr, after, elem) + local idx = 1 + for i, v in ipairs(arr) do + if v.name:find(after) then + idx = i + break + end + end + table.insert(arr, idx, elem) +end + +---@type MenuItem[] +local items = { + + { + name = "Format Buffer", + cmd = function() + local ok, conform = pcall(require, "conform") + + if ok then + conform.format({ lsp_fallback = true }) + else + vim.lsp.buf.format() + end + end, + rtxt = "fm", + }, + + { + name = "Code Actions", + cmd = vim.lsp.buf.code_action, + rtxt = "ca", + }, + + { name = "separator" }, + + { + name = " Lsp Actions", + hl = "Exblue", + items = function() + return require("menus.lsp") + end, + }, + + { name = "separator" }, + + { + name = "Edit Config", + cmd = function() + vim.cmd("tabnew") + local conf = vim.fn.stdpath("config") + vim.cmd("tcd " .. conf .. " | e init.lua") + end, + rtxt = "ed", + }, + + { + name = "Copy Content", + cmd = "%y+", + rtxt = "", + }, + + { + name = "Delete Content", + cmd = "%d", + rtxt = "dc", + }, + + { name = "separator" }, + + { + name = " Open in terminal", + hl = "ExRed", + cmd = function() + local old_buf = require("menu.state").old_data.buf + local old_bufname = vim.api.nvim_buf_get_name(old_buf) + local old_buf_dir = vim.fn.fnamemodify(old_bufname, ":h") + + local cmd = "cd " .. old_buf_dir + + -- base46_cache var is an indicator of nvui user! + if vim.g.base46_cache then + require("nvchad.term").new({ cmd = cmd, pos = "sp" }) + else + vim.cmd("enew") + vim.fn.termopen({ vim.o.shell, "-c", cmd .. " ; " .. vim.o.shell }) + end + end, + }, + + { name = "separator" }, + + { + name = " Color Picker", + cmd = function() + require("minty.huefy").open() + end, + }, } + +local ok = require("which-key") +if ok then + insertafter(items, "Lsp Actions", { + name = " Which-key", + hl = "Exblue", + items = function() + return require("menus.which-key") + end, + }) +end + +return items From cad4eaa68b1b52f2dad2e5740d1ccc29fcd24548 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:07:43 +0200 Subject: [PATCH 09/16] update README.md --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 37f808a..2929bf8 100644 --- a/README.md +++ b/README.md @@ -7,46 +7,107 @@ Menu ui for neovim ( supports nested menus ) https://github.com/user-attachments/assets/89d96170-e039-4d3d-9640-0fdc3358a833 -## Install +## Features + +- LSP Actions menu +- mapleader which-key menu +- neo-tree support +- nvimtree support + +## Installation + +Install the plugin with your package manager: + +[lazy.nvim](https://github.com/folke/lazy.nvim) ```lua -{ "nvzone/volt" , lazy = true }, -{ "nvzone/menu" , lazy = true }, +{ + "nvzone/menu", + dependencies = { "nvzone/volt" }, + opts = { + -- your configuration comes here + -- or leave it empty to use the default settings + }, +} +``` + +Default settings: + +```lua + opts = { + -- Overwrite filetype menu in handler(). If not provided, require("menus.ft." .. filetype) is tried to load the menu. + ft = {}, + -- The default menu to open in handler(), if filetype specific menu is not found. + default_menu = "default", + -- Should we install default mappings? Default mappings presented below. + default_mappings = false, + -- Should the menu have a border? + border = false, + item_gap = 5, + }, ``` ## Usage + +To open a menu, you can use: + ```lua -require("menu").open(options, opts) +require("menu").open(items, opts) ``` -- options is a table or string, if string then it will look at the table from menus* module of this repo -- opts : { mouse = true, border = false }" + +Items is detected to be: +- a function, in which case it is called and it can return a string or a table. +- a string, in which case `require("menus." .. items)` is called. This may result in a function, which is then called. +- the end result has to be a table of menu items. + +Opts has the following attributes: +- **mouse**: (`boolean`) When true, will create menu at cursor position. + +Menu item has the following attributes: +- **name**: (`string`) The name of the item. +- **cmd**: (`string|fun():any`) The command to execute when item is selected. +- **items**: (`MenuItem[]|fun():MenuItem[]`) Submenu items or a function that returns the submenu items (required for submenu) +- **rtxt**: (`string`) Text to show on the right of the item (optional) +- **hl**: (`string`) The hightlight of the item. + +The library provides a default handler: + +```lua +require("menu").handler(opts) +``` + +The handler automatically chooses which menu to open depending on the current filetype. + +Opts has the following attributes: +- **mouse**: (`boolean`) + - When menu is open and **mouse** is set to true, then menu will be closed and a new menu will be repened at new cursor location. + - When menu is open and **mouse** is set to false, menu will just be closed. ### For keyboard users + - Use `h` `l` to move between windows - Use `q` to close the window - Press the keybind defined for menu item or scroll to it and press enter, to execute it -### Examples +### Default mappings + +Keyboard users can run the mapping when inside the menu, mouse users can click. -- Keyboard users can run the mapping when inside the menu, mouse users can click. ```lua -- Keyboard users -vim.keymap.set("n", "", function() - require("menu").open("default") -end, {}) - --- mouse users + nvimtree users! -vim.keymap.set({ "n", "v" }, "", function() - require('menu.utils').delete_old_menus() - - vim.cmd.exec '"normal! \\"' +vim.keymap.set("n", "", function() require("menu").handler {mouse = false} end) +-- Mouse users +vim.keymap.set({ "n", "v" }, "", function() require("menu").handler {mouse = true} end) +``` - -- clicked buf - local buf = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) - local options = vim.bo[buf].ft == "NvimTree" and "nvimtree" or "default" +Same settings in lazy.nvim specification: - require("menu").open(options, { mouse = true }) -end, {}) +```lua + keys = { + { mode = "n", "", function() require("menu").handler({ mouse = false }) end }, + { mode = "n", "", function() require("menu").handler({ mouse = true }) end }, + { mode = "v", "", function() require("menu").handler({ mouse = true }) end }, + }, ``` Check example of [defaults menu](https://github.com/NvChad/menu/blob/main/lua/menus/default.lua) to see know syntax of options table. From e07c211e39d8eef03ea46cef3468ff5794b7cf00 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:12:19 +0200 Subject: [PATCH 10/16] fix README.md indentation --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2929bf8..a80f385 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,13 @@ Default settings: -- Overwrite filetype menu in handler(). If not provided, require("menus.ft." .. filetype) is tried to load the menu. ft = {}, -- The default menu to open in handler(), if filetype specific menu is not found. - default_menu = "default", - -- Should we install default mappings? Default mappings presented below. - default_mappings = false, - -- Should the menu have a border? - border = false, - item_gap = 5, - }, + default_menu = "default", + -- Should we install default mappings? Default mappings presented below. + default_mappings = false, + -- Should the menu have a border? + border = false, + item_gap = 5, + }, ``` ## Usage @@ -104,9 +104,9 @@ Same settings in lazy.nvim specification: ```lua keys = { - { mode = "n", "", function() require("menu").handler({ mouse = false }) end }, - { mode = "n", "", function() require("menu").handler({ mouse = true }) end }, - { mode = "v", "", function() require("menu").handler({ mouse = true }) end }, + { mode = "n", "", function() require("menu").handler({ mouse = false }) end }, + { mode = "n", "", function() require("menu").handler({ mouse = true }) end }, + { mode = "v", "", function() require("menu").handler({ mouse = true }) end }, }, ``` From 9844f9ee95bbf9494f2ba35bb4d722a7e599f7cd Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:14:01 +0200 Subject: [PATCH 11/16] add stylua.toml and apply formatting --- lua/menu/init.lua | 336 +++++++++++++++++++------------------- lua/menus/default.lua | 200 +++++++++++------------ lua/menus/ft/neo-tree.lua | 8 +- lua/menus/which-key.lua | 70 ++++---- stylua.toml | 3 + 5 files changed, 311 insertions(+), 306 deletions(-) create mode 100644 stylua.toml diff --git a/lua/menu/init.lua b/lua/menu/init.lua index c87be1b..d157ea9 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -1,11 +1,11 @@ local M = {} -local state = require("menu.state") -local layout = require("menu.layout") -local ns = vim.api.nvim_create_namespace("NvMenu") -local volt = require("volt") -local volt_events = require("volt.events") -local mappings = require("menu.mappings") -local utils = require("menu.utils") +local state = require "menu.state" +local layout = require "menu.layout" +local ns = vim.api.nvim_create_namespace "NvMenu" +local volt = require "volt" +local volt_events = require "volt.events" +local mappings = require "menu.mappings" +local utils = require "menu.utils" ---@class MenuItem ---@field name string @@ -21,125 +21,125 @@ local utils = require("menu.utils") ---@param items string|MenuItem[]|fun():MenuItem[] ---@param opts MenuOpenOpts M.open = function(items, opts) - opts = opts or {} - - local cur_buf = vim.api.nvim_get_current_buf() - - if vim.bo[cur_buf].ft ~= "NvMenu" then - state.old_data = { - buf = vim.api.nvim_get_current_buf(), - win = vim.api.nvim_get_current_win(), - cursor = vim.api.nvim_win_get_cursor(0), - } - end - - local items_was = type(items) - if type(items) == "function" then - items = items() - end - if type(items) == "string" then - items = require("menus." .. items) - if type(items) == "function" then - items = items() - end - end - assert( - type(items) == "table", - "Items has to be a table." - .. " items_was=" - .. items_was - .. " type(items)=" - .. type(items) - .. " vim.inspect(items)=" - .. vim.inspect(items) - .. " vim.inspect(opts)=" - .. vim.inspect(opts) - .. ". Most probably provided menus configuration is invalid." - ) - - if not state.config then - state.config = opts - end - - local config = state.config - - local buf = vim.api.nvim_create_buf(false, true) - state.bufs[buf] = { items = items, item_gap = M.config.item_gap or 5 } - table.insert(state.bufids, buf) - - local h = #items - local bufv = state.bufs[buf] - bufv.w = utils.get_width(items) - bufv.w = bufv.w + bufv.item_gap - - local win_opts = { - relative = config.mouse and "mouse" or "cursor", - width = bufv.w, - height = h, - row = 1, - col = 0, - border = "single", - style = "minimal", - zindex = 99 + #state.bufids, - } - - if opts.nested then - win_opts.relative = "win" - - if config.mouse then - local pos = vim.fn.getmousepos() - win_opts.win = pos.winid - win_opts.col = vim.api.nvim_win_get_width(pos.winid) + 2 - win_opts.row = pos.winrow - 2 - else - win_opts.win = vim.api.nvim_get_current_win() - win_opts.col = vim.api.nvim_win_get_width(win_opts.win) + 2 - win_opts.row = vim.api.nvim_win_get_cursor(win_opts.win)[1] - 1 - end - end - - local win = vim.api.nvim_open_win(buf, not config.mouse, win_opts) - - volt.gen_data({ - { buf = buf, ns = ns, layout = layout }, - }) - - if M.config.border then - vim.wo[win].winhl = "Normal:Normal,FloatBorder:LineNr" - else - vim.wo[win].winhl = "Normal:ExBlack2Bg,FloatBorder:ExBlack2Border" - end - - volt.run(buf, { h = h, w = bufv.w }) - vim.bo[buf].filetype = "NvMenu" - - volt_events.add(buf) - - local close_post = function() - state.bufs = {} - state.config = nil - - if vim.api.nvim_win_is_valid(state.old_data.win) then - vim.api.nvim_set_current_win(state.old_data.win) - vim.schedule(function() - local cursor_line = math.max(1, state.old_data.cursor[1]) - local cursor_col = math.max(0, state.old_data.cursor[2]) - - vim.api.nvim_win_set_cursor(state.old_data.win, { cursor_line, cursor_col }) - end) - end - - state.bufids = {} - end - - volt.mappings({ bufs = vim.tbl_keys(state.bufs), after_close = close_post }) - - if not config.mouse then - mappings.nav_win() - mappings.actions(items, buf) - else - mappings.auto_close() - end + opts = opts or {} + + local cur_buf = vim.api.nvim_get_current_buf() + + if vim.bo[cur_buf].ft ~= "NvMenu" then + state.old_data = { + buf = vim.api.nvim_get_current_buf(), + win = vim.api.nvim_get_current_win(), + cursor = vim.api.nvim_win_get_cursor(0), + } + end + + local items_was = type(items) + if type(items) == "function" then + items = items() + end + if type(items) == "string" then + items = require("menus." .. items) + if type(items) == "function" then + items = items() + end + end + assert( + type(items) == "table", + "Items has to be a table." + .. " items_was=" + .. items_was + .. " type(items)=" + .. type(items) + .. " vim.inspect(items)=" + .. vim.inspect(items) + .. " vim.inspect(opts)=" + .. vim.inspect(opts) + .. ". Most probably provided menus configuration is invalid." + ) + + if not state.config then + state.config = opts + end + + local config = state.config + + local buf = vim.api.nvim_create_buf(false, true) + state.bufs[buf] = { items = items, item_gap = M.config.item_gap or 5 } + table.insert(state.bufids, buf) + + local h = #items + local bufv = state.bufs[buf] + bufv.w = utils.get_width(items) + bufv.w = bufv.w + bufv.item_gap + + local win_opts = { + relative = config.mouse and "mouse" or "cursor", + width = bufv.w, + height = h, + row = 1, + col = 0, + border = "single", + style = "minimal", + zindex = 99 + #state.bufids, + } + + if opts.nested then + win_opts.relative = "win" + + if config.mouse then + local pos = vim.fn.getmousepos() + win_opts.win = pos.winid + win_opts.col = vim.api.nvim_win_get_width(pos.winid) + 2 + win_opts.row = pos.winrow - 2 + else + win_opts.win = vim.api.nvim_get_current_win() + win_opts.col = vim.api.nvim_win_get_width(win_opts.win) + 2 + win_opts.row = vim.api.nvim_win_get_cursor(win_opts.win)[1] - 1 + end + end + + local win = vim.api.nvim_open_win(buf, not config.mouse, win_opts) + + volt.gen_data { + { buf = buf, ns = ns, layout = layout }, + } + + if M.config.border then + vim.wo[win].winhl = "Normal:Normal,FloatBorder:LineNr" + else + vim.wo[win].winhl = "Normal:ExBlack2Bg,FloatBorder:ExBlack2Border" + end + + volt.run(buf, { h = h, w = bufv.w }) + vim.bo[buf].filetype = "NvMenu" + + volt_events.add(buf) + + local close_post = function() + state.bufs = {} + state.config = nil + + if vim.api.nvim_win_is_valid(state.old_data.win) then + vim.api.nvim_set_current_win(state.old_data.win) + vim.schedule(function() + local cursor_line = math.max(1, state.old_data.cursor[1]) + local cursor_col = math.max(0, state.old_data.cursor[2]) + + vim.api.nvim_win_set_cursor(state.old_data.win, { cursor_line, cursor_col }) + end) + end + + state.bufids = {} + end + + volt.mappings { bufs = vim.tbl_keys(state.bufs), after_close = close_post } + + if not config.mouse then + mappings.nav_win() + mappings.actions(items, buf) + else + mappings.auto_close() + end end M.delete_old_menus = utils.delete_old_menus @@ -153,56 +153,56 @@ M.delete_old_menus = utils.delete_old_menus ---@type MenuConfig M.config = { - ft = {}, - default_menu = "default", - default_mappings = false, - border = false, - item_gap = 5, + ft = {}, + default_menu = "default", + default_mappings = false, + border = false, + item_gap = 5, } ---@param args MenuConfig M.setup = function(args) - M.config = vim.tbl_deep_extend("force", M.config, args or {}) - if M.config.default_mappings then - vim.keymap.set("n", "", function() - M.handler({ mouse = false }) - end) - vim.keymap.set({ "n", "v" }, "", function() - M.handler({ mouse = true }) - end) - end + M.config = vim.tbl_deep_extend("force", M.config, args or {}) + if M.config.default_mappings then + vim.keymap.set("n", "", function() + M.handler { mouse = false } + end) + vim.keymap.set({ "n", "v" }, "", function() + M.handler { mouse = true } + end) + end end ---@param opts MenuOpenOpts M.handler = function(opts) - opts = opts or {} - local window = 0 - if opts.mouse then - -- On second mouse click remove current manu and reopen it. - require("menu.utils").delete_old_menus() - vim.cmd.exec('"normal! \\"') - window = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) - else - if #require("menu.state").bufids > 0 then - -- if a menu is already open, close it. - require("menu.utils").delete_old_menus() - return - end - end - local ft = vim.bo[vim.api.nvim_win_get_buf(window)].ft - -- First try user filetype overwrites. - local items = M.config.ft[ft] - if not items then - -- Then try filetype specific menus. - local ok, mod = pcall(require, "menus.ft." .. ft) - if ok then - items = mod - else - -- Fallback to defaults. - items = M.config.default_menu or "default" - end - end - M.open(items, opts) + opts = opts or {} + local window = 0 + if opts.mouse then + -- On second mouse click remove current manu and reopen it. + require("menu.utils").delete_old_menus() + vim.cmd.exec '"normal! \\"' + window = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) + else + if #require("menu.state").bufids > 0 then + -- if a menu is already open, close it. + require("menu.utils").delete_old_menus() + return + end + end + local ft = vim.bo[vim.api.nvim_win_get_buf(window)].ft + -- First try user filetype overwrites. + local items = M.config.ft[ft] + if not items then + -- Then try filetype specific menus. + local ok, mod = pcall(require, "menus.ft." .. ft) + if ok then + items = mod + else + -- Fallback to defaults. + items = M.config.default_menu or "default" + end + end + M.open(items, opts) end return M diff --git a/lua/menus/default.lua b/lua/menus/default.lua index 232aa48..2621b0d 100644 --- a/lua/menus/default.lua +++ b/lua/menus/default.lua @@ -1,112 +1,112 @@ local function insertafter(arr, after, elem) - local idx = 1 - for i, v in ipairs(arr) do - if v.name:find(after) then - idx = i - break - end - end - table.insert(arr, idx, elem) + local idx = 1 + for i, v in ipairs(arr) do + if v.name:find(after) then + idx = i + break + end + end + table.insert(arr, idx, elem) end ---@type MenuItem[] local items = { - { - name = "Format Buffer", - cmd = function() - local ok, conform = pcall(require, "conform") - - if ok then - conform.format({ lsp_fallback = true }) - else - vim.lsp.buf.format() - end - end, - rtxt = "fm", - }, - - { - name = "Code Actions", - cmd = vim.lsp.buf.code_action, - rtxt = "ca", - }, - - { name = "separator" }, - - { - name = " Lsp Actions", - hl = "Exblue", - items = function() - return require("menus.lsp") - end, - }, - - { name = "separator" }, - - { - name = "Edit Config", - cmd = function() - vim.cmd("tabnew") - local conf = vim.fn.stdpath("config") - vim.cmd("tcd " .. conf .. " | e init.lua") - end, - rtxt = "ed", - }, - - { - name = "Copy Content", - cmd = "%y+", - rtxt = "", - }, - - { - name = "Delete Content", - cmd = "%d", - rtxt = "dc", - }, - - { name = "separator" }, - - { - name = " Open in terminal", - hl = "ExRed", - cmd = function() - local old_buf = require("menu.state").old_data.buf - local old_bufname = vim.api.nvim_buf_get_name(old_buf) - local old_buf_dir = vim.fn.fnamemodify(old_bufname, ":h") - - local cmd = "cd " .. old_buf_dir - - -- base46_cache var is an indicator of nvui user! - if vim.g.base46_cache then - require("nvchad.term").new({ cmd = cmd, pos = "sp" }) - else - vim.cmd("enew") - vim.fn.termopen({ vim.o.shell, "-c", cmd .. " ; " .. vim.o.shell }) - end - end, - }, - - { name = "separator" }, - - { - name = " Color Picker", - cmd = function() - require("minty.huefy").open() - end, - }, + { + name = "Format Buffer", + cmd = function() + local ok, conform = pcall(require, "conform") + + if ok then + conform.format { lsp_fallback = true } + else + vim.lsp.buf.format() + end + end, + rtxt = "fm", + }, + + { + name = "Code Actions", + cmd = vim.lsp.buf.code_action, + rtxt = "ca", + }, + + { name = "separator" }, + + { + name = " Lsp Actions", + hl = "Exblue", + items = function() + return require "menus.lsp" + end, + }, + + { name = "separator" }, + + { + name = "Edit Config", + cmd = function() + vim.cmd "tabnew" + local conf = vim.fn.stdpath "config" + vim.cmd("tcd " .. conf .. " | e init.lua") + end, + rtxt = "ed", + }, + + { + name = "Copy Content", + cmd = "%y+", + rtxt = "", + }, + + { + name = "Delete Content", + cmd = "%d", + rtxt = "dc", + }, + + { name = "separator" }, + + { + name = " Open in terminal", + hl = "ExRed", + cmd = function() + local old_buf = require("menu.state").old_data.buf + local old_bufname = vim.api.nvim_buf_get_name(old_buf) + local old_buf_dir = vim.fn.fnamemodify(old_bufname, ":h") + + local cmd = "cd " .. old_buf_dir + + -- base46_cache var is an indicator of nvui user! + if vim.g.base46_cache then + require("nvchad.term").new { cmd = cmd, pos = "sp" } + else + vim.cmd "enew" + vim.fn.termopen { vim.o.shell, "-c", cmd .. " ; " .. vim.o.shell } + end + end, + }, + + { name = "separator" }, + + { + name = " Color Picker", + cmd = function() + require("minty.huefy").open() + end, + }, } -local ok = require("which-key") +local ok = require "which-key" if ok then - insertafter(items, "Lsp Actions", { - name = " Which-key", - hl = "Exblue", - items = function() - return require("menus.which-key") - end, - }) + insertafter(items, "Lsp Actions", { + name = " Which-key", + hl = "Exblue", + items = function() + return require "menus.which-key" + end, + }) end return items diff --git a/lua/menus/ft/neo-tree.lua b/lua/menus/ft/neo-tree.lua index e13dd64..cf02d05 100644 --- a/lua/menus/ft/neo-tree.lua +++ b/lua/menus/ft/neo-tree.lua @@ -22,7 +22,9 @@ end local function copy_path(how) return function() local node = get_state().tree:get_node() - if node.type == "message" then return end + if node.type == "message" then + return + end vim.fn.setreg('"', vim.fn.fnamemodify(node.path, how)) vim.fn.setreg("+", vim.fn.fnamemodify(node.path, how)) end @@ -32,7 +34,9 @@ end local function open_in_terminal() return function() local node = get_state().tree:get_node() - if node.type == "message" then return end + if node.type == "message" then + return + end local path = node.path local node_type = vim.uv.fs_stat(path).type local dir = node_type == "directory" and path or vim.fn.fnamemodify(path, ":h") diff --git a/lua/menus/which-key.lua b/lua/menus/which-key.lua index 4214b1c..e2e6564 100644 --- a/lua/menus/which-key.lua +++ b/lua/menus/which-key.lua @@ -1,8 +1,8 @@ ---@param self string ---@param start string local function string_startswith(self, start) - ---@diagnostic disable-next-line: param-type-mismatch - return self:sub(1, #start) == start + ---@diagnostic disable-next-line: param-type-mismatch + return self:sub(1, #start) == start end local walk @@ -11,46 +11,44 @@ local walk ---@param prefix string ---@return MenuItem[] local node_to_item = function(node, prefix) - -- Create the name. - local name = node.mapping and node.mapping.desc - or node.keymap and (node.keymap.desc or node.keymap.rhs) - or node.keys - -- Create element to add. Elements with children have items. - if next(node._children) then - -- If item has children, descend to them. - return { - name = name .. " " .. node.path[#node.path], - items = walk(node, prefix), - rtxt = node.path[#node.path] - } - else - -- If keymap is a callback, we can just straight call it, which is more reliable and easier. - -- If key is , it has to start with 1 for normal! command to pick it up properly. - local cmd = node.keymap and node.keymap.callback or "normal! " .. node.keys:gsub("", " "):gsub("^ ", "1 ") - -- If item has no children, execute a normal command as part of it. - return { name = name, cmd = cmd, rtxt = node.path[#node.path] } - end + -- Create the name. + local name = node.mapping and node.mapping.desc or node.keymap and (node.keymap.desc or node.keymap.rhs) or node.keys + -- Create element to add. Elements with children have items. + if next(node._children) then + -- If item has children, descend to them. + return { + name = name .. " " .. node.path[#node.path], + items = walk(node, prefix), + rtxt = node.path[#node.path], + } + else + -- If keymap is a callback, we can just straight call it, which is more reliable and easier. + -- If key is , it has to start with 1 for normal! command to pick it up properly. + local cmd = node.keymap and node.keymap.callback or "normal! " .. node.keys:gsub("", " "):gsub("^ ", "1 ") + -- If item has no children, execute a normal command as part of it. + return { name = name, cmd = cmd, rtxt = node.path[#node.path] } + end end ---@param node wk.Node ---@param prefix string ---@return MenuItem[]? walk = function(node, prefix) - ---@type MenuItem[] - local items = nil - local children = node._children - if children then - items = {} - for _, child in pairs(children) do - if string_startswith(child.keys, prefix) then - table.insert(items, node_to_item(child, prefix)) - end - end - table.sort(items, function(a, b) - return a.name < b.name - end) - end - return items + ---@type MenuItem[] + local items = nil + local children = node._children + if children then + items = {} + for _, child in pairs(children) do + if string_startswith(child.keys, prefix) then + table.insert(items, node_to_item(child, prefix)) + end + end + table.sort(items, function(a, b) + return a.name < b.name + end) + end + return items end local root = require("which-key.buf").get({ mode = "n" }).tree.root diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..364ef9c --- /dev/null +++ b/stylua.toml @@ -0,0 +1,3 @@ +indent_type = "Spaces" +indent_width = 2 +no_call_parentheses = true From 4e660ed506b8604fe1944745d0225db7b709106f Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:46:04 +0200 Subject: [PATCH 12/16] fix bug - rebase me --- lua/menu/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index d157ea9..dd98a02 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -181,7 +181,7 @@ M.handler = function(opts) -- On second mouse click remove current manu and reopen it. require("menu.utils").delete_old_menus() vim.cmd.exec '"normal! \\"' - window = vim.api.nvim_win_get_buf(vim.fn.getmousepos().winid) + window = vim.fn.getmousepos().winid else if #require("menu.state").bufids > 0 then -- if a menu is already open, close it. From 1182b221fb156cac82ab767362eb700e6113e55f Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 Apr 2025 23:47:03 +0200 Subject: [PATCH 13/16] init.lua: add nested_col --- lua/menu/init.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index dd98a02..6376c16 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -89,11 +89,11 @@ M.open = function(items, opts) if config.mouse then local pos = vim.fn.getmousepos() win_opts.win = pos.winid - win_opts.col = vim.api.nvim_win_get_width(pos.winid) + 2 + win_opts.col = vim.api.nvim_win_get_width(pos.winid) + M.config.nested_col win_opts.row = pos.winrow - 2 else win_opts.win = vim.api.nvim_get_current_win() - win_opts.col = vim.api.nvim_win_get_width(win_opts.win) + 2 + win_opts.col = vim.api.nvim_win_get_width(win_opts.win) + M.config.nested_col win_opts.row = vim.api.nvim_win_get_cursor(win_opts.win)[1] - 1 end end @@ -150,14 +150,16 @@ M.delete_old_menus = utils.delete_old_menus ---@field default_mappings? boolean ---@field border? boolean ---@field item_gap? integer +---@field nested_col? integer ----@type MenuConfig +---@class MenuConfig M.config = { ft = {}, default_menu = "default", default_mappings = false, border = false, item_gap = 5, + nested_col = 2, } ---@param args MenuConfig From 4e71bdbb43812881cd23649e883a90204f2a949a Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 Apr 2025 08:40:57 +0200 Subject: [PATCH 14/16] update which-key --- lua/menus/which-key.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/menus/which-key.lua b/lua/menus/which-key.lua index e2e6564..a082768 100644 --- a/lua/menus/which-key.lua +++ b/lua/menus/which-key.lua @@ -9,7 +9,7 @@ local walk ---@param node wk.Node ---@param prefix string ----@return MenuItem[] +---@return MenuItem[]? local node_to_item = function(node, prefix) -- Create the name. local name = node.mapping and node.mapping.desc or node.keymap and (node.keymap.desc or node.keymap.rhs) or node.keys @@ -22,10 +22,13 @@ local node_to_item = function(node, prefix) rtxt = node.path[#node.path], } else - -- If keymap is a callback, we can just straight call it, which is more reliable and easier. - -- If key is , it has to start with 1 for normal! command to pick it up properly. - local cmd = node.keymap and node.keymap.callback or "normal! " .. node.keys:gsub("", " "):gsub("^ ", "1 ") -- If item has no children, execute a normal command as part of it. + local feed = vim.api.nvim_replace_termcodes(node.keys, true, true, true) + local cmd = node.action + or node.keymap and node.keymap.callback + or function() + vim.api.nvim_feedkeys(feed, "mit", false) + end return { name = name, cmd = cmd, rtxt = node.path[#node.path] } end end @@ -40,7 +43,7 @@ walk = function(node, prefix) if children then items = {} for _, child in pairs(children) do - if string_startswith(child.keys, prefix) then + if child and child.keys and string_startswith(child.keys, prefix) then table.insert(items, node_to_item(child, prefix)) end end From 6983883d36b0321be38db41b35f0a8f519bbbf17 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 Apr 2025 09:17:06 +0200 Subject: [PATCH 15/16] fix no height with zero items --- lua/menu/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index 6376c16..a4090bd 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -67,7 +67,7 @@ M.open = function(items, opts) state.bufs[buf] = { items = items, item_gap = M.config.item_gap or 5 } table.insert(state.bufids, buf) - local h = #items + local h = #items or 1 local bufv = state.bufs[buf] bufv.w = utils.get_width(items) bufv.w = bufv.w + bufv.item_gap From 638b9090aa35f98eb37a1ff78734b6fd945ef31c Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 Apr 2025 09:17:35 +0200 Subject: [PATCH 16/16] fix which-key menu, add which-key menu from leader and make it more lazy loaded --- lua/menu/init.lua | 8 +++++--- lua/menus/default.lua | 12 ++++++++++-- lua/menus/which-key.lua | 27 +++++++++++++++++---------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lua/menu/init.lua b/lua/menu/init.lua index a4090bd..de2a8e6 100644 --- a/lua/menu/init.lua +++ b/lua/menu/init.lua @@ -33,7 +33,7 @@ M.open = function(items, opts) } end - local items_was = type(items) + local items_was = items if type(items) == "function" then items = items() end @@ -46,8 +46,10 @@ M.open = function(items, opts) assert( type(items) == "table", "Items has to be a table." - .. " items_was=" - .. items_was + .. " type(items_was)=" + .. type(items_was) + .. " debug.getinfo(items_was)=" + .. vim.inspect(type(items_was) == "function" and debug.getinfo(items_was)) .. " type(items)=" .. type(items) .. " vim.inspect(items)=" diff --git a/lua/menus/default.lua b/lua/menus/default.lua index 2621b0d..c4a85c0 100644 --- a/lua/menus/default.lua +++ b/lua/menus/default.lua @@ -101,10 +101,18 @@ local items = { local ok = require "which-key" if ok then insertafter(items, "Lsp Actions", { - name = " Which-key", + name = " Which-key from leader", hl = "Exblue", items = function() - return require "menus.which-key" + local subitems = require "menus.which-key"(vim.g.mapleader)[1].items + return type(subitems) == "function" and subitems() or subitems + end, + }) + insertafter(items, "Lsp Actions", { + name = " Which-key all keys", + hl = "Exblue", + items = function() + return require "menus.which-key"() end, }) end diff --git a/lua/menus/which-key.lua b/lua/menus/which-key.lua index a082768..e0f6d45 100644 --- a/lua/menus/which-key.lua +++ b/lua/menus/which-key.lua @@ -8,7 +8,7 @@ end local walk ---@param node wk.Node ----@param prefix string +---@param prefix string? ---@return MenuItem[]? local node_to_item = function(node, prefix) -- Create the name. @@ -18,7 +18,10 @@ local node_to_item = function(node, prefix) -- If item has children, descend to them. return { name = name .. " " .. node.path[#node.path], - items = walk(node, prefix), + items = function() + return walk(node, prefix) or {} + end, + hl = "Exblue", rtxt = node.path[#node.path], } else @@ -34,7 +37,7 @@ local node_to_item = function(node, prefix) end ---@param node wk.Node ----@param prefix string +---@param prefix string? ---@return MenuItem[]? walk = function(node, prefix) ---@type MenuItem[] @@ -43,7 +46,7 @@ walk = function(node, prefix) if children then items = {} for _, child in pairs(children) do - if child and child.keys and string_startswith(child.keys, prefix) then + if child and child.keys and (not prefix or prefix == "" or string_startswith(child.keys, prefix)) then table.insert(items, node_to_item(child, prefix)) end end @@ -54,9 +57,13 @@ walk = function(node, prefix) return items end -local root = require("which-key.buf").get({ mode = "n" }).tree.root --- print(vim.inspect(root)) -local prefix = vim.g.mapleader:gsub(" ", "") -local menu = walk(root, prefix) --- print(vim.inspect(menu)) -return menu and next(menu) and menu[1].items or nil +---@param prefix string? +---@param mode string? +return function(prefix, mode) + local root = require("which-key.buf").get({ mode = mode or "n" }).tree.root + -- print(vim.inspect(root)) + prefix = prefix and vim.g.mapleader:gsub(prefix, "") + local items = walk(root, prefix) + -- print(vim.inspect(menu)) + return items and next(items) and items or nil +end