diff --git a/doc/telescope.txt b/doc/telescope.txt index 3c89161..c20f3ea 100644 --- a/doc/telescope.txt +++ b/doc/telescope.txt @@ -143,6 +143,20 @@ telescope.setup({opts}) *telescope.setup()* } + *telescope.defaults.cycle_layout_list* + cycle_layout_list: ~ + Determines the layouts to cycle through when using `actions.cycle_layout_next` + and `actions.cycle_layout_prev`. + Should be a list of "layout setups". + Each "layout setup" can take one of two forms: + 1. string
+ This is interpreted as the name of a `layout_strategy` + 2. table
+ A table with possible keys `layout_strategy`, `layout_config` and `previewer` + + Default: TODO + + *telescope.defaults.winblend* winblend: ~ Configure winblend for telescope floating windows. See |winblend| for @@ -2238,6 +2252,65 @@ action_set.scroll_results({prompt_bufnr}, {direction}) *action_set.scroll_result +================================================================================ + *telescope.actions.layout* + +The layout actions are actions to be used to change the layout of a picker. + +action_layout.toggle_preview({prompt_bufnr}) *action_layout.toggle_preview()* + Toggle preview window. + - Note: preview window can be toggled even if preview is set to false. + + This action is not mapped by default. + + + Parameters: ~ + {prompt_bufnr} (number) The prompt bufnr + + +action_layout.toggle_prompt_position({prompt_bufnr}) *action_layout.toggle_prompt_position()* + Toggles the `prompt_position` option between "top" and "bottom". Checks if + `prompt_position` is an option for the current layout. + + This action is not mapped by default. + + + Parameters: ~ + {prompt_bufnr} (number) The prompt bufnr + + +action_layout.toggle_mirror({prompt_bufnr}) *action_layout.toggle_mirror()* + Toggles the `mirror` option between `true` and `false`. Checks if `mirror` + is an option for the current layout. + + This action is not mapped by default. + + + Parameters: ~ + {prompt_bufnr} (number) The prompt bufnr + + +action_layout.cycle_layout_next({prompt_bufnr}) *action_layout.cycle_layout_next()* + Cycles to the next layout in `cycle_layout_list`. + + This action is not mapped by default. + + + Parameters: ~ + {prompt_bufnr} (number) The prompt bufnr + + +action_layout.cycle_layout_prev({prompt_bufnr}) *action_layout.cycle_layout_prev()* + Cycles to the previous layout in `cycle_layout_list`. + + This action is not mapped by default. + + + Parameters: ~ + {prompt_bufnr} (number) The prompt bufnr + + + ================================================================================ *telescope.actions.utils* diff --git a/lua/telescope/actions/layout.lua b/lua/telescope/actions/layout.lua new file mode 100644 index 0000000..f40151f --- /dev/null +++ b/lua/telescope/actions/layout.lua @@ -0,0 +1,140 @@ +---@tag telescope.actions.layout + +---@brief [[ +--- The layout actions are actions to be used to change the layout of a picker. +---@brief ]] + +local action_state = require "telescope.actions.state" +local state = require "telescope.state" +local layout_strats = require "telescope.pickers.layout_strategies" + +local action_layout = {} + +--- Toggle preview window. +--- - Note: preview window can be toggled even if preview is set to false. +--- +--- This action is not mapped by default. +---@param prompt_bufnr number: The prompt bufnr +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 + picker.hidden_previewer = picker.previewer + picker.previewer = nil + elseif picker.hidden_previewer and not status.preview_win then + picker.previewer = picker.hidden_previewer + picker.hidden_previewer = nil + else + return + end + picker:full_layout_update() +end + +-- TODO IMPLEMENT (mentored project available, contact @l-kershaw) +action_layout.toggle_padding = function(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) + -- if padding ~= 0 + -- 1. Save `height` and `width` of picker + -- 2. Set both to `{padding = 0}` + -- else + -- 1. Lookup previous `height` and `width` of picker + -- 2. Set both to previous values + picker:full_layout_update() +end + +--- Toggles the `prompt_position` option between "top" and "bottom". +--- Checks if `prompt_position` is an option for the current layout. +--- +--- This action is not mapped by default. +---@param prompt_bufnr number: The prompt bufnr +action_layout.toggle_prompt_position = function(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) + picker.layout_config = picker.layout_config or {} + picker.layout_config[picker.layout_strategy] = picker.layout_config[picker.layout_strategy] or {} + -- flex layout is weird and needs handling separately + if picker.layout_strategy == "flex" then + picker.layout_config.flex.horizontal = picker.layout_config.flex.horizontal or {} + picker.layout_config.flex.vertical = picker.layout_config.flex.vertical or {} + local old_pos = picker.layout_config.flex[picker.__flex_strategy].prompt_position + local new_pos = old_pos == "top" and "bottom" or "top" + picker.layout_config[picker.__flex_strategy].prompt_position = new_pos + picker.layout_config.flex[picker.__flex_strategy].prompt_position = new_pos + picker:full_layout_update() + elseif layout_strats._configurations[picker.layout_strategy].prompt_position then + if picker.layout_config.prompt_position == "top" then + picker.layout_config.prompt_position = "bottom" + picker.layout_config[picker.layout_strategy].prompt_position = "bottom" + else + picker.layout_config.prompt_position = "top" + picker.layout_config[picker.layout_strategy].prompt_position = "top" + end + picker:full_layout_update() + end +end + +--- Toggles the `mirror` option between `true` and `false`. +--- Checks if `mirror` is an option for the current layout. +--- +--- This action is not mapped by default. +---@param prompt_bufnr number: The prompt bufnr +action_layout.toggle_mirror = function(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) + -- flex layout is weird and needs handling separately + if picker.layout_strategy == "flex" then + picker.layout_config.flex.horizontal = picker.layout_config.flex.horizontal or {} + picker.layout_config.flex.vertical = picker.layout_config.flex.vertical or {} + local new_mirror = not picker.layout_config.flex[picker.__flex_strategy].mirror + picker.layout_config[picker.__flex_strategy].mirror = new_mirror + picker.layout_config.flex[picker.__flex_strategy].mirror = new_mirror + picker:full_layout_update() + elseif layout_strats._configurations[picker.layout_strategy].mirror then + picker.layout_config = picker.layout_config or {} + local new_mirror = not picker.layout_config.mirror + picker.layout_config.mirror = new_mirror + picker.layout_config[picker.layout_strategy] = picker.layout_config[picker.layout_strategy] or {} + picker.layout_config[picker.layout_strategy].mirror = new_mirror + picker:full_layout_update() + end +end + +-- Helper function for `cycle_layout_next` and `cycle_layout_prev`. +local get_cycle_layout = function(dir) + return function(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) + if picker.__layout_index then + picker.__layout_index = ((picker.__layout_index + dir - 1) % #picker.__cycle_layout_list) + 1 + else + picker.__layout_index = 1 + end + local new_layout = picker.__cycle_layout_list[picker.__layout_index] + if type(new_layout) == "string" then + picker.layout_strategy = new_layout + picker.layout_config = nil + picker.previewer = picker.all_previewers and picker.all_previewers[1] or nil + elseif type(new_layout) == "table" then + picker.layout_strategy = new_layout.layout_strategy + picker.layout_config = new_layout.layout_config + picker.previewer = (new_layout.previewer == nil and picker.all_previewers[picker.current_previewer_index]) + or new_layout.previewer + else + error("Not a valid layout setup: " .. vim.inspect(new_layout) .. "\nShould be a string or a table") + end + + picker:full_layout_update() + end +end + +--- Cycles to the next layout in `cycle_layout_list`. +--- +--- This action is not mapped by default. +---@param prompt_bufnr number: The prompt bufnr +action_layout.cycle_layout_next = get_cycle_layout(1) + +--- Cycles to the previous layout in `cycle_layout_list`. +--- +--- This action is not mapped by default. +---@param prompt_bufnr number: The prompt bufnr +action_layout.cycle_layout_prev = get_cycle_layout(-1) + +return action_layout diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index 127307b..a96bc0b 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -189,6 +189,23 @@ append( append("layout_config", layout_config_defaults, layout_config_description) +append( + "cycle_layout_list", + { "horizontal", "vertical", { layout_strategy = "horizontal", previewer = false } }, + [[ + Determines the layouts to cycle through when using `actions.cycle_layout_next` + and `actions.cycle_layout_prev`. + Should be a list of "layout setups". + Each "layout setup" can take one of two forms: + 1. string
+ This is interpreted as the name of a `layout_strategy` + 2. table
+ A table with possible keys `layout_strategy`, `layout_config` and `previewer` + + Default: TODO + ]] +) + append( "winblend", 0, diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 76e6136..b626c46 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -104,6 +104,8 @@ function Picker:new(opts) layout_strategy = layout_strategy, layout_config = config.smarter_depth_2_extend(opts.layout_config or {}, config.values.layout_config or {}), + __cycle_layout_list = get_default(opts.cycle_layout_list, config.values.cycle_layout_list), + window = { winblend = get_default( opts.winblend, @@ -330,6 +332,7 @@ function Picker:find() end local results_win, results_opts, results_border_win = self:_create_window("", popup_opts.results, true) + local results_bufnr = a.nvim_win_get_buf(results_win) self.results_bufnr = results_bufnr @@ -572,9 +575,9 @@ function Picker:recalculate_layout() -- self.max_results = popup_opts.results.height end -local update_scroll = function(win, oldinfo, oldcursor, strategy, max_results) +local update_scroll = function(win, oldinfo, oldcursor, strategy, buf_maxline) if strategy == "ascending" then - vim.api.nvim_win_set_cursor(win, { max_results, 0 }) + vim.api.nvim_win_set_cursor(win, { buf_maxline, 0 }) vim.api.nvim_win_set_cursor(win, { oldinfo.topline, 0 }) vim.api.nvim_win_set_cursor(win, oldcursor) elseif strategy == "descending" then @@ -586,34 +589,15 @@ local update_scroll = function(win, oldinfo, oldcursor, strategy, max_results) end end -function Picker:toggle_preview() - local status = state.get_status(self.prompt_bufnr) - - if self.previewer and status.preview_win then - self.hidden_previewer = self.previewer - self.previewer = nil - elseif self.hidden_previewer and not status.preview_win then - self.previewer = self.hidden_previewer - self.hidden_previewer = nil - else - return - end - - local oldinfo = vim.fn.getwininfo(status.results_win)[1] - local oldcursor = vim.api.nvim_win_get_cursor(status.results_win) - +function Picker:full_layout_update() + local oldinfo = vim.fn.getwininfo(self.results_win)[1] + local oldcursor = vim.api.nvim_win_get_cursor(self.results_win) self:recalculate_layout() self:refresh_previewer() - update_scroll(status.results_win, oldinfo, oldcursor, self.sorting_strategy, self.max_results) -end -function Picker:toggle_padding() - -- if padding ~= 0 - -- 1. Save `height` and `width` of picker - -- 2. Set both to `{padding = 0}` - -- else - -- 1. Lookup previous `height` and `width` of picker - -- 2. Set both to previous values + -- update scrolled position + local buf_maxline = #vim.api.nvim_buf_get_lines(self.results_bufnr, 0, -1, false) + update_scroll(self.results_win, oldinfo, oldcursor, self.sorting_strategy, buf_maxline) end -- TODO: update multi-select with the correct tag name when available @@ -1310,13 +1294,7 @@ function pickers.on_resize_window(prompt_bufnr) local status = state.get_status(prompt_bufnr) local picker = status.picker - local oldinfo = vim.fn.getwininfo(status.results_win)[1] - local oldcursor = vim.api.nvim_win_get_cursor(status.results_win) - picker:recalculate_layout() - picker:refresh_previewer() - - -- update scrolled position - update_scroll(status.results_win, oldinfo, oldcursor, picker.sorting_strategy, picker.max_results) + picker:full_layout_update() end --- Get the prompt text without the prompt prefix. diff --git a/lua/telescope/pickers/layout_strategies.lua b/lua/telescope/pickers/layout_strategies.lua index 411b7fc..271fc72 100644 --- a/lua/telescope/pickers/layout_strategies.lua +++ b/lua/telescope/pickers/layout_strategies.lua @@ -707,8 +707,10 @@ layout_strategies.flex = make_documented_layout( local flip_lines = if_nil(layout_config.flip_lines, 20) if max_columns < flip_columns and max_lines > flip_lines then + self.__flex_strategy = "vertical" return layout_strategies.vertical(self, max_columns, max_lines, layout_config.vertical) else + self.__flex_strategy = "horizontal" return layout_strategies.horizontal(self, max_columns, max_lines, layout_config.horizontal) end end diff --git a/scripts/gendocs.lua b/scripts/gendocs.lua index 025e077..95f6ea6 100644 --- a/scripts/gendocs.lua +++ b/scripts/gendocs.lua @@ -18,6 +18,7 @@ docs.test = function() "./lua/telescope/actions/init.lua", "./lua/telescope/actions/state.lua", "./lua/telescope/actions/set.lua", + "./lua/telescope/actions/layout.lua", "./lua/telescope/actions/utils.lua", "./lua/telescope/actions/generate.lua", "./lua/telescope/previewers/init.lua",