From 6b7c7987d88121b169de444ac367b4d3b10dd683 Mon Sep 17 00:00:00 2001 From: Munif Tanjim Date: Mon, 12 Jun 2023 22:18:33 +0600 Subject: [PATCH] feat(pickers): fully customizable layout --- lua/telescope/actions/generate.lua | 8 +- lua/telescope/actions/layout.lua | 4 +- lua/telescope/actions/set.lua | 8 +- lua/telescope/builtin/__internal.lua | 4 +- lua/telescope/config.lua | 10 + lua/telescope/pickers.lua | 420 +++++++++++------- lua/telescope/pickers/entry_display.lua | 4 +- lua/telescope/pickers/layout.lua | 94 ++++ lua/telescope/previewers/buffer_previewer.lua | 24 +- lua/telescope/previewers/previewer.lua | 2 +- lua/telescope/previewers/term_previewer.lua | 10 +- lua/telescope/testharness/helpers.lua | 2 +- lua/telescope/utils.lua | 2 +- 13 files changed, 390 insertions(+), 202 deletions(-) create mode 100644 lua/telescope/pickers/layout.lua diff --git a/lua/telescope/actions/generate.lua b/lua/telescope/actions/generate.lua index d17e890dc0..a4a3ca7f83 100644 --- a/lua/telescope/actions/generate.lua +++ b/lua/telescope/actions/generate.lua @@ -75,12 +75,12 @@ action_generate.refine = function(prompt_bufnr, opts) end -- title - if opts.prompt_title and current_picker.prompt_border then - current_picker.prompt_border:change_title(opts.prompt_title) + if opts.prompt_title and current_picker.layout.prompt.border then + current_picker.layout.prompt.border:change_title(opts.prompt_title) end - if opts.results_title and current_picker.results_border then - current_picker.results_border:change_title(opts.results_title) + if opts.results_title and current_picker.layout.results.border then + current_picker.layout.results.border:change_title(opts.results_title) end local results = {} diff --git a/lua/telescope/actions/layout.lua b/lua/telescope/actions/layout.lua index 0e8b27a968..257ade4c08 100644 --- a/lua/telescope/actions/layout.lua +++ b/lua/telescope/actions/layout.lua @@ -26,10 +26,10 @@ action_layout.toggle_preview = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) local status = state.get_status(picker.prompt_bufnr) - if picker.previewer and status.preview_win then + if picker.previewer and status.layout.preview.winid then picker.hidden_previewer = picker.previewer picker.previewer = nil - elseif picker.hidden_previewer and not status.preview_win then + elseif picker.hidden_previewer and not status.layout.preview.winid then picker.previewer = picker.hidden_previewer picker.hidden_previewer = nil else diff --git a/lua/telescope/actions/set.lua b/lua/telescope/actions/set.lua index d535df5b36..4dc1fb8539 100644 --- a/lua/telescope/actions/set.lua +++ b/lua/telescope/actions/set.lua @@ -231,11 +231,11 @@ action_set.scroll_previewer = function(prompt_bufnr, direction) local status = state.get_status(prompt_bufnr) -- Check if we actually have a previewer and a preview window - if type(previewer) ~= "table" or previewer.scroll_fn == nil or status.preview_win == nil then + if type(previewer) ~= "table" or previewer.scroll_fn == nil or status.layout.preview.winid == nil then return end - local default_speed = vim.api.nvim_win_get_height(status.preview_win) / 2 + local default_speed = vim.api.nvim_win_get_height(status.layaout.preview.winid) / 2 local speed = status.picker.layout_config.scroll_speed or default_speed previewer:scroll_fn(math.floor(speed * direction)) @@ -270,12 +270,12 @@ end -- Valid directions include: "1", "-1" action_set.scroll_results = function(prompt_bufnr, direction) local status = state.get_status(prompt_bufnr) - local default_speed = vim.api.nvim_win_get_height(status.results_win) / 2 + local default_speed = vim.api.nvim_win_get_height(status.layout.results.winid) / 2 local speed = status.picker.layout_config.scroll_speed or default_speed local input = direction > 0 and [[]] or [[]] - vim.api.nvim_win_call(status.results_win, function() + vim.api.nvim_win_call(status.layout.results.winid, function() vim.cmd([[normal! ]] .. math.floor(speed) .. input) end) diff --git a/lua/telescope/builtin/__internal.lua b/lua/telescope/builtin/__internal.lua index 031856f940..cf8fb39b33 100644 --- a/lua/telescope/builtin/__internal.lua +++ b/lua/telescope/builtin/__internal.lua @@ -978,8 +978,8 @@ internal.colorscheme = function(opts) preview_fn = function(_, entry, status) if not deleted then deleted = true - del_win(status.preview_win) - del_win(status.preview_border_win) + del_win(status.layout.preview.winid) + del_win(status.layout.preview.border.winid) end vim.cmd("colorscheme " .. entry.value) end, diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index f69f30dd8b..a9c6e31420 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -188,6 +188,16 @@ append( Default: 'horizontal']] ) +append( + "create_layout", + nil, + [[ + Configure the layout of Telescope pickers. + See |telescope.layout| for details. + + Default: 'nil']] +) + append("layout_config", layout_config_defaults, layout_config_description) append( diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 1ad989d2e5..cd9d602a14 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -20,6 +20,7 @@ local entry_display = require "telescope.pickers.entry_display" local p_highlighter = require "telescope.pickers.highlights" local p_scroller = require "telescope.pickers.scroller" local p_window = require "telescope.pickers.window" +local Layout = require "telescope.pickers.layout" local EntryManager = require "telescope.entry_manager" local MultiSelect = require "telescope.pickers.multi" @@ -31,6 +32,188 @@ local ns_telescope_matching = a.nvim_create_namespace "telescope_matching" local ns_telescope_prompt = a.nvim_create_namespace "telescope_prompt" local ns_telescope_prompt_prefix = a.nvim_create_namespace "telescope_prompt_prefix" +---@class telescope_popup_options +---@field border table<1|2|3|4, integer> +---@field borderchars table<1|2|3|4|5|6|7|8, string> +---@field borderhighlight string +---@field col integer +---@field enter boolean +---@field height integer +---@field highlight string +---@field line integer +---@field minheight integer +---@field title integer +---@field titlehighlight integer +---@field width integer + +-- Create three windows: +-- 1. Prompt window +-- 2. Options window +-- 3. Preview window +-- +---@param picker Picker +local function default_create_layout(picker) + local function make_border(border) + if not border then + return nil + end + return { + winid = border.win_id, + bufnr = border.bufnr, + change_title = border.change_title and function(...) + border.change_title(...) + end, + } + end + + local layout = Layout { + picker = picker, + ---@param self TelescopeLayout + mount = function(self) + local line_count = vim.o.lines - vim.o.cmdheight + if vim.o.laststatus ~= 0 then + line_count = line_count - 1 + end + + local popup_opts = picker:get_window_options(vim.o.columns, line_count) + + -- `popup.nvim` massaging so people don't have to remember minheight shenanigans + popup_opts.results.minheight = popup_opts.results.height + popup_opts.results.highlight = "TelescopeResultsNormal" + popup_opts.results.borderhighlight = "TelescopeResultsBorder" + popup_opts.results.titlehighlight = "TelescopeResultsTitle" + popup_opts.prompt.minheight = popup_opts.prompt.height + popup_opts.prompt.highlight = "TelescopePromptNormal" + popup_opts.prompt.borderhighlight = "TelescopePromptBorder" + popup_opts.prompt.titlehighlight = "TelescopePromptTitle" + if popup_opts.preview then + popup_opts.preview.minheight = popup_opts.preview.height + popup_opts.preview.highlight = "TelescopePreviewNormal" + popup_opts.preview.borderhighlight = "TelescopePreviewBorder" + popup_opts.preview.titlehighlight = "TelescopePreviewTitle" + end + + local results_win, results_opts = picker:_create_window("", popup_opts.results) + local results_bufnr = a.nvim_win_get_buf(results_win) + + self.results = Layout.Window { + winid = results_win, + bufnr = results_bufnr, + border = make_border(results_opts.border), + } + + if popup_opts.preview then + local preview_win, preview_opts = picker:_create_window("", popup_opts.preview) + local preview_bufnr = a.nvim_win_get_buf(preview_win) + + self.preview = Layout.Window { + winid = preview_win, + bufnr = preview_bufnr, + border = make_border(preview_opts.border), + } + end + + local prompt_win, prompt_opts = picker:_create_window("", popup_opts.prompt) + local prompt_bufnr = a.nvim_win_get_buf(prompt_win) + + self.prompt = Layout.Window { + winid = prompt_win, + bufnr = prompt_bufnr, + border = make_border(prompt_opts.border), + } + end, + ---@param self TelescopeLayout + unmount = function(self) + utils.win_delete("results_win", self.results.winid, true, true) + if self.preview then + utils.win_delete("preview_win", self.preview.winid, true, true) + end + + utils.win_delete("prompt_border_win", self.prompt.border.winid, true, true) + utils.win_delete("results_border_win", self.results.border.winid, true, true) + if self.preview then + utils.win_delete("preview_border_win", self.preview.border.winid, true, true) + end + + -- we cant use win_delete. We first need to close and then delete the buffer + if vim.api.nvim_win_is_valid(self.prompt.winid) then + vim.api.nvim_win_close(self.prompt.winid, true) + end + utils.buf_delete(self.prompt.bufnr) + end, + ---@param self TelescopeLayout + update = function(self) + local line_count = vim.o.lines - vim.o.cmdheight + if vim.o.laststatus ~= 0 then + line_count = line_count - 1 + end + + local popup_opts = picker:get_window_options(vim.o.columns, line_count) + -- `popup.nvim` massaging so people don't have to remember minheight shenanigans + popup_opts.results.minheight = popup_opts.results.height + popup_opts.prompt.minheight = popup_opts.prompt.height + if popup_opts.preview then + popup_opts.preview.minheight = popup_opts.preview.height + end + + local prompt_win = self.prompt.winid + local results_win = self.results.winid + local preview_win = self.preview and self.preview.winid + + local preview_opts + if popup_opts.preview then + if preview_win ~= nil then + -- Move all popups at the same time + popup.move(prompt_win, popup_opts.prompt) + popup.move(results_win, popup_opts.results) + popup.move(preview_win, popup_opts.preview) + else + popup_opts.preview.highlight = "TelescopePreviewNormal" + popup_opts.preview.borderhighlight = "TelescopePreviewBorder" + popup_opts.preview.titlehighlight = "TelescopePreviewTitle" + local preview_bufnr = (self.preview and self.preview.bufnr ~= nil) + and vim.api.nvim_buf_is_valid(self.preview.bufnr) + and self.preview.bufnr + or "" + preview_win, preview_opts = picker:_create_window(preview_bufnr, popup_opts.preview) + if preview_bufnr == "" then + preview_bufnr = a.nvim_win_get_buf(preview_win) + end + self.preview = Layout.Window { + winid = preview_win, + bufnr = preview_bufnr, + border = make_border(preview_opts.border), + } + if picker.previewer and picker.previewer.state and picker.previewer.state.winid then + picker.previewer.state.winid = preview_win + end + + -- Move prompt and results after preview created + vim.defer_fn(function() + popup.move(prompt_win, popup_opts.prompt) + popup.move(results_win, popup_opts.results) + end, 0) + end + elseif preview_win ~= nil then + popup.move(prompt_win, popup_opts.prompt) + popup.move(results_win, popup_opts.results) + + -- Remove preview after the prompt and results are moved + vim.defer_fn(function() + utils.win_delete("preview_win", preview_win, true) + utils.win_delete("preview_win", self.preview.border.winid, true) + self.preview = nil + end, 0) + else + popup.move(prompt_win, popup_opts.prompt) + popup.move(results_win, popup_opts.results) + end + end, + } + + return layout +end + local pickers = {} -- TODO: Add overscroll option for results buffer @@ -141,6 +324,7 @@ function Picker:new(opts) __scrolling_limit = tonumber(vim.F.if_nil(opts.temp__scrolling_limit, 250)), }, self) + obj.create_layout = opts.create_layout or config.values.create_layout or default_create_layout obj.get_window_options = opts.get_window_options or p_window.get_window_options if obj.all_previewers ~= nil and obj.all_previewers ~= false then @@ -321,13 +505,11 @@ end --- A helper function for creating each of the windows in a picker ---@param bufnr number: the buffer number to be used in the window ---@param popup_opts table: options to pass to `popup.create` ----@param nowrap boolean: is |'wrap'| disabled in the created window -function Picker:_create_window(bufnr, popup_opts, nowrap) +function Picker:_create_window(bufnr, popup_opts) local what = bufnr or "" local win, opts = popup.create(what, popup_opts) a.nvim_win_set_option(win, "winblend", self.window.winblend) - a.nvim_win_set_option(win, "wrap", not nowrap) local border_win = opts and opts.border and opts.border.win_id if border_win then a.nvim_win_set_option(border_win, "winblend", self.window.winblend) @@ -346,67 +528,35 @@ function Picker:find() -- User autocmd run it before create Telescope window vim.api.nvim_exec_autocmds("User", { pattern = "TelescopeFindPre" }) - -- Create three windows: - -- 1. Prompt window - -- 2. Options window - -- 3. Preview window - - local line_count = vim.o.lines - vim.o.cmdheight - if vim.o.laststatus ~= 0 then - line_count = line_count - 1 + local layout = self:create_layout() + layout:mount() + + self.layout = layout + self.prompt_win, self.prompt_bufnr, self.prompt_border = + layout.prompt.winid, layout.prompt.bufnr, layout.prompt.border + self.results_win, self.results_bufnr, self.results_border = + layout.results.winid, layout.results.bufnr, layout.results.border + if layout.preview then + self.preview_win, self.preview_bufnr, self.preview_border = + layout.preview.winid, layout.preview.bufnr, layout.preview.border + else + self.preview_win, self.preview_bufnr, self.preview_border = nil, nil, nil end - local popup_opts = self:get_window_options(vim.o.columns, line_count) - - -- `popup.nvim` massaging so people don't have to remember minheight shenanigans - popup_opts.results.minheight = popup_opts.results.height - popup_opts.results.highlight = "TelescopeResultsNormal" - popup_opts.results.borderhighlight = "TelescopeResultsBorder" - popup_opts.results.titlehighlight = "TelescopeResultsTitle" - popup_opts.prompt.minheight = popup_opts.prompt.height - popup_opts.prompt.highlight = "TelescopePromptNormal" - popup_opts.prompt.borderhighlight = "TelescopePromptBorder" - popup_opts.prompt.titlehighlight = "TelescopePromptTitle" - if popup_opts.preview then - popup_opts.preview.minheight = popup_opts.preview.height - popup_opts.preview.highlight = "TelescopePreviewNormal" - popup_opts.preview.borderhighlight = "TelescopePreviewBorder" - popup_opts.preview.titlehighlight = "TelescopePreviewTitle" + pcall(a.nvim_buf_set_option, self.results_bufnr, "tabstop", 1) -- #1834 + pcall(a.nvim_buf_set_option, self.prompt_bufnr, "tabstop", 1) -- #1834 + a.nvim_buf_set_option(self.prompt_bufnr, "buftype", "prompt") + if not self.wrap_results then + a.nvim_win_set_option(self.results_win, "wrap", false) end - - local results_win, results_opts, results_border_win = - self:_create_window("", popup_opts.results, not self.wrap_results) - - local results_bufnr = a.nvim_win_get_buf(results_win) - pcall(a.nvim_buf_set_option, results_bufnr, "tabstop", 1) -- #1834 - - self.results_bufnr = results_bufnr - self.results_win = results_win - self.results_border = results_opts and results_opts.border - - local preview_win, preview_opts, preview_bufnr, preview_border_win - if popup_opts.preview then - preview_win, preview_opts, preview_border_win = self:_create_window("", popup_opts.preview) - preview_bufnr = a.nvim_win_get_buf(preview_win) + a.nvim_win_set_option(self.prompt_win, "wrap", true) + if self.preview_win then + a.nvim_win_set_option(self.preview_win, "wrap", true) end - -- This is needed for updating the title - local preview_border = preview_opts and preview_opts.border - self.preview_win = preview_win - self.preview_border = preview_border - - local prompt_win, prompt_opts, prompt_border_win = self:_create_window("", popup_opts.prompt) - local prompt_bufnr = a.nvim_win_get_buf(prompt_win) - pcall(a.nvim_buf_set_option, prompt_bufnr, "tabstop", 1) -- #1834 - - self.prompt_bufnr = prompt_bufnr - self.prompt_win = prompt_win - self.prompt_border = prompt_opts and prompt_opts.border -- Prompt prefix local prompt_prefix = self.prompt_prefix - a.nvim_buf_set_option(prompt_bufnr, "buftype", "prompt") - vim.fn.prompt_setprompt(prompt_bufnr, prompt_prefix) - self.prompt_prefix = prompt_prefix + vim.fn.prompt_setprompt(self.prompt_bufnr, prompt_prefix) self:_reset_prefix_color() -- TODO: This could be configurable in the future, but I don't know why you would @@ -415,9 +565,9 @@ function Picker:find() -- This just lets us stop doing stuff after tons of things. self.max_results = self.__scrolling_limit - vim.api.nvim_buf_set_lines(results_bufnr, 0, self.max_results, false, utils.repeated_table(self.max_results, "")) + vim.api.nvim_buf_set_lines(self.results_bufnr, 0, self.max_results, false, utils.repeated_table(self.max_results, "")) - local status_updater = self:get_status_updater(prompt_win, prompt_bufnr) + local status_updater = self:get_status_updater(self.prompt_win, self.prompt_bufnr) local debounced_status = debounce.throttle_leading(status_updater, 50) local tx, rx = channel.mpsc() @@ -451,8 +601,8 @@ function Picker:find() self.sorter:_init() -- Do filetype last, so that users can register at the last second. - pcall(a.nvim_buf_set_option, prompt_bufnr, "filetype", "TelescopePrompt") - pcall(a.nvim_buf_set_option, results_bufnr, "filetype", "TelescopeResults") + pcall(a.nvim_buf_set_option, self.prompt_bufnr, "filetype", "TelescopePrompt") + pcall(a.nvim_buf_set_option, self.results_bufnr, "filetype", "TelescopeResults") await_schedule() @@ -467,8 +617,8 @@ function Picker:find() self:_reset_track() - if not vim.api.nvim_buf_is_valid(prompt_bufnr) then - log.debug("ON_LINES: Invalid prompt_bufnr", prompt_bufnr) + if not vim.api.nvim_buf_is_valid(self.prompt_bufnr) then + log.debug("ON_LINES: Invalid prompt_bufnr", self.prompt_bufnr) return end @@ -505,7 +655,7 @@ function Picker:find() end) -- Register attach - vim.api.nvim_buf_attach(prompt_bufnr, false, { + vim.api.nvim_buf_attach(self.prompt_bufnr, false, { on_lines = function(...) if self._finder_attached then find_id = self:_next_find_id() @@ -523,46 +673,45 @@ function Picker:find() vim.api.nvim_create_augroup("PickerInsert", {}) -- TODO: Use WinLeave as well? vim.api.nvim_create_autocmd("BufLeave", { - buffer = prompt_bufnr, + buffer = self.prompt_bufnr, group = "PickerInsert", nested = true, once = true, callback = function() - require("telescope.pickers").on_close_prompt(prompt_bufnr) + require("telescope.pickers").on_close_prompt(self.prompt_bufnr) end, }) vim.api.nvim_create_autocmd("VimResized", { - buffer = prompt_bufnr, + buffer = self.prompt_bufnr, group = "PickerInsert", nested = true, callback = function() - require("telescope.pickers").on_resize_window(prompt_bufnr) + require("telescope.pickers").on_resize_window(self.prompt_bufnr) end, }) - self.prompt_bufnr = prompt_bufnr - state.set_status( - prompt_bufnr, + self.prompt_bufnr, setmetatable({ - prompt_bufnr = prompt_bufnr, - prompt_win = prompt_win, - prompt_border_win = prompt_border_win, - - results_bufnr = results_bufnr, - results_win = results_win, - results_border_win = results_border_win, - - preview_bufnr = preview_bufnr, - preview_win = preview_win, - preview_border_win = preview_border_win, + layout = layout, picker = self, + + -- compatibility + prompt_bufnr = self.prompt_bufnr, + prompt_win = self.prompt_win, + prompt_border_win = self.prompt_border.winid, + results_bufnr = self.results_bufnr, + results_win = self.results_win, + results_border_win = self.results_border.winid, + preview_bufnr = self.preview_bufnr, + preview_win = self.preview_win, + preview_border_win = self.preview_border and self.preview_border.winid, }, { __mode = "kv", }) ) - mappings.apply_keymap(prompt_bufnr, self.attach_mappings, config.values.mappings) + mappings.apply_keymap(self.prompt_bufnr, self.attach_mappings, config.values.mappings) tx.send() main_loop() @@ -570,79 +719,20 @@ end --- A helper function to update picker windows when layout options are changed function Picker:recalculate_layout() - local line_count = vim.o.lines - vim.o.cmdheight - if vim.o.laststatus ~= 0 then - line_count = line_count - 1 - end - - local popup_opts = self:get_window_options(vim.o.columns, line_count) - -- `popup.nvim` massaging so people don't have to remember minheight shenanigans - popup_opts.results.minheight = popup_opts.results.height - popup_opts.prompt.minheight = popup_opts.prompt.height - if popup_opts.preview then - popup_opts.preview.minheight = popup_opts.preview.height - end - local status = state.get_status(self.prompt_bufnr) - local prompt_win = status.prompt_win - local results_win = status.results_win - local preview_win = status.preview_win - - local preview_opts, preview_border_win - if popup_opts.preview then - if preview_win ~= nil then - -- Move all popups at the same time - popup.move(prompt_win, popup_opts.prompt) - popup.move(results_win, popup_opts.results) - popup.move(preview_win, popup_opts.preview) - else - popup_opts.preview.highlight = "TelescopePreviewNormal" - popup_opts.preview.borderhighlight = "TelescopePreviewBorder" - popup_opts.preview.titlehighlight = "TelescopePreviewTitle" - local preview_bufnr = status.preview_bufnr ~= nil - and vim.api.nvim_buf_is_valid(status.preview_bufnr) - and status.preview_bufnr - or "" - preview_win, preview_opts, preview_border_win = self:_create_window(preview_bufnr, popup_opts.preview) - if preview_bufnr == "" then - preview_bufnr = a.nvim_win_get_buf(preview_win) - end - status.preview_win = preview_win - status.preview_bufnr = preview_bufnr - status.preview_border_win = preview_border_win - state.set_status(prompt_win, status) - self.preview_win = preview_win - self.preview_border_win = preview_border_win - self.preview_border = preview_opts and preview_opts.border - if self.previewer and self.previewer.state and self.previewer.state.winid then - self.previewer.state.winid = preview_win - end + status.layout:update() - -- Move prompt and results after preview created - vim.defer_fn(function() - popup.move(prompt_win, popup_opts.prompt) - popup.move(results_win, popup_opts.results) - end, 0) - end - elseif preview_win ~= nil then - popup.move(prompt_win, popup_opts.prompt) - popup.move(results_win, popup_opts.results) - - -- Remove preview after the prompt and results are moved - vim.defer_fn(function() - utils.win_delete("preview_win", preview_win, true) - utils.win_delete("preview_win", status.preview_border_win, true) - status.preview_win = nil - status.preview_border_win = nil - state.set_status(prompt_win, status) - self.preview_win = nil - self.preview_border_win = nil - self.preview_border = nil - end, 0) + local layout = status.layout + self.prompt_win, self.prompt_bufnr, self.prompt_border = + layout.prompt.winid, layout.prompt.bufnr, layout.prompt.border + self.results_win, self.results_bufnr, self.results_border = + layout.results.winid, layout.results.bufnr, layout.results.border + if layout.preview then + self.preview_win, self.preview_bufnr, self.preview_border = + layout.preview.winid, layout.preview.bufnr, layout.preview.border else - popup.move(prompt_win, popup_opts.prompt) - popup.move(results_win, popup_opts.results) + self.preview_win, self.preview_bufnr, self.preview_border = nil, nil, nil end -- Temporarily disabled: Draw the screen ASAP. This makes things feel speedier. @@ -745,20 +835,9 @@ end ---@param status table: table containing information on the picker --- and associated windows. Generally obtained from `state.get_status` function Picker.close_windows(status) - utils.win_delete("results_win", status.results_win, true, true) - utils.win_delete("preview_win", status.preview_win, true, true) - - utils.win_delete("prompt_border_win", status.prompt_border_win, true, true) - utils.win_delete("results_border_win", status.results_border_win, true, true) - utils.win_delete("preview_border_win", status.preview_border_win, true, true) - - -- we cant use win_delete. We first need to close and then delete the buffer - if vim.api.nvim_win_is_valid(status.prompt_win) then - vim.api.nvim_win_close(status.prompt_win, true) - end - utils.buf_delete(status.prompt_bufnr) - - state.clear_status(status.prompt_bufnr) + local prompt_bufnr = status.layout.prompt.bufnr + status.layout:unmount() + state.clear_status(prompt_bufnr) end --- Get the entry table of the current selection @@ -1075,7 +1154,12 @@ end --- Refresh the previewer based on the current `status` of the picker function Picker:refresh_previewer() local status = state.get_status(self.prompt_bufnr) - if self.previewer and status.preview_win and a.nvim_win_is_valid(status.preview_win) then + if + self.previewer + and status.layout.preview + and status.layout.preview.winid + and a.nvim_win_is_valid(status.layout.preview.winid) + then self:_increment "previewed" self.previewer:preview(self._selection_entry, status) @@ -1087,7 +1171,7 @@ function Picker:refresh_previewer() local new_title = self.previewer:title(self._selection_entry, config.values.dynamic_preview_title) if new_title ~= nil and new_title ~= self.preview_title then self.preview_title = new_title - self.preview_border:change_title(new_title) + self.layout.preview.border:change_title(new_title) end end end diff --git a/lua/telescope/pickers/entry_display.lua b/lua/telescope/pickers/entry_display.lua index d201bdd3e2..1353cff13f 100644 --- a/lua/telescope/pickers/entry_display.lua +++ b/lua/telescope/pickers/entry_display.lua @@ -75,8 +75,8 @@ entry_display.create = function(configuration) if width == nil then local status = state.get_status(vim.F.if_nil(configuration.prompt_bufnr, vim.api.nvim_get_current_buf())) local s = {} - s[1] = vim.api.nvim_win_get_width(status.results_win) - #status.picker.selection_caret - s[2] = vim.api.nvim_win_get_height(status.results_win) + s[1] = vim.api.nvim_win_get_width(status.layout.results.winid) - #status.picker.selection_caret + s[2] = vim.api.nvim_win_get_height(status.layout.results.winid) width = resolve.resolve_width(v.width)(nil, s[1], s[2]) end if type(item) == "table" then diff --git a/lua/telescope/pickers/layout.lua b/lua/telescope/pickers/layout.lua new file mode 100644 index 0000000000..e4169b45b1 --- /dev/null +++ b/lua/telescope/pickers/layout.lua @@ -0,0 +1,94 @@ +local function wrap_instance(class, instance) + local self = instance + if not getmetatable(instance) then + self = setmetatable(instance, { __index = class }) + end + return self +end + +---@class TelescopeWindowBorder +---@field bufnr? integer +---@field winid? integer + +---@param class TelescopeWindowBorder +---@return TelescopeWindowBorder +local function init_border(class, config) + config = config or {} + + local self = wrap_instance(class, config) + if not self.change_title then + self.change_title = class.change_title + end + + return self +end + +---@class TelescopeWindowBorder +local Border = setmetatable({}, { + __call = init_border, + __name = "TelescopeWindowBorder", +}) + +---@param title string +---@param pos? "NW"|"N"|"NE"|"SW"|"S"|"SE" +function Border:change_title(title, pos) end + +---@class TelescopeWindow +---@field border TelescopeWindowBorder +---@field bufnr integer +---@field winid integer + +---@param class TelescopeWindow +---@return TelescopeWindow +local function init_window(class, config) + config = config or {} + + local self = wrap_instance(class, config) + self.border = Border(config.border) + + return self +end + +---@class TelescopeWindow +local Window = setmetatable({}, { + __call = init_window, + __name = "TelescopeWindow", +}) + +---@class TelescopeLayout +---@field prompt TelescopeWindow +---@field results TelescopeWindow +---@field preview? TelescopeWindow + +---@param class TelescopeLayout +---@return TelescopeLayout +local function init_layout(class, config) + config = config or {} + + local self = wrap_instance(class, config) + + assert(config.mount, "missing layout:mount") + assert(config.unmount, "missing layout:unmount") + assert(config.update, "missing layout:update") + + return self +end + +---@class TelescopeLayout +local Layout = setmetatable({ + Window = Window, +}, { + __call = init_layout, + __name = "TelescopeLayout", +}) + +function Layout:mount() end + +function Layout:unmount() end + +function Layout:update() end + +---@alias TelescopeWindow.constructor fun(config: table): TelescopeWindow +---@alias TelescopeLayout.constructor fun(config: table): TelescopeLayout + +return Layout --[[@as TelescopeLayout.constructor|{ Window: TelescopeWindow.constructor }]] diff --git a/lua/telescope/previewers/buffer_previewer.lua b/lua/telescope/previewers/buffer_previewer.lua index ad3779ea37..be9235ac2f 100644 --- a/lua/telescope/previewers/buffer_previewer.lua +++ b/lua/telescope/previewers/buffer_previewer.lua @@ -410,31 +410,31 @@ previewers.new_buffer_previewer = function(opts) function opts.preview_fn(self, entry, status) if get_bufnr(self) == nil then - set_bufnr(self, vim.api.nvim_win_get_buf(status.preview_win)) - preview_window_id = status.preview_win + set_bufnr(self, vim.api.nvim_win_get_buf(status.layout.preview.winid)) + preview_window_id = status.layout.preview.winid end if opts.get_buffer_by_name and get_bufnr_by_bufname(self, opts.get_buffer_by_name(self, entry)) then self.state.bufname = opts.get_buffer_by_name(self, entry) self.state.bufnr = get_bufnr_by_bufname(self, self.state.bufname) - utils.win_set_buf_noautocmd(status.preview_win, self.state.bufnr) + utils.win_set_buf_noautocmd(status.layout.preview.winid, self.state.bufnr) else local bufnr = vim.api.nvim_create_buf(false, true) set_bufnr(self, bufnr) vim.schedule(function() if vim.api.nvim_buf_is_valid(bufnr) then - utils.win_set_buf_noautocmd(status.preview_win, bufnr) + utils.win_set_buf_noautocmd(status.layout.preview.winid, bufnr) end end) - vim.api.nvim_win_set_option(status.preview_win, "winhl", "Normal:TelescopePreviewNormal") - vim.api.nvim_win_set_option(status.preview_win, "signcolumn", "no") - vim.api.nvim_win_set_option(status.preview_win, "foldlevel", 100) - vim.api.nvim_win_set_option(status.preview_win, "wrap", false) - vim.api.nvim_win_set_option(status.preview_win, "scrollbind", false) + vim.api.nvim_win_set_option(status.layout.preview.winid, "winhl", "Normal:TelescopePreviewNormal") + vim.api.nvim_win_set_option(status.layout.preview.winid, "signcolumn", "no") + vim.api.nvim_win_set_option(status.layout.preview.winid, "foldlevel", 100) + vim.api.nvim_win_set_option(status.layout.preview.winid, "wrap", false) + vim.api.nvim_win_set_option(status.layout.preview.winid, "scrollbind", false) - self.state.winid = status.preview_win + self.state.winid = status.layout.preview.winid self.state.bufname = nil end @@ -1018,7 +1018,7 @@ previewers.autocommands = defaulter(function(_) table.insert(display, string.format(" augroup: %s - [ %d entries ]", entry.value.group_name, #results)) -- TODO: calculate banner width/string in setup() -- TODO: get column characters to be the same HL group as border - table.insert(display, string.rep("─", vim.fn.getwininfo(status.preview_win)[1].width)) + table.insert(display, string.rep("─", vim.fn.getwininfo(status.layout.preview.winid)[1].width)) for idx, item in ipairs(results) do if item == entry then @@ -1046,7 +1046,7 @@ previewers.autocommands = defaulter(function(_) -- set the cursor position after self.state.bufnr is connected to the -- preview window (which is scheduled in new_buffer_previewer) vim.schedule(function() - pcall(vim.api.nvim_win_set_cursor, status.preview_win, { selected_row, 0 }) + pcall(vim.api.nvim_win_set_cursor, status.layout.preview.winid, { selected_row, 0 }) end) self.state.last_set_bufnr = self.state.bufnr diff --git a/lua/telescope/previewers/previewer.lua b/lua/telescope/previewers/previewer.lua index b217a56213..a02d5bf383 100644 --- a/lua/telescope/previewers/previewer.lua +++ b/lua/telescope/previewers/previewer.lua @@ -39,7 +39,7 @@ function Previewer:preview(entry, status) end if vim.api.nvim_buf_is_valid(self._empty_bufnr) then - vim.api.nvim_win_set_buf(status.preview_win, self._empty_bufnr) + vim.api.nvim_win_set_buf(status.layout.preview.winid, self._empty_bufnr) end return end diff --git a/lua/telescope/previewers/term_previewer.lua b/lua/telescope/previewers/term_previewer.lua index 368f6a9794..115103cfca 100644 --- a/lua/telescope/previewers/term_previewer.lua +++ b/lua/telescope/previewers/term_previewer.lua @@ -186,18 +186,18 @@ previewers.new_termopen_previewer = function(opts) function opts.preview_fn(self, entry, status) if get_bufnr(self) == nil then - set_bufnr(self, vim.api.nvim_win_get_buf(status.preview_win)) + set_bufnr(self, vim.api.nvim_win_get_buf(status.layout.preview.winid)) end local prev_bufnr = get_bufnr_by_bufentry(self, entry) if prev_bufnr then self.state.termopen_bufnr = prev_bufnr - utils.win_set_buf_noautocmd(status.preview_win, self.state.termopen_bufnr) + utils.win_set_buf_noautocmd(status.layout.preview.winid, self.state.termopen_bufnr) self.state.termopen_id = term_ids[self.state.termopen_bufnr] else local bufnr = vim.api.nvim_create_buf(false, true) set_bufnr(self, bufnr) - utils.win_set_buf_noautocmd(status.preview_win, bufnr) + utils.win_set_buf_noautocmd(status.layout.preview.winid, bufnr) local term_opts = { cwd = opts.cwd or vim.loop.cwd(), @@ -277,7 +277,7 @@ previewers.vimgrep = defaulter(function(opts) end, get_command = function(entry, status) - local win_id = status.preview_win + local win_id = status.layout.preview.winid local height = vim.api.nvim_win_get_height(win_id) local p = from_entry.path(entry, true, false) @@ -312,7 +312,7 @@ previewers.qflist = defaulter(function(opts) end, get_command = function(entry, status) - local win_id = status.preview_win + local win_id = status.layout.preview.winid local height = vim.api.nvim_win_get_height(win_id) local p = from_entry.path(entry, true, false) diff --git a/lua/telescope/testharness/helpers.lua b/lua/telescope/testharness/helpers.lua index 5296c455ee..1f0b0b1d78 100644 --- a/lua/telescope/testharness/helpers.lua +++ b/lua/telescope/testharness/helpers.lua @@ -7,7 +7,7 @@ end test_helpers.get_results_bufnr = function() local state = require "telescope.state" - return state.get_status(vim.api.nvim_get_current_buf()).results_bufnr + return state.get_status(vim.api.nvim_get_current_buf()).layout.results.bufnr end test_helpers.get_file = function() diff --git a/lua/telescope/utils.lua b/lua/telescope/utils.lua index 652a14688f..5522340758 100644 --- a/lua/telescope/utils.lua +++ b/lua/telescope/utils.lua @@ -203,7 +203,7 @@ end local calc_result_length = function(truncate_len) local status = get_status(vim.api.nvim_get_current_buf()) - local len = vim.api.nvim_win_get_width(status.results_win) - status.picker.selection_caret:len() - 2 + local len = vim.api.nvim_win_get_width(status.layout.results.winid) - status.picker.selection_caret:len() - 2 return type(truncate_len) == "number" and len - truncate_len or len end