Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Floating window support #4

Merged
merged 7 commits into from
Aug 31, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 199 additions & 26 deletions lua/nvim-drawer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local mod = {}
--- Initial size of the drawer, in lines or columns.
--- @field size integer
--- Position of the drawer.
--- @field position 'left' | 'right' | 'above' | 'below'
--- @field position 'left' | 'right' | 'above' | 'below' | 'float'
--- Don't keep the same buffer across all tabs.
--- @field nvim_tree_hack? boolean
--- Called before a buffer is created. This is called very rarely.
Expand Down Expand Up @@ -42,6 +42,24 @@ local mod = {}
--- is open.
--- Called in the context of the drawer window.
--- @field on_did_open? fun(event: { instance: DrawerInstance, winid: integer, bufnr: integer }): nil
--- @field win_config? DrawerWindowConfig

--- Adapted from `vim.api.keyset.win_config`
--- @class DrawerWindowConfig
--- @field margin? number
--- @field width? number
--- @field height? number
--- @field anchor? 'NE' | 'NC' | 'N' | 'NW' | 'CE' | 'E' | 'CC' | 'C' | 'CW' | 'W' | 'SE' | 'SC' | 'S' | 'SW'
--- @field external? boolean
--- @field focusable? boolean
--- @field zindex? integer
--- @field border? any
--- @field title? any
--- @field title_pos? string
--- @field footer? any
--- @field footer_pos? string
--- @field style? string
--- @field fixed? boolean

--- @class DrawerState
--- Whether the drawer assumes it's open or not.
Expand All @@ -56,9 +74,10 @@ local mod = {}
--- @field buffers integer[]
--- The internal ID of the drawer.
--- @field index integer
--- The windows belonging to the drawer.
---- @field winids integer[]
--- The windows and buffers belonging to the drawer.
--- @field windows_and_buffers table<integer, integer>
--- Whether the drawer is zoomed or not.
--- @field is_zoomed boolean

--- @type DrawerInstance[]
local instances = {}
Expand Down Expand Up @@ -105,6 +124,7 @@ function mod.create_drawer(opts)
count = 0,
buffers = {},
windows_and_buffers = {},
is_zoomed = false,
},
}

Expand Down Expand Up @@ -171,23 +191,7 @@ function mod.create_drawer(opts)
bufnr = bufnr,
})

winid = vim.api.nvim_open_win(bufnr, false, {
win = -1,
split = instance.opts.position,

width = (
instance.opts.position == 'left'
or instance.opts.position == 'right'
)
and instance.state.size
or nil,
height = (
instance.opts.position == 'above'
or instance.opts.position == 'below'
)
and instance.state.size
or nil,
})
winid = vim.api.nvim_open_win(bufnr, false, instance.build_win_config())

