fix(picker): better main window management. Closes #842

This commit is contained in:
Folke Lemaitre 2025-02-01 11:26:17 +01:00
parent 597ba50ce9
commit f0f053a1d9
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
4 changed files with 80 additions and 16 deletions

View file

@ -123,10 +123,13 @@ M.edit_vsplit = { "vsplit", "confirm" }
M.edit_tab = { "tab", "confirm" }
local function wincmd(picker, cmd)
local win = vim.api.nvim_get_current_win()
if vim.api.nvim_win_is_valid(picker.main) then
vim.api.nvim_win_call(picker.main, function()
vim.cmd(cmd)
picker.main = vim.api.nvim_get_current_win()
local ft = vim.bo[vim.api.nvim_get_current_buf()].filetype
vim.api.nvim_set_current_win(win)
end)
end
end

View file

@ -1,5 +1,8 @@
---@class snacks.picker.main
---@class snacks.picker.Main
---@field opts snacks.picker.main.Config
---@field win number
local M = {}
M.__index = M
---@class snacks.picker.main.Config
---@field float? boolean main window can be a floating window (defaults to false)
@ -7,19 +10,40 @@ local M = {}
---@field current? boolean main window should be the current window (defaults to false)
---@param opts? snacks.picker.main.Config
function M.get(opts)
function M.new(opts)
opts = vim.tbl_extend("force", {
float = false,
file = true,
current = false,
}, opts or {})
local self = setmetatable({}, M)
self.opts = opts
self.win = vim.api.nvim_get_current_win()
self.win = self:find()
return self
end
function M:get()
if not self.win or not vim.api.nvim_win_is_valid(self.win) then
self.win = self:find()
end
return self.win
end
---@param win number
function M:set(win)
self.win = win
end
function M:find()
local current = vim.api.nvim_get_current_win()
if opts.current then
if self.opts.current then
return current
end
local prev = vim.fn.winnr("#")
local wins = { current, prev }
local all = vim.api.nvim_list_wins()
local non_float = 0
local wins = { self.win, current, prev }
local all = vim.api.nvim_tabpage_list_wins(0)
-- sort all by lastused of the win buffer
table.sort(all, function(a, b)
local ba = vim.api.nvim_win_get_buf(a)
@ -27,27 +51,32 @@ function M.get(opts)
return vim.fn.getbufinfo(ba)[1].lastused > vim.fn.getbufinfo(bb)[1].lastused
end)
vim.list_extend(wins, all)
---@param win number
wins = vim.tbl_filter(function(win)
-- exclude invalid windows
if win == 0 or not vim.api.nvim_win_is_valid(win) then
return false
end
local win_config = vim.api.nvim_win_get_config(win)
local is_float = win_config.relative ~= ""
if not is_float then
non_float = win
end
if vim.w[win].snacks_layout then
return false
end
local buf = vim.api.nvim_win_get_buf(win)
-- exclude non-file buffers
if opts.file and vim.bo[vim.api.nvim_win_get_buf(win)].buftype ~= "" then
if self.opts.file and vim.bo[buf].buftype ~= "" then
return false
end
local win_config = vim.api.nvim_win_get_config(win)
local is_float = win_config.relative ~= ""
-- exclude floating windows and non-focusable windows
if is_float and (not opts.float or not win_config.focusable) then
if is_float and (not self.opts.float or not win_config.focusable) then
return false
end
return true
end, wins)
return wins[1] or current
return wins[1] or non_float
end
return M

View file

@ -18,6 +18,7 @@ local _id = 0
---@field list snacks.picker.list
---@field matcher snacks.picker.Matcher
---@field main number
---@field _main snacks.picker.Main
---@field preview snacks.picker.Preview
---@field shown? boolean
---@field sort snacks.picker.sort
@ -28,7 +29,6 @@ local _id = 0
---@field history snacks.picker.History
---@field visual? snacks.picker.Visual
local M = {}
M.__index = M
--- Keep track of garbage collection
---@type table<snacks.Picker,boolean>
@ -49,8 +49,26 @@ M.last = nil
---@alias snacks.picker.history.Record {pattern: string, search: string, live?: boolean}
function M:__index(key)
if M[key] then
return M[key]
end
if key == "main" then
return self._main:get()
end
end
function M:__newindex(key, value)
if key == "main" then
self._main:set(value)
else
rawset(self, key, value)
end
end
---@hide
---@param opts? snacks.picker.Config
---@return snacks.Picker
function M.new(opts)
local self = setmetatable({}, M)
_id = _id + 1
@ -112,7 +130,7 @@ function M.new(opts)
self.visual = Snacks.picker.util.visual()
self.start_time = uv.hrtime()
Snacks.picker.current = self
self.main = require("snacks.picker.core.main").get(self.opts.main)
self._main = require("snacks.picker.core.main").new(self.opts.main)
local actions = require("snacks.picker.core.actions").get(self)
self.opts.win.input.actions = actions
self.opts.win.list.actions = actions
@ -210,6 +228,11 @@ function M:is_focused()
return vim.tbl_contains({ self.input.win.win, self.list.win.win, self.preview.win.win }, current)
end
function M:on_current_tab()
return self.layout:valid()
and vim.api.nvim_get_current_tabpage() == vim.api.nvim_win_get_tabpage(self.layout.root.win)
end
--- Execute the callback in normal mode.
--- When still in insert mode, stop insert mode first,
--- and then`vim.schedule` the callback.

View file

@ -152,8 +152,12 @@ function State.new(picker)
if uv.fs_stat(buf_file) then
self:open(buf_file)
end
picker.list.win:on({ "WinEnter", "BufEnter" }, function()
self:follow()
picker.list.win:on({ "WinEnter", "BufEnter" }, function(_, ev)
vim.schedule(function()
if ev.buf == vim.api.nvim_get_current_buf() then
self:follow()
end
end)
end)
picker.list.win:on("TermClose", function()
self:update()
@ -185,7 +189,7 @@ function State:follow()
return
end
local picker = self:picker()
if not picker or picker:is_focused() then
if not picker or picker:is_focused() or not picker:on_current_tab() then
return
end
local win = vim.api.nvim_get_current_win()
@ -194,6 +198,11 @@ function State:follow()
end
local buf = vim.api.nvim_get_current_buf()
local file = vim.api.nvim_buf_get_name(buf)
local item = picker:current()
if item and item.file == norm(file) then
return
end
dd("follow", file)
self:show(file)
end
@ -201,7 +210,7 @@ end
---@param opts? {refresh?: boolean}
function State:show(path, opts)
opts = opts or {}
path = vim.fs.normalize(path)
path = norm(path)
if not uv.fs_stat(path) then
return
end