_extui.lua 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. --- @brief
  2. ---
  3. ---WARNING: This is an experimental interface intended to replace the message
  4. ---grid in the TUI.
  5. ---
  6. ---To enable the experimental UI (default opts shown):
  7. ---```lua
  8. ---require('vim._extui').enable({
  9. --- enable = true, -- Whether to enable or disable the UI.
  10. --- msg = { -- Options related to the message module.
  11. --- ---@type 'cmd'|'msg' Where to place regular messages, either in the
  12. --- ---cmdline or in a separate ephemeral message window.
  13. --- target = 'cmd',
  14. --- timeout = 4000, -- Time a message is visible in the message window.
  15. --- },
  16. ---})
  17. ---```
  18. ---
  19. ---There are four separate window types used by this interface:
  20. ---- "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and
  21. --- messages if 'cmdheight' > 0.
  22. ---- "msg": The message window; used for messages when 'cmdheight' == 0.
  23. ---- "pager": The pager window; used for |:messages| and certain messages
  24. --- that should be shown in full.
  25. ---- "dialog": The dialog window; used for prompt messages that expect user input.
  26. ---
  27. ---These four windows are assigned the "cmd", "msg", "pager" and "dialog"
  28. ---'filetype' respectively. Use a |FileType| autocommand to configure any local
  29. ---options for these windows and their respective buffers.
  30. ---
  31. ---Rather than a |hit-enter-prompt|, messages shown in the cmdline area that do
  32. ---not fit are appended with a `[+x]` "spill" indicator, where `x` indicates the
  33. ---spilled lines. To see the full message, the |g<| command can be used.
  34. local api = vim.api
  35. local ext = require('vim._extui.shared')
  36. ext.msg = require('vim._extui.messages')
  37. ext.cmd = require('vim._extui.cmdline')
  38. local M = {}
  39. local function ui_callback(event, ...)
  40. local handler = ext.msg[event] or ext.cmd[event]
  41. ext.check_targets()
  42. handler(...)
  43. api.nvim__redraw({
  44. flush = handler ~= ext.cmd.cmdline_hide or nil,
  45. cursor = handler == ext.cmd[event] and true or nil,
  46. win = handler == ext.cmd[event] and ext.wins.cmd or nil,
  47. })
  48. end
  49. local scheduled_ui_callback = vim.schedule_wrap(ui_callback)
  50. ---@nodoc
  51. function M.enable(opts)
  52. vim.validate('opts', opts, 'table', true)
  53. if opts.msg then
  54. vim.validate('opts.msg.pos', opts.msg.pos, 'nil', true, 'nil: "pos" moved to opts.target')
  55. vim.validate('opts.msg.box', opts.msg.box, 'nil', true, 'nil: "timeout" moved to opts.msg')
  56. vim.validate('opts.msg.target', opts.msg.target, function(tar)
  57. return tar == 'cmd' or tar == 'msg'
  58. end, "'cmd'|'msg'")
  59. end
  60. ext.cfg = vim.tbl_deep_extend('keep', opts, ext.cfg)
  61. if ext.cfg.enable == false then
  62. -- Detach and cleanup windows, buffers and autocommands.
  63. for _, win in pairs(ext.wins) do
  64. api.nvim_win_close(win, true)
  65. end
  66. for _, buf in pairs(ext.bufs) do
  67. api.nvim_buf_delete(buf, {})
  68. end
  69. api.nvim_clear_autocmds({ group = ext.augroup })
  70. vim.ui_detach(ext.ns)
  71. return
  72. end
  73. vim.ui_attach(ext.ns, { ext_messages = true, set_cmdheight = false }, function(event, ...)
  74. if not (ext.msg[event] or ext.cmd[event]) then
  75. return
  76. end
  77. if vim.in_fast_event() then
  78. scheduled_ui_callback(event, ...)
  79. else
  80. ui_callback(event, ...)
  81. end
  82. return true
  83. end)
  84. -- Use MsgArea and hide search highlighting in the cmdline window.
  85. -- TODO: Add new highlight group/namespaces for other windows? It is
  86. -- not clear if MsgArea is wanted in the msg, pager and dialog windows.
  87. api.nvim_set_hl(ext.ns, 'Normal', { link = 'MsgArea' })
  88. api.nvim_set_hl(ext.ns, 'Search', { link = 'MsgArea' })
  89. api.nvim_set_hl(ext.ns, 'CurSearch', { link = 'MsgArea' })
  90. api.nvim_set_hl(ext.ns, 'IncSearch', { link = 'MsgArea' })
  91. -- The visibility and appearance of the cmdline and message window is
  92. -- dependent on some option values. Reconfigure windows when option value
  93. -- has changed and after VimEnter when the user configured value is known.
  94. -- TODO: Reconsider what is needed when this module is enabled by default early in startup.
  95. local function check_cmdheight(value)
  96. ext.check_targets()
  97. -- 'cmdheight' set; (un)hide cmdline window and set its height.
  98. local cfg = { height = math.max(value, 1), hide = value == 0 }
  99. api.nvim_win_set_config(ext.wins.cmd, cfg)
  100. -- Change message position when 'cmdheight' was or becomes 0.
  101. if value == 0 or ext.cmdheight == 0 then
  102. ext.cfg.msg.target = value == 0 and 'msg' or 'cmd'
  103. ext.msg.prev_msg = ''
  104. end
  105. ext.cmdheight = value
  106. end
  107. vim.schedule(function()
  108. check_cmdheight(vim.o.cmdheight)
  109. end)
  110. api.nvim_create_autocmd('OptionSet', {
  111. group = ext.augroup,
  112. pattern = { 'cmdheight' },
  113. callback = function()
  114. check_cmdheight(vim.v.option_new)
  115. ext.msg.set_pos()
  116. end,
  117. desc = 'Set cmdline and message window dimensions for changed option values.',
  118. })
  119. api.nvim_create_autocmd({ 'VimResized', 'TabEnter' }, {
  120. group = ext.augroup,
  121. callback = ext.msg.set_pos,
  122. desc = 'Set cmdline and message window dimensions after shell resize or tabpage change.',
  123. })
  124. api.nvim_create_autocmd('WinEnter', {
  125. callback = function()
  126. local win = api.nvim_get_current_win()
  127. if vim.tbl_contains(ext.wins, win) and api.nvim_win_get_config(win).hide then
  128. vim.cmd.wincmd('p')
  129. end
  130. end,
  131. desc = 'Make sure hidden extui window is never current.',
  132. })
  133. end
  134. return M