feat: Proper floating window for keymap help and OutlineStatus
No more obnoxious '}' on the cmdline when pressing `?`! scope: - More type hints - Added class Float for creating floating windows with size that fit the content and position centered on the screen - show_help action for outline window (key `?`) now uses a floating window - :OutlineStatus now provides better information, and shows content in a floating window. future: - Floating window option configuration
This commit is contained in:
@@ -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()
|
||||
|
||||
129
lua/outline/docs.lua
Normal file
129
lua/outline/docs.lua
Normal file
@@ -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 <Esc> 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', '<Esc>' }, 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 <Esc> 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', '<Esc>' }, function()
|
||||
f:close()
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
89
lua/outline/float.lua
Normal file
89
lua/outline/float.lua
Normal file
@@ -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
|
||||
@@ -72,24 +72,17 @@ 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, {
|
||||
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
|
||||
)
|
||||
M._highlight_current_item(code_win, cfg.o.outline_items.auto_set_cursor)
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user