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:
hedy
2023-11-18 20:52:50 +08:00
parent 6af621f168
commit e56145f8dd
7 changed files with 275 additions and 53 deletions

View File

@@ -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
View 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
View 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

View File

@@ -72,26 +72,19 @@ 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(code_win, cfg.o.outline_items.auto_set_cursor)
M._highlight_current_item( end,
code_win, })
cfg.o.outline_items.auto_set_cursor
)
end,
})
end end
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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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