diff --git a/lua/outline/config.lua b/lua/outline/config.lua index 1bedce8..b820ad7 100644 --- a/lua/outline/config.lua +++ b/lua/outline/config.lua @@ -261,11 +261,6 @@ function M.get_providers() return M.providers end -function M.show_help() - print('Current keymaps:') - print(vim.inspect(M.o.keymaps)) -end - ---Check for inconsistent or mutually exclusive opts. -- Does not alter the opts. Might show messages. function M.check_config() diff --git a/lua/outline/docs.lua b/lua/outline/docs.lua new file mode 100644 index 0000000..a93d5d5 --- /dev/null +++ b/lua/outline/docs.lua @@ -0,0 +1,129 @@ +local Float = require('outline.float') +local cfg = require('outline.config') +local providers = require('outline.providers') +local utils = require('outline.utils') + +local M = {} + +function M.show_help() + local keyhint = 'Press q or to close this window.' + local title = 'Current keymaps:' + local lines = { keyhint, '', title, '' } + ---@type outline.HL[] + local hl = { { line = 0, from = 0, to = #keyhint, name = 'Comment' } } + local left = {} + local right = {} + local max_left_width = 0 + local indent = ' ' + local key_hl = 'Special' + + for action, keys in pairs(cfg.o.keymaps) do + if type(keys) == 'string' then + table.insert(left, keys) + table.insert(hl, { + line = #left + 3, + from = #indent, + to = #keys + #indent, + name = key_hl, + }) + else + local i = #indent + table.insert(left, table.concat(keys, ' / ')) + for _, key in ipairs(keys) do + table.insert(hl, { + line = #left + 3, + from = i, + to = #key + i, + name = key_hl, + }) + i = i + #key + 3 + end + end + if #left[#left] > max_left_width then + max_left_width = #left[#left] + end + table.insert(right, action) + end + + for i, l in ipairs(left) do + local pad = string.rep(' ', max_left_width - #l + 2) + table.insert(lines, indent .. l .. pad .. right[i]) + end + + local f = Float:new() + f:open(lines, hl, 'Outline Help', 1) + + utils.nmap(f.bufnr, { 'q', '' }, function() + f:close() + end) +end + +---Display outline window status in a floating window +---@param ctx outline.StatusContext +function M.show_status(ctx) + local keyhint = 'Press q or to close this window.' + local lines = { keyhint, '' } + ---@type outline.HL[] + local hl = { { line = 0, from = 0, to = #keyhint, name = 'Comment' } } + local p = ctx.provider + local priority = cfg.o.providers.priority + local pref + + if utils.table_has_content(priority) then + pref = 'Configured providers are: ' + table.insert(lines, pref .. table.concat(priority, ', ') .. '.') + local i = #pref + for _, name in ipairs(priority) do + table.insert(hl, { line = #lines - 1, from = i, to = i + #name, name = 'Special' }) + i = i + #name + 2 + end + else + pref = 'config ' + local content = 'providers.priority' + table.insert(lines, pref .. content .. ' is an empty list!') + table.insert(hl, { line = #lines - 1, from = #pref, to = #pref + #content, name = 'ErrorMsg' }) + end + + table.insert(lines, '') + + if p ~= nil then + pref = 'Current provider: ' + table.insert(lines, pref .. p.name) + table.insert(hl, { line = #lines - 1, from = #pref, to = -1, name = 'Special' }) + if p.get_status then + table.insert(lines, 'Provider info:') + table.insert(lines, '') + local l = p.get_status() + local indent = ' ' + for _, line in ipairs(vim.split(l, '\n', { plain = true, trimempty = false })) do + table.insert(lines, indent .. line) + end + end + + table.insert(lines, '') + + table.insert( + lines, + ('Outline window is %s.'):format((ctx.outline_open and 'open') or 'not open') + ) + + table.insert(lines, '') + + if ctx.code_win_active then + table.insert(lines, 'Code window is active.') + else + table.insert(lines, 'Code window is not active!') + table.insert(lines, 'Try closing and reopening the outline.') + end + else + table.insert(lines, 'No providers.') + end + + local f = Float:new() + f:open(lines, hl, 'Outline Status', 1) + utils.nmap(f.bufnr, { 'q', '' }, function() + f:close() + end) +end + +return M diff --git a/lua/outline/float.lua b/lua/outline/float.lua new file mode 100644 index 0000000..06b7770 --- /dev/null +++ b/lua/outline/float.lua @@ -0,0 +1,89 @@ +-- local cfg = require('outline.config') + +local Float = {} + +---@class outline.Float +---@field bufnr integer +---@field winnr integer +---@field ns integer + +function Float:new() + return setmetatable({ bufnr = nil, winnr = nil, ns = nil }, { __index = Float }) +end + +---@param lines string[] +---@param hl outline.HL[] +---@param title string +---@param indent integer? +function Float:open(lines, hl, title, indent) + indent = indent or 0 + + self.bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(self.bufnr, 'bufhidden', 'delete') + + local maxwidth = 0 + for _, l in ipairs(lines) do + if #l > maxwidth then + maxwidth = #l + end + end + + local ui = vim.api.nvim_list_uis()[1] + local nvim_height, nvim_width = ui.height, ui.width + + local padding_w = 3 + + local height, width = math.min(nvim_height, #lines + 1), maxwidth + 2 * padding_w + local row = math.floor((nvim_height - height) / 2) + local col = math.floor((nvim_width - width) / 2) + + self.winnr = vim.api.nvim_open_win(self.bufnr, true, { + relative = 'editor', + width = width, + height = height, + row = row, + col = col, + border = 'rounded', + style = 'minimal', + title = title, + title_pos = 'center', + }) + + if indent > 0 then + local pad = string.rep(' ', indent) + for i = 1, #lines do + lines[i] = pad .. lines[i] + end + end + + vim.api.nvim_win_set_option(self.winnr, 'winfixwidth', true) + vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, lines) + vim.api.nvim_buf_set_option(self.bufnr, 'modifiable', false) + vim.api.nvim_buf_set_option(self.bufnr, 'ft', 'OutlineHelp') + + if hl then + self.ns = vim.api.nvim_create_namespace('OutlineHelp') + for _, h in ipairs(hl) do + vim.api.nvim_buf_add_highlight( + self.bufnr, + self.ns, + h.name, + h.line, + h.from + indent, + (h.to ~= -1 and h.to + indent) or -1 + ) + end + vim.api.nvim_win_set_hl_ns(self.winnr, self.ns) + end +end + +function Float:close() + if self.winnr then + vim.api.nvim_buf_clear_namespace(self.bufnr, self.ns, 0, -1) + vim.api.nvim_win_close(self.winnr, true) + self.winnr = nil + self.bufnr = nil + end +end + +return Float diff --git a/lua/outline/init.lua b/lua/outline/init.lua index 3f39bda..0ff26af 100644 --- a/lua/outline/init.lua +++ b/lua/outline/init.lua @@ -72,26 +72,19 @@ end ---@param code_buf integer Must be valid local function setup_attached_buffer_autocmd(code_win, code_buf) local events = cfg.o.outline_items.auto_update_events - if - cfg.o.outline_items.highlight_hovered_item - or cfg.o.symbol_folding.auto_unfold_hover - then + if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then if M.state.autocmds[code_win] then vim.api.nvim_del_autocmd(M.state.autocmds[code_win]) M.state.autocmds[code_win] = nil end if utils.str_or_nonempty_table(events.follow) then - M.state.autocmds[code_win] = - vim.api.nvim_create_autocmd(events.follow, { - buffer = code_buf, - callback = function() - M._highlight_current_item( - code_win, - cfg.o.outline_items.auto_set_cursor - ) - end, - }) + M.state.autocmds[code_win] = vim.api.nvim_create_autocmd(events.follow, { + buffer = code_buf, + callback = function() + M._highlight_current_item(code_win, cfg.o.outline_items.auto_set_cursor) + end, + }) end end end @@ -398,7 +391,7 @@ local function setup_keymaps(bufnr) -- code actions map(cfg.o.keymaps.code_actions, require('outline.code_action').show_code_actions) -- show help - map(cfg.o.keymaps.show_help, require('outline.config').show_help) + map(cfg.o.keymaps.show_help, require('outline.docs').show_help) -- close outline map(cfg.o.keymaps.close, function() M.view:close() @@ -611,37 +604,6 @@ function M.is_open() return M.view:is_open() end ----Display outline window status in the message area. -function M.show_status() - -- TODO: Use a floating window instead - local p = _G._outline_current_provider - if not M.is_active() then - p = providers.find_provider() - end - - if p ~= nil then - print('Current provider: ' .. p.name) - if p.get_status then - print(p.get_status()) - print() - end - - if M.view:is_open() then - print('Outline window is open.') - else - print('Outline window is not open.') - end - - if require('outline.preview').has_code_win() then - print('Code window is active.') - else - print('Code window is either closed or invalid. Please close and reopen the outline window.') - end - else - print('No providers') - end -end - function M.is_active() local winid = vim.fn.win_getid() if M.view:is_open() and winid == M.view.winnr then @@ -659,6 +621,29 @@ function M.has_provider() return providers.has_provider() end +function M.show_status() + ---@type outline.StatusContext + local ctx = {} + local p = _G._outline_current_provider + if not M.is_active() then + p = providers.find_provider() + end + + if p ~= nil then + ctx.provider = p + ctx.outline_open = false + if M.view and M.view:is_open() then + ctx.outline_open = true + end + ctx.code_win_active = false + if require('outline.preview').has_code_win() then + ctx.code_win_active = true + end + end + + return require('outline.docs').show_status(ctx) +end + local function setup_commands() local cmd = function(n, c, o) vim.api.nvim_create_user_command('Outline' .. n, c, o) diff --git a/lua/outline/providers/init.lua b/lua/outline/providers/init.lua index c01b92e..966da92 100644 --- a/lua/outline/providers/init.lua +++ b/lua/outline/providers/init.lua @@ -3,8 +3,6 @@ local cfg = require('outline.config') local M = {} local import_prefix = 'outline/providers/' -_G._outline_current_provider = nil - function M.find_provider() if not M.providers then M.providers = vim.tbl_map(function(p) diff --git a/lua/outline/types/outline.lua b/lua/outline/types/outline.lua index f521881..4d56cb9 100644 --- a/lua/outline/types/outline.lua +++ b/lua/outline/types/outline.lua @@ -60,6 +60,28 @@ ---@field hovered boolean ---@field folded boolean +-- PROVIDER + +---@class outline.Provider +---@field should_use_provider fun(bufnr:integer):boolean +---@field hover_info fun(bufnr:integer, params:table, on_info:function) +---@field request_symbols fun(on_symbols:function, opts:table) +---@field name string +---@field get_status? fun():string + +-- HELP + +---@class outline.HL +---@field line integer +---@field from integer +---@field to integer +---@field name string + +---@class outline.StatusContext +---@field provider outline.Provider? +---@field outline_open boolean? +---@field code_win_active boolean? + -- API ---@class outline.OutlineOpts diff --git a/lua/outline/view.lua b/lua/outline/view.lua index 4093e0a..febb57c 100644 --- a/lua/outline/view.lua +++ b/lua/outline/view.lua @@ -2,6 +2,10 @@ local cfg = require('outline.config') local View = {} +---@class View +---@field bufnr integer +---@field winnr integer + function View:new() return setmetatable({ bufnr = nil, winnr = nil }, { __index = View }) end