vim.api.nvim_win_call(winid, function()
vim.opt_local.bufhidden = 'hide'
Expand Down Expand Up @@ -228,6 +232,7 @@ function mod.create_drawer(opts)
winid = winid,
})
vim.api.nvim_win_set_buf(winid, bufnr)
vim.api.nvim_win_set_config(winid, instance.build_win_config())
vim.api.nvim_win_call(winid, function()
try_callback('on_did_open_buffer', {
instance = instance,
Expand Down Expand Up @@ -276,6 +281,164 @@ function mod.create_drawer(opts)
instance.state.previous_bufnr = final_bufnr
end

function instance.build_win_config()
--- @type vim.api.keyset.win_config
local win_config = {}

--- @type integer
local cmdheight = vim.opt.cmdheight:get()
--- @type integer
local screen_width = vim.opt.columns:get()
--- @type integer
local screen_height = vim.opt.lines:get()
local screen_height_without_cmdline = screen_height - cmdheight

if instance.opts.position == 'float' then
win_config.relative = 'editor'

--- @type DrawerWindowConfig
local instance_win_config = vim.tbl_deep_extend('force', {
anchor = 'CC',
margin = 0,
}, instance.opts.win_config or {})

--- @type vim.api.keyset.win_config
win_config = vim.tbl_deep_extend('force', win_config, instance_win_config)

local border_width = {
left = 0,
right = 0,
top = 0,
bottom = 0,
}
if win_config.border ~= nil and win_config.border ~= 'none' then
border_width = {
left = 1,
right = 1,
top = 1,
bottom = 1,
}
end

-- Taken from https://github.com/MarioCarrion/videos/blob/269956e913b76e6bb4ed790e4b5d25255cb1db4f/2023/01/nvim/lua/plugins/nvim-tree.lua
local window_width = (instance_win_config.width < 1)
and (screen_width * instance_win_config.width)
or instance_win_config.width
local window_height = (instance_win_config.height < 1)
and (screen_height_without_cmdline * instance_win_config.height)
or instance_win_config.height
local window_width_int = math.floor(window_width)
- (instance_win_config.margin * 2)
local window_height_int = math.floor(window_height)
- (instance_win_config.margin * 2)
local center_x = (
screen_width
- (window_width_int + border_width.left + border_width.right)
) / 2
local center_y = (
(
screen_height
- (window_height_int + border_width.top + border_width.bottom)
) / 2
) - cmdheight

win_config.width = window_width_int
win_config.height = window_height_int

local anchor = instance_win_config.anchor
if anchor == 'N' then
anchor = 'NC'
elseif anchor == 'S' then
anchor = 'SC'
elseif anchor == 'E' then
anchor = 'CE'
elseif anchor == 'W' then
anchor = 'CW'
elseif anchor == 'C' then
anchor = 'CC'
end
local anchor_x = string.sub(anchor, 2, 2)
local anchor_y = string.sub(anchor, 1, 1)

if anchor_y == 'N' then
win_config.row = instance_win_config.margin
elseif anchor_y == 'C' then
win_config.row = center_y
elseif anchor_y == 'S' then
win_config.row = screen_height_without_cmdline
- window_height_int
- instance_win_config.margin
- border_width.top
- border_width.bottom
end

if anchor_x == 'E' then
win_config.col = screen_width
- window_width_int
- instance_win_config.margin
- border_width.left
- border_width.right
elseif anchor_x == 'C' then
win_config.col = center_x
elseif anchor_x == 'W' then
win_config.col = instance_win_config.margin
end

if instance.state.is_zoomed then
win_config.row = instance_win_config.margin
win_config.col = instance_win_config.margin
win_config.width = screen_width
- (instance_win_config.margin * 2)
- border_width.left
- border_width.right
win_config.height = screen_height_without_cmdline
- (instance_win_config.margin * 2)
- border_width.top
- border_width.bottom
end

-- Cleanup
win_config.margin = nil
win_config.anchor = nil
else
win_config.win = -1
win_config.split = instance.opts.position

if
instance.opts.position == 'left'
or instance.opts.position == 'right'
then
win_config.width = instance.state.size

if instance.state.is_zoomed then
win_config.width = screen_width
end
end
if
instance.opts.position == 'above'
or instance.opts.position == 'below'
then
win_config.height = instance.state.size

if instance.state.is_zoomed then
win_config.height = screen_height
end
end
end

return win_config
end

function instance.toggle_zoom()
local winid = instance.get_winid()
if winid == -1 then
return
end

instance.state.is_zoomed = not instance.state.is_zoomed
vim.api.nvim_win_set_config(winid, instance.build_win_config())
end

--- Navigate to the next or previous buffer.
--- ```lua
--- --- Go to the next buffer.
Expand Down Expand Up @@ -524,12 +687,6 @@ function mod.setup(_)
for _, instance in ipairs(instances) do
if instance.state.is_open then
instance.open({ focus = false })

local winid = instance.get_winid()
instance.set_size(instance.state.size)

local bufnr = instance.state.previous_bufnr
vim.api.nvim_win_set_buf(winid, bufnr)
Comment on lines -527 to -532
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal milestone!

It always irked me that the restore logic here was so involved, even going back to vim-drawer: https://github.com/mikew/vim-drawer/blob/38c760debb0e85a3586878ea40ff6cc978fea054/autoload/drawer.vim#L290-L300

Really, .open and .close should suss themselves out correctly without having to account for any weird side effects, and finally they do!

else
instance.close({ save_size = false })
end
Expand All @@ -555,6 +712,22 @@ function mod.setup(_)
end,
})

vim.api.nvim_create_autocmd('VimResized', {
desc = 'nvim-drawer: Resize drawers',
group = drawer_augroup,
callback = function()
for _, instance in ipairs(instances) do
for winid, _ in pairs(instance.state.windows_and_buffers) do
if instance.opts.position == 'float' then
if vim.api.nvim_win_is_valid(winid) then
vim.api.nvim_win_set_config(winid, instance.build_win_config())
end
end
end
end
end,
})

vim.api.nvim_create_autocmd('BufWipeout', {
desc = 'nvim-drawer: Cleanup when buffer is wiped out',
group = drawer_augroup,
Expand Down