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
|
return M.providers
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.show_help()
|
|
||||||
print('Current keymaps:')
|
|
||||||
print(vim.inspect(M.o.keymaps))
|
|
||||||
end
|
|
||||||
|
|
||||||
---Check for inconsistent or mutually exclusive opts.
|
---Check for inconsistent or mutually exclusive opts.
|
||||||
-- Does not alter the opts. Might show messages.
|
-- Does not alter the opts. Might show messages.
|
||||||
function M.check_config()
|
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
|
---@param code_buf integer Must be valid
|
||||||
local function setup_attached_buffer_autocmd(code_win, code_buf)
|
local function setup_attached_buffer_autocmd(code_win, code_buf)
|
||||||
local events = cfg.o.outline_items.auto_update_events
|
local events = cfg.o.outline_items.auto_update_events
|
||||||
if
|
if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then
|
||||||
cfg.o.outline_items.highlight_hovered_item
|
|
||||||
or cfg.o.symbol_folding.auto_unfold_hover
|
|
||||||
then
|
|
||||||
if M.state.autocmds[code_win] then
|
if M.state.autocmds[code_win] then
|
||||||
vim.api.nvim_del_autocmd(M.state.autocmds[code_win])
|
vim.api.nvim_del_autocmd(M.state.autocmds[code_win])
|
||||||
M.state.autocmds[code_win] = nil
|
M.state.autocmds[code_win] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if utils.str_or_nonempty_table(events.follow) then
|
if utils.str_or_nonempty_table(events.follow) then
|
||||||
M.state.autocmds[code_win] =
|
M.state.autocmds[code_win] = vim.api.nvim_create_autocmd(events.follow, {
|
||||||
vim.api.nvim_create_autocmd(events.follow, {
|
|
||||||
buffer = code_buf,
|
buffer = code_buf,
|
||||||
callback = function()
|
callback = function()
|
||||||
M._highlight_current_item(
|
M._highlight_current_item(code_win, cfg.o.outline_items.auto_set_cursor)
|
||||||
code_win,
|
|
||||||
cfg.o.outline_items.auto_set_cursor
|
|
||||||
)
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -398,7 +391,7 @@ local function setup_keymaps(bufnr)
|
|||||||
-- code actions
|
-- code actions
|
||||||
map(cfg.o.keymaps.code_actions, require('outline.code_action').show_code_actions)
|
map(cfg.o.keymaps.code_actions, require('outline.code_action').show_code_actions)
|
||||||
-- show help
|
-- 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
|
-- close outline
|
||||||
map(cfg.o.keymaps.close, function()
|
map(cfg.o.keymaps.close, function()
|
||||||
M.view:close()
|
M.view:close()
|
||||||
@@ -611,37 +604,6 @@ function M.is_open()
|
|||||||
return M.view:is_open()
|
return M.view:is_open()
|
||||||
end
|
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()
|
function M.is_active()
|
||||||
local winid = vim.fn.win_getid()
|
local winid = vim.fn.win_getid()
|
||||||
if M.view:is_open() and winid == M.view.winnr then
|
if M.view:is_open() and winid == M.view.winnr then
|
||||||
@@ -659,6 +621,29 @@ function M.has_provider()
|
|||||||
return providers.has_provider()
|
return providers.has_provider()
|
||||||
end
|
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 function setup_commands()
|
||||||
local cmd = function(n, c, o)
|
local cmd = function(n, c, o)
|
||||||
vim.api.nvim_create_user_command('Outline' .. 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 M = {}
|
||||||
local import_prefix = 'outline/providers/'
|
local import_prefix = 'outline/providers/'
|
||||||
|
|
||||||
_G._outline_current_provider = nil
|
|
||||||
|
|
||||||
function M.find_provider()
|
function M.find_provider()
|
||||||
if not M.providers then
|
if not M.providers then
|
||||||
M.providers = vim.tbl_map(function(p)
|
M.providers = vim.tbl_map(function(p)
|
||||||
|
|||||||
@@ -60,6 +60,28 @@
|
|||||||
---@field hovered boolean
|
---@field hovered boolean
|
||||||
---@field folded 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
|
-- API
|
||||||
|
|
||||||
---@class outline.OutlineOpts
|
---@class outline.OutlineOpts
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ local cfg = require('outline.config')
|
|||||||
|
|
||||||
local View = {}
|
local View = {}
|
||||||
|
|
||||||
|
---@class View
|
||||||
|
---@field bufnr integer
|
||||||
|
---@field winnr integer
|
||||||
|
|
||||||
function View:new()
|
function View:new()
|
||||||
return setmetatable({ bufnr = nil, winnr = nil }, { __index = View })
|
return setmetatable({ bufnr = nil, winnr = nil }, { __index = View })
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user