diff --git a/README.md b/README.md index d48a525b78..fd0cef786e 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,6 @@ require('telescope').setup{ '--column', '--smart-case' }, - prompt_position = "bottom", prompt_prefix = "> ", selection_caret = "> ", entry_prefix = " ", @@ -146,7 +145,7 @@ require('telescope').setup{ selection_strategy = "reset", sorting_strategy = "descending", layout_strategy = "horizontal", - layout_defaults = { + layout_config = { horizontal = { mirror = false, }, @@ -159,10 +158,6 @@ require('telescope').setup{ generic_sorter = require'telescope.sorters'.get_generic_fuzzy_sorter, shorten_path = true, winblend = 0, - width = 0.75, - preview_cutoff = 120, - results_height = 1, - results_width = 0.8, border = {}, borderchars = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }, color_devicons = true, @@ -196,19 +191,14 @@ EOF | Keys | Description | Options | |------------------------|-------------------------------------------------------|----------------------------| -| `prompt_position` | Where the prompt should be located. | top/bottom | | `prompt_prefix` | What should the prompt prefix be. | string | | `selection_caret` | What should the selection caret be. | string | | `entry_prefix` | What should be shown in front of every entry. (current selection excluded) | string| | `initial_mode` | The initial mode when a prompt is opened. | insert/normal | | `sorting_strategy` | Where first selection should be located. | descending/ascending | | `layout_strategy` | How the telescope is drawn. | [supported layouts](https://github.com/nvim-telescope/telescope.nvim/wiki/Layouts) | -| `winblend` | How transparent is the telescope window should be. | NUM | -| `layout_defaults` | Extra settings for fine-tuning how your layout looks | [supported settings](https://github.com/nvim-telescope/telescope.nvim/wiki/Layouts#layout-defaults) | -| `width` | TODO | NUM | -| `preview_cutoff` | TODO | NUM | -| `results_height` | TODO | NUM | -| `results_width` | TODO | NUM | +| `winblend` | How transparent is the telescope window should be. | number | +| `layout_config` | Extra settings for fine-tuning how your layout looks | [supported settings](https://github.com/nvim-telescope/telescope.nvim/wiki/Layouts#layout-defaults) | | `borderchars` | The border chars, it gives border telescope window | dict | | `color_devicons` | Whether to color devicons or not | boolean | | `use_less` | Whether to use less with bat or less/cat if bat not installed | boolean | @@ -604,7 +594,6 @@ Picker:new{ selection_strategy = "reset", -- follow, reset, row border = {}, borderchars = {"─", "│", "─", "│", "┌", "┐", "┘", "└"}, - preview_cutoff = 120, default_selection_index = 1, -- Change the index of the initial selection row } ``` @@ -637,24 +626,37 @@ end ### Layout (display) -`Resolvable`: -1. 0 <= number < 1: - - This means total height as a percentage -2. 1 <= number: - - This means total height as a fixed number -3. function(picker, columns, lines): - - returns one of the above options - - `return max.min(110, max_rows * .5)` - -```lua -layout_strategies.horizontal = function(self, max_columns, max_lines) - local layout_config = validate_layout_config(self.layout_config or {}, { - width_padding = "How many cells to pad the width", - height_padding = "How many cells to pad the height", - preview_width = "(Resolvable): Determine preview width", - }) - ... -end +Layout can be configured by choosing a specific `layout_strategy` and +specifying a particular `layout_config` for that strategy. +For more details on available strategies and configuration options, +see `:help telescope.layout`. + +Some options for configuring sizes in layouts are "resolvable". +This means that they can take different forms, and will be interpreted differently according to which form they take. +For example, if we wanted to set the `width` of a picker using the `vertical` +layout strategy to 50% of the screen width, we would specify that width +as `0.5`, but if we wanted to specify the `width` to be exactly 80 +characters wide, we would specify it as `80`. +For more details on resolving sizes, see `:help telescope.resolve`. + +As an example, if we wanted to specify the layout strategy and width, +but only for this instance, we could do something like: +``` +:lua require('telescope.builtin').find_files({layout_strategy='vertical',layout_config={width=0.5}}) +``` +or if we wanted to change the width for every time we use the `vertical` +layout strategy, we could add the following to our `setup()` call: +``` +require('telescope').setup({ + defaults = { + layout_config = { + vertical = { width = 0.5 } + -- other layout configuration here + }, + -- other defaults configuration here + }, + -- other configuration values here +}) ``` ## Vim Commands diff --git a/doc/telescope.txt b/doc/telescope.txt index 2c5e712095..51c4bef651 100644 --- a/doc/telescope.txt +++ b/doc/telescope.txt @@ -19,12 +19,91 @@ telescope.setup({opts}) *telescope.setup()* Valid keys for {opts.defaults} + *telescope.defaults.default_mappings* + default_mappings: ~ + Not recommended to use except for advanced users. + + Will allow you to completely remove all of telescope's default maps and use your own. + + *telescope.defaults.entry_prefix* entry_prefix: ~ Prefix in front of each result entry. Current selection not included. Default: ' ' + *telescope.defaults.layout_config* + layout_config: ~ + Determines the default configuration values for layout strategies. + See |telescope.layout| for details of the configurations options for + each strategy. + + Allows setting defaults for all strategies as top level options and + for overriding for specific options. + For example, the default values below set the default width to 80% of + the screen width for all strategies except 'center', which has width + of 50% of the screen width. + + Default: { + center = { + preview_cutoff = 40 + }, + height = 0.9, + horizontal = { + preview_cutoff = 120, + prompt_position = "bottom" + }, + vertical = { + preview_cutoff = 40 + }, + width = 0.8 + } + + + *telescope.defaults.layout_strategy* + layout_strategy: ~ + Determines the default layout of Telescope pickers. + See |telescope.layout| for details of the available strategies. + + Default: 'horizontal' + + *telescope.defaults.mappings* + mappings: ~ + Your mappings to override telescope's default mappings. + + Format is: + { + mode = { ..keys } + } + + where {mode} is the one character letter for a mode ('i' for insert, 'n' for normal). + + For example: + + mappings = { + i = { + [""] = actions.close, + }, + } + + + To disable a keymap, put [map] = false + So, to not map "", just put + + ..., + [""] = false, + ..., + + Into your config. + + + otherwise, just set the mapping to the function that you want it to be. + + ..., + [""] = actions.select_default + ..., + + *telescope.defaults.prompt_prefix* prompt_prefix: ~ Will be shown in front of the prompt. @@ -883,6 +962,56 @@ action_state.get_current_picker({prompt_bufnr})*action_state.get_current_picker( +================================================================================ + *telescope.resolve* + +Provides "resolver functions" to allow more customisable inputs for options. + +resolver.resolve_height() *resolver.resolve_height()* + Converts input to a function that returns the height. The input must take + one of four forms: + 1. 0 <= number < 1 + This means total height as a percentage. + 2. 1 <= number + This means total height as a fixed number. + 3. function + Must have signature: function(self, max_columns, max_lines): number + 4. table of the form: {padding = `foo`}
where `foo` has one of the + previous three forms.
The height is then set to be the remaining + space after padding. For example, if the window has height 50, and the + input is {padding = 5}, the height returned will be `40 = 50 - 2*5` + + The returned function will have signature: function(self, max_columns, + max_lines): number + + + +resolver.resolve_width() *resolver.resolve_width()* + Converts input to a function that returns the width. The input must take + one of four forms: + 1. 0 <= number < 1 + This means total width as a percentage. + 2. 1 <= number + This means total width as a fixed number. + 3. function + Must have signature: function(self, max_columns, max_lines): number + 4. table of the form: {padding = `foo`}
where `foo` has one of the + previous three forms.
The width is then set to be the remaining + space after padding. For example, if the window has width 100, and the + input is {padding = 5}, the width returned will be `90 = 100 - 2*5` + + The returned function will have signature: function(self, max_columns, + max_lines): number + + + +resolver.win_option() *resolver.win_option()* + Win option always returns a table with preview, results, and prompt. It + handles many different ways. Some examples are as follows: + + + + ================================================================================ *telescope.previewers* @@ -1158,7 +1287,7 @@ Layout strategies are different functions to position telescope. All layout strategies are functions with the following signature: - function(picker, columns, lines) + function(picker, columns, lines, layout_config) -- Do some calculations here... return { preview = preview_configuration @@ -1168,99 +1297,165 @@ All layout strategies are functions with the following signature: end Parameters: ~ - - picker : A Picker object. (docs coming soon) - - columns : number Columns in the vim window - - lines : number Lines in the vim window - + - picker : A Picker object. (docs coming soon) + - columns : (number) Columns in the vim window + - lines : (number) Lines in the vim window + - layout_config : (table) The configuration values specific to the picker. -TODO: I would like to make these link to `telescope.layout_strategies.*`, but -it's not yet possible. -Available layout strategies include: - - horizontal: - - See |layout_strategies.horizontal| +This means you can create your own layout strategy if you want! Just be aware +for now that we may change some APIs or interfaces, so they may break if you +create your own. - - vertical: - - See |layout_strategies.vertical| +A good method for creating your own would be to copy one of the strategies that +most resembles what you want from +"./lua/telescope/pickers/layout_strategies.lua" in the telescope repo. - - flex: - - See |layout_strategies.flex| - -Available tweaks to the settings in layout defaults include (can be applied to -horizontal and vertical layouts): - - mirror (default is `false`): - - Flip the view of the current layout: - - If using horizontal: if `true`, swaps the location of the - results/prompt window and preview window - - If using vertical: if `true`, swaps the location of the results and - prompt windows - - - width_padding: - - How many cells to pad the width of Telescope's layout window - - - height_padding: - - How many cells to pad the height of Telescope's layout window - - - preview_width: - - Change the width of Telescope's preview window - - - scroll_speed: - - Change the scrolling speed of the previewer layout_strategies.horizontal() *layout_strategies.horizontal()* - Horizontal previewer - - +-------------+--------------+ - | | | - | Results | | - | | Preview | - | | | - +-------------| | - | Prompt | | - +-------------+--------------+ - + Horizontal layout has two columns, one for the preview and one for the + prompt and results. + + ┌──────────────────────────────────────────────────┐ + │ │ + │ ┌───────────────────┐┌───────────────────┐ │ + │ │ ││ │ │ + │ │ ││ │ │ + │ │ ││ │ │ + │ │ Results ││ │ │ + │ │ ││ Preview │ │ + │ │ ││ │ │ + │ │ ││ │ │ + │ └───────────────────┘│ │ │ + │ ┌───────────────────┐│ │ │ + │ │ Prompt ││ │ │ + │ └───────────────────┘└───────────────────┘ │ + │ │ + └──────────────────────────────────────────────────┘ + + `picker.layout_config` shared options: + - height: + - How tall to make Telescope's entire layout + - See |resolver.resolve_height()| + - mirror: Flip the location of the results/prompt and preview windows + - scroll_speed: The speed when scrolling through the previewer + - width: + - How wide to make Telescope's entire layout + - See |resolver.resolve_width()| + + `picker.layout_config` unique options: + - preview_cutoff: When columns are less than this value, the preview will be disabled + - preview_width: + - Change the width of Telescope's preview window + - See |resolver.resolve_width()| + - prompt_position: + - Where to place prompt window. + - Available Values: 'bottom', 'top' layout_strategies.center() *layout_strategies.center()* - Centered layout wih smaller default sizes (I think) + Centered layout with a combined block of the prompt and results aligned to + the middle of the screen. The preview window is then placed in the + remaining space above. Particularly useful for creating dropdown menus (see + |telescope.themes| and |themes.get_dropdown()|`). + + ┌──────────────────────────────────────────────────┐ + │ ┌────────────────────────────────────────┐ │ + │ | Preview | │ + │ | Preview | │ + │ └────────────────────────────────────────┘ │ + │ ┌────────────────────────────────────────┐ │ + │ | Prompt | │ + │ ├────────────────────────────────────────┤ │ + │ | Result | │ + │ | Result | │ + │ └────────────────────────────────────────┘ │ + │ │ + │ │ + │ │ + │ │ + └──────────────────────────────────────────────────┘ + + `picker.layout_config` shared options: + - height: + - How tall to make Telescope's entire layout + - See |resolver.resolve_height()| + - mirror: Flip the location of the results/prompt and preview windows + - scroll_speed: The speed when scrolling through the previewer + - width: + - How wide to make Telescope's entire layout + - See |resolver.resolve_width()| + + `picker.layout_config` unique options: + - preview_cutoff: When lines are less than this value, the preview will be disabled - +--------------+ - | Preview | - +--------------+ - | Prompt | - +--------------+ - | Result | - | Result | - | Result | - +--------------+ +layout_strategies.vertical() *layout_strategies.vertical()* + Vertical layout stacks the items on top of each other. Particularly useful + with thinner windows. + + ┌──────────────────────────────────────────────────┐ + │ │ + │ ┌────────────────────────────────────────┐ │ + │ | Preview | │ + │ | Preview | │ + │ | Preview | │ + │ └────────────────────────────────────────┘ │ + │ ┌────────────────────────────────────────┐ │ + │ | Result | │ + │ | Result | │ + │ └────────────────────────────────────────┘ │ + │ ┌────────────────────────────────────────┐ │ + │ | Prompt | │ + │ └────────────────────────────────────────┘ │ + │ │ + └──────────────────────────────────────────────────┘ + + `picker.layout_config` shared options: + - height: + - How tall to make Telescope's entire layout + - See |resolver.resolve_height()| + - mirror: Flip the location of the results/prompt and preview windows + - scroll_speed: The speed when scrolling through the previewer + - width: + - How wide to make Telescope's entire layout + - See |resolver.resolve_width()| + + `picker.layout_config` unique options: + - preview_cutoff: When lines are less than this value, the preview will be disabled + - preview_height: + - Change the height of Telescope's preview window + - See |resolver.resolve_height()| -layout_strategies.vertical() *layout_strategies.vertical()* - Vertical perviewer stacks the items on top of each other. +layout_strategies.flex() *layout_strategies.flex()* + Flex layout swaps between `horizontal` and `vertical` strategies based on + the window width + - Supports |layout_strategies.vertical| or |layout_strategies.horizontal| + features - +-----------------+ - | Previewer | - | Previewer | - | Previewer | - +-----------------+ - | Result | - | Result | - | Result | - +-----------------+ - | Prompt | - +-----------------+ + `picker.layout_config` shared options: + - height: + - How tall to make Telescope's entire layout + - See |resolver.resolve_height()| + - mirror: Flip the location of the results/prompt and preview windows + - scroll_speed: The speed when scrolling through the previewer + - width: + - How wide to make Telescope's entire layout + - See |resolver.resolve_width()| + `picker.layout_config` unique options: + - flip_columns: The number of columns required to move to horizontal mode + - flip_lines: The number of lines required to move to horizontal mode + - horizontal: Options to pass when switching to horizontal layout + - vertical: Options to pass when switching to vertical layout -layout_strategies.flex() *layout_strategies.flex()* - Swap between `horizontal` and `vertical` strategies based on the window - width - - Supports `vertical` or `horizontal` features - Uses: - - flip_columns - - flip_lines +layout_strategies.bottom_pane() *layout_strategies.bottom_pane()* + Bottom pane can be used to create layouts similar to "ivy". + + For an easy ivy configuration, see |themes.get_ivy()| diff --git a/doc/telescope_changelog.txt b/doc/telescope_changelog.txt new file mode 100644 index 0000000000..fd0a3edf7a --- /dev/null +++ b/doc/telescope_changelog.txt @@ -0,0 +1,44 @@ +================================================================================ + *telescope.changelog* + +# Changelog + + *telescope.changelog-823* + +Date: May 17, 2021 +PR: https://github.com/nvim-telescope/telescope.nvim/pull/823 + +A lot of new and breaking changes here as we work to clean up the way users +can configure how telescope's windows are configured. + +|telescope.setup()| has changed `layout_defaults` -> `layout_config`. + This makes it so that the setup and the pickers share the same key, + otherwise it is too confusing which key is for which. + + +`picker:find()` now has different values available for configuring the UI. + All configuration for the layout must be passed in the key: + `layout_config`. + + Previously, these keys were passed via `picker:find(opts)`, but should be + passed via `opts.layout_config` now. + - {height} + - {width} + - {prompt_position} + - {preview_cutoff} + + These keys are removed: + - {results_height}: This key is no longer valid. Instead, use `height` + and the corresponding `preview_*` options for the layout strategy to + get the correct results height. This simplifies the configuration + for many of the existing strategies. + + - {results_width}: This key actually never did anything. It was + leftover from some hacking that I had attempted before. Instead you + should be using something like the `preview_width` configuration + option for |layout_strategies.horizontal()| + + You should get error messages when you try and use any of the above keys now. + + + vim:tw=78:ts=8:ft=help:norl: diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index c62e21b7af..d57f97ace5 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -1,3 +1,5 @@ +local log = require('telescope.log') + -- Keep the values around between reloads _TelescopeConfigurationValues = _TelescopeConfigurationValues or {} @@ -35,6 +37,26 @@ local dedent = function(str, leave_indent) return str end +-- A function that creates an amended copy of the `base` table, +-- by replacing keys at "level 2" that match keys in "level 1" in `priority`, +-- and then performs a deep_extend. +-- May give unexpected results if used with tables of "depth" +-- greater than 2. +local smarter_depth_2_extend = function(priority, base) + local result = {} + for key, val in pairs(base) do + if type(val) ~= 'table' then + result[key] = first_non_null(priority[key],val) + else + result[key] = {} + for k, v in pairs(val) do + result[key][k] = first_non_null(priority[k],v) + end + end + end + result = vim.tbl_deep_extend("keep",priority,result) + return result +end local sorters = require('telescope.sorters') @@ -43,14 +65,47 @@ local sorters = require('telescope.sorters') local config = {} +config.smarter_depth_2_extend = smarter_depth_2_extend + config.values = _TelescopeConfigurationValues config.descriptions = {} -function config.set_defaults(defaults) - defaults = defaults or {} +-- A table of all the usual defaults for telescope. +-- Keys will be the name of the default, +-- values will be a list where: +-- - first entry is the value +-- - second entry is the description of the option +local telescope_defaults = { + sorting_strategy = { ... } +} + +-- @param user_defaults table: a table where keys are the names of options, +-- and values are the ones the user wants +-- @param tele_defaults table: (optional) a table containing all of the defaults +-- for telescope [defaults to `telescope_defaults`] +function config.set_defaults(user_defaults, tele_defaults) + user_defaults = user_defaults or {} + tele_defaults = tele_defaults or telescope_defaults + + if user_defaults.layout_default then + if user_defaults.layout_config == nil then + log.info("Using 'layout_default' in setup() is deprecated. Use 'layout_config' instead.") + user_defaults.layout_config = user_defaults.layout_default + else + error("Using 'layout_default' in setup() is deprecated. Remove this key and use 'layout_config' instead.") + end + end local function get(name, default_val) - return first_non_null(defaults[name], config.values[name], default_val) + if name == "layout_config" then + return smarter_depth_2_extend( + user_defaults[name] or {}, + vim.tbl_deep_extend("keep", + config.values[name] or {}, + default_val or {} + )) + end + return first_non_null(user_defaults[name], config.values[name], default_val) end local function set(name, default_val, description) @@ -63,107 +118,172 @@ function config.set_defaults(defaults) end end - set("sorting_strategy", "descending", [[ + for key, info in pairs(tele_defaults) do + set(key, info[1], info[2]) + end + + local M = {} + M.get = get + return M +end + +telescope_defaults["sorting_strategy"] = { "descending", [[ Determines the direction "better" results are sorted towards. Available options are: - "descending" (default) - - "ascending"]]) + - "ascending"]]} - set("selection_strategy", "reset", [[ +telescope_defaults["selection_strategy"] = { "reset", [[ Determines how the cursor acts after each sort iteration. Available options are: - "reset" (default) - "follow" - - "row"]]) + - "row"]]} - set("scroll_strategy", "cycle", [[ +telescope_defaults["scroll_strategy"] = {"cycle", [[ Determines what happens you try to scroll past view of the picker. Available options are: - "cycle" (default) - - "limit"]]) + - "limit"]]} + +telescope_defaults["layout_strategy"] = {"horizontal", [[ + Determines the default layout of Telescope pickers. + See |telescope.layout| for details of the available strategies. + + Default: 'horizontal']]} + +local layout_config_defaults = { + width = 0.8, + height = 0.9, + + horizontal = { + prompt_position = "bottom", + preview_cutoff = 120, + }, - set("layout_strategy", "horizontal") - set("layout_defaults", {}) + vertical = { + preview_cutoff = 40, + }, - set("width", 0.75) - set("winblend", 0) - set("prompt_position", "bottom") - set("preview_cutoff", 120) + center = { + preview_cutoff = 40, + }, +} - set("results_height", 1) - set("results_width", 0.8) +local layout_config_description = string.format([[ + Determines the default configuration values for layout strategies. + See |telescope.layout| for details of the configurations options for + each strategy. - set("prompt_prefix", "> ", [[ + Allows setting defaults for all strategies as top level options and + for overriding for specific options. + For example, the default values below set the default width to 80%% of + the screen width for all strategies except 'center', which has width + of 50%% of the screen width. + + Default: %s +]], vim.inspect(layout_config_defaults, { newline = "\n ", indent = " " })) + +telescope_defaults["layout_config"] = {layout_config_defaults, layout_config_description} + +telescope_defaults["winblend"] = {0} + +telescope_defaults["prompt_prefix"] = {"> ", [[ Will be shown in front of the prompt. - Default: '> ']]) - set("selection_caret", "> ", [[ + Default: '> ']]} +telescope_defaults["selection_caret"] = {"> ", [[ Will be shown in front of the selection. - Default: '> ']]) - set("entry_prefix", " ", [[ + Default: '> ']]} +telescope_defaults["entry_prefix"] = {" ", [[ Prefix in front of each result entry. Current selection not included. - Default: ' ']]) - set("initial_mode", "insert") + Default: ' ']]} +telescope_defaults["initial_mode"] = {"insert"} - set("border", {}) - set("borderchars", { '─', '│', '─', '│', '╭', '╮', '╯', '╰'}) +telescope_defaults["border"] = {{}} +telescope_defaults["borderchars"] = {{ '─', '│', '─', '│', '╭', '╮', '╯', '╰'}} - set("get_status_text", function(self) +telescope_defaults["get_status_text"] = {function(self) local xx = (self.stats.processed or 0) - (self.stats.filtered or 0) local yy = self.stats.processed or 0 if xx == 0 and yy == 0 then return "" end return string.format("%s / %s", xx, yy) - end) - - -- Builtin configuration - - -- List that will be executed. - -- Last argument will be the search term (passed in during execution) - set("vimgrep_arguments", - {'rg', '--color=never', '--no-heading', '--with-filename', '--line-number', '--column', '--smart-case'} - ) - set("use_less", true) - set("color_devicons", true) - - set("set_env", nil) - - -- TODO: Add motions to keybindings - - -- To disable a keymap, put [map] = false - -- So, to not map "", just put - -- - -- ..., - -- [""] = false, - -- ..., - -- - -- Into your config. - -- - -- Otherwise, just set the mapping to the function that you want it to be. - -- - -- ..., - -- [""] = actions.select_default - -- ..., - -- - set("mappings", {}) - set("default_mappings", nil) - - set("generic_sorter", sorters.get_generic_fuzzy_sorter) - set("prefilter_sorter", sorters.prefilter) - set("file_sorter", sorters.get_fuzzy_file) - - set("file_ignore_patterns", nil) - - set("file_previewer", function(...) return require('telescope.previewers').vim_buffer_cat.new(...) end) - set("grep_previewer", function(...) return require('telescope.previewers').vim_buffer_vimgrep.new(...) end) - set("qflist_previewer", function(...) return require('telescope.previewers').vim_buffer_qflist.new(...) end) - set("buffer_previewer_maker", function(...) return require('telescope.previewers').buffer_previewer_maker(...) end) -end + end} + +-- Builtin configuration + +-- List that will be executed. +-- Last argument will be the search term (passed in during execution) +telescope_defaults["vimgrep_arguments"] = { + {'rg', '--color=never', '--no-heading', '--with-filename', '--line-number', '--column', '--smart-case'} +} +telescope_defaults["use_less"] = {true} +telescope_defaults["color_devicons"] = {true} + +telescope_defaults["set_env"] = {nil} + +telescope_defaults["mappings"] = {{}, [[ +Your mappings to override telescope's default mappings. + +Format is: +{ + mode = { ..keys } +} + +where {mode} is the one character letter for a mode ('i' for insert, 'n' for normal). + +For example: + +mappings = { + i = { + [""] = actions.close, + }, +} + + +To disable a keymap, put [map] = false + So, to not map "", just put + + ..., + [""] = false, + ..., + + Into your config. + + +otherwise, just set the mapping to the function that you want it to be. + + ..., + [""] = actions.select_default + ..., +]]} + +telescope_defaults["default_mappings"] = {nil, [[ +Not recommended to use except for advanced users. + +Will allow you to completely remove all of telescope's default maps and use your own. +]]} + +telescope_defaults["generic_sorter"] = {sorters.get_generic_fuzzy_sorter} +telescope_defaults["prefilter_sorter"] = {sorters.prefilter} +telescope_defaults["file_sorter"] = {sorters.get_fuzzy_file} + +telescope_defaults["file_ignore_patterns"] = {nil} + +telescope_defaults["file_previewer"] = { + function(...) return require('telescope.previewers').vim_buffer_cat.new(...) end} +telescope_defaults["grep_previewer"] = { + function(...) return require('telescope.previewers').vim_buffer_vimgrep.new(...) end} +telescope_defaults["qflist_previewer"] = { + function(...) return require('telescope.previewers').vim_buffer_qflist.new(...) end} +telescope_defaults["buffer_previewer_maker"] = { + function(...) return require('telescope.previewers').buffer_previewer_maker(...) end} function config.clear_defaults() for k, _ in pairs(config.values) do diff --git a/lua/telescope/config/resolve.lua b/lua/telescope/config/resolve.lua index 464bffbcab..9a4bc5b384 100644 --- a/lua/telescope/config/resolve.lua +++ b/lua/telescope/config/resolve.lua @@ -1,3 +1,8 @@ +---@tag telescope.resolve + +---@brief [[ +--- Provides "resolver functions" to allow more customisable inputs for options. +---@brief ]] --[[ @@ -40,7 +45,7 @@ height = 3. function(picker, columns, lines) -> returns one of the above options - return max.min(110, max_rows * .5) + return math.min(110, max_rows * .5) if columns > 120 then return 110 @@ -88,46 +93,81 @@ That's the next step to scrolling. local get_default = require('telescope.utils').get_default local resolver = {} +local _resolve_map = {} -local _resolve_map = { - -- Booleans - [function(val) return val == false end] = function(selector, val) - return function(...) - return val - end - end, +-- Booleans +_resolve_map[function(val) return val == false end] = function(_, val) + return function(...) + return val + end +end - -- Percentages - [function(val) return type(val) == 'number' and val >= 0 and val < 1 end] = function(selector, val) - return function(...) - local selected = select(selector, ...) - return math.floor(val * selected) - end - end, +-- Percentages +_resolve_map[function(val) return type(val) == 'number' and val >= 0 and val < 1 end] = function(selector, val) + return function(...) + local selected = select(selector, ...) + return math.floor(val * selected) + end +end - -- Numbers - [function(val) return type(val) == 'number' and val >= 1 end] = function(selector, val) - return function(...) - local selected = select(selector, ...) - return math.min(val, selected) - end - end, +-- Numbers +_resolve_map[function(val) return type(val) == 'number' and val >= 1 end] = function(selector, val) + return function(...) + local selected = select(selector, ...) + return math.min(val, selected) + end +end +-- Tables TODO: +-- ... {70, max} - -- Tables TODO: - -- ... {70, max} +-- function: +-- Function must have same signature as get_window_layout +-- function(self, max_columns, max_lines): number +-- +-- Resulting number is used for this configuration value. +_resolve_map[function(val) return type(val) == 'function' end] = function(_, val) + return val +end +-- Add padding option +_resolve_map[function(val) return type(val) == 'table' and val['padding'] ~= nil end] = function(selector, val) + local resolve_pad = function(value) + for k, v in pairs(_resolve_map) do + if k(value) then + return v(selector, value) + end + end + + error('invalid configuration option for padding:' .. tostring(value)) + end + + return function(...) + local selected = select(selector, ...) + local padding = resolve_pad(val['padding']) + return math.floor(selected - 2 * padding(...)) + end +end - -- function: - -- Function must have same signature as get_window_layout - -- function(self, max_columns, max_lines): number - -- - -- Resulting number is used for this configuration value. - [function(val) return type(val) == 'function' end] = function(selector, val) - return val - end, -} +--- Converts input to a function that returns the height. +--- The input must take one of four forms: +--- 1. 0 <= number < 1
+--- This means total height as a percentage. +--- 2. 1 <= number
+--- This means total height as a fixed number. +--- 3. function
+--- Must have signature: +--- function(self, max_columns, max_lines): number +--- 4. table of the form: +--- {padding = `foo`}
+--- where `foo` has one of the previous three forms.
+--- The height is then set to be the remaining space after padding. +--- For example, if the window has height 50, and the input is {padding = 5}, +--- the height returned will be `40 = 50 - 2*5` +--- +--- The returned function will have signature: +--- function(self, max_columns, max_lines): number resolver.resolve_height = function(val) for k, v in pairs(_resolve_map) do if k(val) then @@ -138,6 +178,24 @@ resolver.resolve_height = function(val) error('invalid configuration option for height:' .. tostring(val)) end +--- Converts input to a function that returns the width. +--- The input must take one of four forms: +--- 1. 0 <= number < 1
+--- This means total width as a percentage. +--- 2. 1 <= number
+--- This means total width as a fixed number. +--- 3. function
+--- Must have signature: +--- function(self, max_columns, max_lines): number +--- 4. table of the form: +--- {padding = `foo`}
+--- where `foo` has one of the previous three forms.
+--- The width is then set to be the remaining space after padding. +--- For example, if the window has width 100, and the input is {padding = 5}, +--- the width returned will be `90 = 100 - 2*5` +--- +--- The returned function will have signature: +--- function(self, max_columns, max_lines): number resolver.resolve_width = function(val) for k, v in pairs(_resolve_map) do if k(val) then diff --git a/lua/telescope/deprecated.lua b/lua/telescope/deprecated.lua new file mode 100644 index 0000000000..e59781c8e0 --- /dev/null +++ b/lua/telescope/deprecated.lua @@ -0,0 +1,46 @@ + +local deprecated = {} + +deprecated.picker_window_options = function(opts) + local messages = {} + + -- Deprecated: PR:823, 2021/05/17 + -- Can be removed in a few weeks. + + if opts.width then + table.insert(messages, "'opts.width' is no longer valid. Please use 'layout_config.width' instead") + end + + if opts.height then + table.insert(messages, "'opts.height' is no longer valid. Please use 'layout_config.height' instead") + end + + if opts.results_height then + table.insert(messages, "'opts.results_height' is no longer valid. Please see ':help telescope.changelog-823'") + end + + if opts.results_width then + table.insert(messages, + "'opts.results_width' actually didn't do anything. Please see ':help telescope.changelog-823'" + ) + end + + if opts.prompt_position then + table.insert(messages, + "'opts.prompt_position' is no longer valid. Please use 'layout_config.prompt_position' instead." + ) + end + + if opts.preview_cutoff then + table.insert(messages, + "'opts.preview_cutoff' is no longer valid. Please use 'layout_config.preview_cutoff' instead." + ) + end + + if #messages > 0 then + table.insert(messages, 1, "Deprecated window options. Please see ':help telescope.changelog'") + vim.api.nvim_err_write(table.concat(messages, "\n \n ") .. "\n \nPress to continue\n") + end +end + +return deprecated diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 7613daf56c..33e5af273b 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -14,6 +14,7 @@ local actions = require('telescope.actions') local action_set = require('telescope.actions.set') local config = require('telescope.config') local debounce = require('telescope.debounce') +local deprecated = require('telescope.deprecated') local log = require('telescope.log') local mappings = require('telescope.mappings') local state = require('telescope.state') @@ -56,6 +57,8 @@ function Picker:new(opts) actions._clear() action_set._clear() + deprecated.picker_window_options(opts) + local layout_strategy = get_default(opts.layout_strategy, config.values.layout_strategy) local obj = setmetatable({ @@ -93,33 +96,13 @@ function Picker:new(opts) selection_strategy = get_default(opts.selection_strategy, config.values.selection_strategy), layout_strategy = layout_strategy, - layout_config = get_default( - opts.layout_config, - (config.values.layout_defaults or {})[layout_strategy] - ) or {}, + layout_config = vim.tbl_deep_extend("keep", opts.layout_config or {}, config.values.layout_config or {}), window = { - -- TODO: This won't account for different layouts... - -- TODO: If it's between 0 and 1, it's a percetnage. - -- TODO: If its's a single number, it's always that many columsn - -- TODO: If it's a list, of length 2, then it's a range of min to max? - height = get_default(opts.height, 0.8), - width = get_default(opts.width, config.values.width), - - get_preview_width = get_default(opts.preview_width, config.values.get_preview_width), - - results_width = get_default(opts.results_width, config.values.results_width), - results_height = get_default(opts.results_height, config.values.results_height), - winblend = get_default(opts.winblend, config.values.winblend), - prompt_position = get_default(opts.prompt_position, config.values.prompt_position), - - -- Border config border = get_default(opts.border, config.values.border), borderchars = get_default(opts.borderchars, config.values.borderchars), }, - - preview_cutoff = get_default(opts.preview_cutoff, config.values.preview_cutoff), }, self) obj.get_window_options = opts.get_window_options or p_window.get_window_options diff --git a/lua/telescope/pickers/layout_strategies.lua b/lua/telescope/pickers/layout_strategies.lua index 21994b3097..bde4c0dc61 100644 --- a/lua/telescope/pickers/layout_strategies.lua +++ b/lua/telescope/pickers/layout_strategies.lua @@ -7,7 +7,7 @@ --- All layout strategies are functions with the following signature: --- ---
----   function(picker, columns, lines)
+---   function(picker, columns, lines, layout_config)
 ---     -- Do some calculations here...
 ---     return {
 ---       preview = preview_configuration
@@ -17,129 +17,249 @@
 ---   end
 ---
 ---   Parameters: ~
----     - picker  : A Picker object. (docs coming soon)
----     - columns : number Columns in the vim window
----     - lines   : number Lines in the vim window
+---     - picker        : A Picker object. (docs coming soon)
+---     - columns       : (number) Columns in the vim window
+---     - lines         : (number) Lines in the vim window
+---     - layout_config : (table) The configuration values specific to the picker.
 ---
 --- 
--- ---- TODO: I would like to make these link to `telescope.layout_strategies.*`, ---- but it's not yet possible. +--- This means you can create your own layout strategy if you want! Just be aware +--- for now that we may change some APIs or interfaces, so they may break if you create +--- your own. --- ---- Available layout strategies include: ---- - horizontal: ---- - See |layout_strategies.horizontal| +--- A good method for creating your own would be to copy one of the strategies that most +--- resembles what you want from "./lua/telescope/pickers/layout_strategies.lua" in the +--- telescope repo. --- ---- - vertical: ---- - See |layout_strategies.vertical| ---- ---- - flex: ---- - See |layout_strategies.flex| ---- ---- Available tweaks to the settings in layout defaults include ---- (can be applied to horizontal and vertical layouts): ---- - mirror (default is `false`): ---- - Flip the view of the current layout: ---- - If using horizontal: if `true`, swaps the location of the ---- results/prompt window and preview window ---- - If using vertical: if `true`, swaps the location of the results and ---- prompt windows ---- ---- - width_padding: ---- - How many cells to pad the width of Telescope's layout window ---- ---- - height_padding: ---- - How many cells to pad the height of Telescope's layout window ---- ---- - preview_width: ---- - Change the width of Telescope's preview window ---- ---- - scroll_speed: ---- - Change the scrolling speed of the previewer ---@brief ]] -local config = require('telescope.config') -local resolve = require("telescope.config.resolve") - +local resolve = require('telescope.config.resolve') local p_window = require('telescope.pickers.window') --- Check if there are any borders. Right now it's a little raw as --- there are a few things that contribute to the border -local is_borderless = function(opts) - return opts.window.border == false +local get_border_size = function(opts) + if opts.window.border == false then + return 0 + end + + return 1 +end + +local layout_strategies = {} +layout_strategies._configurations = {} + +--@param strategy_config table: table with keys for each option for a strategy +--@return table: table with keys for each option (for this strategy) and with keys for each layout_strategy +local get_valid_configuration_keys = function(strategy_config) + local valid_configuration_keys = { + -- There are a few keys we should say are valid to start with. + preview_cutoff = true, + } + + for key in pairs(strategy_config) do + valid_configuration_keys[key] = true + end + + for name in pairs(layout_strategies) do + valid_configuration_keys[name] = true + end + + return valid_configuration_keys end +--@param strategy_name string: the name of the layout_strategy we are validating for +--@param configuration table: table with keys for each option available +--@param values table: table containing all of the non-default options we want to set +--@param default_layout_config: table with the default values to configure layouts +--@return table: table containing the combined options (defaults and non-defaults) +local function validate_layout_config(strategy_name, configuration, values, default_layout_config) + assert(strategy_name, "It is required to have a strategy name for validation.") + local valid_configuration_keys = get_valid_configuration_keys(configuration) + + -- If no default_layout_config provided, check Telescope's config values + default_layout_config = default_layout_config or require('telescope.config').values.layout_config + + local result = {} + local get_value = function(k) + -- skip "private" items + if string.sub(k, 1, 1) == "_" then return end + + local val + -- Prioritise options that are specific to this strategy + if values[strategy_name] ~= nil and values[strategy_name][k] ~= nil then + val = values[strategy_name][k] + end + + -- Handle nested layout config values + if layout_strategies[k] + and strategy_name ~= k + and type(val) == 'table' then + val = vim.tbl_deep_extend("force", default_layout_config[k], val) + end + + if val == nil and values[k] ~= nil then + val = values[k] + end + + if val == nil then + if default_layout_config[strategy_name] ~= nil + and default_layout_config[strategy_name][k] ~= nil then + val = default_layout_config[strategy_name][k] + else + val = default_layout_config[k] + end + end -local function validate_layout_config(options, values) - for k, _ in pairs(options) do - if not values[k] then - error(string.format( - "Unsupported layout_config key: %s\n%s", - k, - vim.inspect(values) + return val + end + + -- Always set the values passed first. + for k in pairs(values) do + if not valid_configuration_keys[k] then + -- TODO: At some popint we'll move to error here, + -- but it's a bit annoying to just straight up crash everyone's stuff. + vim.api.nvim_err_writeln(string.format( + "Unsupported layout_config key for the %s strategy: %s\n%s", + strategy_name, k, vim.inspect(values) )) end + + result[k] = get_value(k) + end + + -- And then set other valid keys via "inheritance" style extension + for k in pairs(valid_configuration_keys) do + if result[k] == nil then + result[k] = get_value(k) + end end - return options + return result end -local layout_strategies = {} +-- List of options that are shared by more than one layout. +local shared_options = { + width = { "How wide to make Telescope's entire layout", "See |resolver.resolve_width()|" }, + height = { "How tall to make Telescope's entire layout", "See |resolver.resolve_height()|" }, + mirror = "Flip the location of the results/prompt and preview windows", + scroll_speed = "The speed when scrolling through the previewer", +} + +-- Used for generating vim help documentation. +layout_strategies._format = function(name) + local strategy_config = layout_strategies._configurations[name] + if vim.tbl_isempty(strategy_config) then + return {} + end + + local results = {"
", "`picker.layout_config` shared options:"}
+
+  local strategy_keys = vim.tbl_keys(strategy_config)
+  table.sort(strategy_keys, function(a, b)
+    return a < b
+  end)
+
+  local add_value = function(k, val)
+    if type(val) == 'string' then
+      table.insert(results, string.format('  - %s: %s', k, val))
+    elseif type(val) == 'table' then
+      table.insert(results, string.format('  - %s:', k))
+      for _, line in ipairs(val) do
+        table.insert(results, string.format('    - %s', line))
+      end
+    else
+      error("Unknown type:" .. type(val))
+    end
+  end
+
+  for _, k in ipairs(strategy_keys) do
+    if shared_options[k] then
+      add_value(k, strategy_config[k])
+    end
+  end
+
+  table.insert(results, "")
+  table.insert(results, "`picker.layout_config` unique options:")
 
---- Horizontal previewer
+  for _, k in ipairs(strategy_keys) do
+    if not shared_options[k] then
+      add_value(k, strategy_config[k])
+    end
+  end
+
+  table.insert(results, "
") + return results +end + +--@param name string: the name to be assigned to the layout +--@param layout_config table: table where keys are the available options for the layout +--@param layout function: function with signature +-- function(self, max_columns, max_lines, layout_config): table +-- the returned table is the sizing and location information for the parts of the picker +--@retun function: wrapped function that inputs a validated layout_config into the `layout` function +local function make_documented_layout(name, layout_config, layout) + -- Save configuration data to be used by documentation + layout_strategies._configurations[name] = layout_config + + -- Return new function that always validates configuration + return function(self, max_columns, max_lines, override_layout) + return layout( + self, + max_columns, + max_lines, + validate_layout_config( + name, layout_config, vim.tbl_deep_extend("keep", override_layout or {}, self.layout_config or {}) + ) + ) + end +end + + +--- Horizontal layout has two columns, one for the preview +--- and one for the prompt and results. --- ---
----   +-------------+--------------+
----   |             |              |
----   |   Results   |              |
----   |             |    Preview   |
----   |             |              |
----   +-------------|              |
----   |   Prompt    |              |
----   +-------------+--------------+
+--- ┌──────────────────────────────────────────────────┐
+--- │                                                  │
+--- │    ┌───────────────────┐┌───────────────────┐    │
+--- │    │                   ││                   │    │
+--- │    │                   ││                   │    │
+--- │    │                   ││                   │    │
+--- │    │      Results      ││                   │    │
+--- │    │                   ││      Preview      │    │
+--- │    │                   ││                   │    │
+--- │    │                   ││                   │    │
+--- │    └───────────────────┘│                   │    │
+--- │    ┌───────────────────┐│                   │    │
+--- │    │      Prompt       ││                   │    │
+--- │    └───────────────────┘└───────────────────┘    │
+--- │                                                  │
+--- └──────────────────────────────────────────────────┘
 --- 
-layout_strategies.horizontal = function(self, max_columns, max_lines) - local layout_config = validate_layout_config(self.layout_config or {}, { - width_padding = "How many cells to pad the width", - height_padding = "How many cells to pad the height", - preview_width = "(Resolvable): Determine preview width", - mirror = "Flip the location of the results/prompt and preview windows", - scroll_speed = "The speed when scrolling through the previewer", - }) - +---@eval { ["description"] = require('telescope.pickers.layout_strategies')._format("horizontal") } +--- +layout_strategies.horizontal = make_documented_layout('horizontal', vim.tbl_extend("error", shared_options, { + preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|", }, + preview_cutoff = "When columns are less than this value, the preview will be disabled", + prompt_position = { "Where to place prompt window.", "Available Values: 'bottom', 'top'" }, +}), function(self, max_columns, max_lines, layout_config) local initial_options = p_window.get_initial_window_options(self) local preview = initial_options.preview local results = initial_options.results local prompt = initial_options.prompt - -- TODO: Test with 120 width terminal - -- TODO: Test with self.width - local width_padding = resolve.resolve_width(layout_config.width_padding or function(_, cols) - if cols < self.preview_cutoff then - return 2 - elseif cols < 150 then - return 5 - else - return 10 - end - end)(self, max_columns, max_lines) - local picker_width = max_columns - 2 * width_padding + local width_opt = layout_config.width + local picker_width = resolve.resolve_width(width_opt)(self, max_columns, max_lines) + local width_padding = math.floor((max_columns - picker_width)/2) - local height_padding = resolve.resolve_height(layout_config.height_padding or function(_, _, lines) - if lines < 40 then - return 4 - else - return math.floor(0.1 * lines) - end - end)(self, max_columns, max_lines) - local picker_height = max_lines - 2 * height_padding + local height_opt = layout_config.height + local picker_height = resolve.resolve_height(height_opt)(self, max_columns, max_lines) + local height_padding = math.floor((max_lines - picker_height)/2) - if self.previewer then + if self.previewer and max_columns >= layout_config.preview_cutoff then preview.width = resolve.resolve_width(layout_config.preview_width or function(_, cols) - if not self.previewer or cols < self.preview_cutoff then - return 0 - elseif cols < 150 then + if cols < 150 then return math.floor(cols * 0.4) elseif cols < 200 then return 80 @@ -175,14 +295,14 @@ layout_strategies.horizontal = function(self, max_columns, max_lines) end preview.line = height_padding - if self.window.prompt_position == "top" then + if layout_config.prompt_position == "top" then prompt.line = height_padding results.line = prompt.line + prompt.height + 2 - elseif self.window.prompt_position == "bottom" then + elseif layout_config.prompt_position == "bottom" then results.line = height_padding prompt.line = results.line + results.height + 2 else - error("Unknown prompt_position: " .. self.window.prompt_position) + error("Unknown prompt_position: " .. tostring(self.window.prompt_position) .. "\n" .. vim.inspect(layout_config)) end return { @@ -190,33 +310,54 @@ layout_strategies.horizontal = function(self, max_columns, max_lines) results = results, prompt = prompt } -end +end) ---- Centered layout wih smaller default sizes (I think) +--- Centered layout with a combined block of the prompt +--- and results aligned to the middle of the screen. +--- The preview window is then placed in the remaining space above. +--- Particularly useful for creating dropdown menus +--- (see |telescope.themes| and |themes.get_dropdown()|`). --- ---
----    +--------------+
----    |    Preview   |
----    +--------------+
----    |    Prompt    |
----    +--------------+
----    |    Result    |
----    |    Result    |
----    |    Result    |
----    +--------------+
+--- ┌──────────────────────────────────────────────────┐
+--- │    ┌────────────────────────────────────────┐    │
+--- │    |                 Preview                |    │
+--- │    |                 Preview                |    │
+--- │    └────────────────────────────────────────┘    │
+--- │    ┌────────────────────────────────────────┐    │
+--- │    |                 Prompt                 |    │
+--- │    ├────────────────────────────────────────┤    │
+--- │    |                 Result                 |    │
+--- │    |                 Result                 |    │
+--- │    └────────────────────────────────────────┘    │
+--- │                                                  │
+--- │                                                  │
+--- │                                                  │
+--- │                                                  │
+--- └──────────────────────────────────────────────────┘
 --- 
-layout_strategies.center = function(self, columns, lines) +---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("center") } +--- +layout_strategies.center = make_documented_layout("center", vim.tbl_extend("error", shared_options, { + preview_cutoff = "When lines are less than this value, the preview will be disabled", +}), function(self, max_columns, max_lines,layout_config) local initial_options = p_window.get_initial_window_options(self) local preview = initial_options.preview local results = initial_options.results local prompt = initial_options.prompt - -- This sets the height/width for the whole layout - local height = resolve.resolve_height(self.window.results_height)(self, columns, lines) - local width = resolve.resolve_width(self.window.width)(self, columns, lines) + -- This sets the width for the whole layout + local width_opt = layout_config.width + local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines) - local max_results = (height > lines and lines or height) - local max_width = (width > columns and columns or width) + -- This sets the number of results displayed + local res_height_opt = layout_config.height + local res_height = resolve.resolve_height(res_height_opt)(self, max_columns, max_lines) + + local max_results = (res_height > max_lines and max_lines or res_height) + local max_width = (width > max_columns and max_columns or width) + + local bs = get_border_size(self) prompt.height = 1 results.height = max_results @@ -225,87 +366,84 @@ layout_strategies.center = function(self, columns, lines) results.width = max_width preview.width = max_width - -- border size - local bs = 1 - if is_borderless(self) then - bs = 0 - end - - prompt.line = (lines / 2) - ((max_results + (bs * 2)) / 2) + -- Align the prompt and results so halfway up the screen is + -- in the middle of this combined block + prompt.line = (max_lines / 2) - ((max_results + (bs * 2)) / 2) results.line = prompt.line + 1 + (bs) preview.line = 1 - preview.height = math.floor(prompt.line - (2 + bs)) - if not self.previewer or columns < self.preview_cutoff then + if self.previewer and max_lines >= layout_config.preview_cutoff then + preview.height = math.floor(prompt.line - (2 + bs)) + else preview.height = 0 end - results.col = math.ceil((columns / 2) - (width / 2) - bs) + results.col = math.ceil((max_columns / 2) - (width / 2) - bs) prompt.col = results.col preview.col = results.col return { - preview = self.previewer and preview.width > 0 and preview, + preview = self.previewer and preview.height > 0 and preview, results = results, prompt = prompt } -end +end) ---- Vertical perviewer stacks the items on top of each other. +--- Vertical layout stacks the items on top of each other. +--- Particularly useful with thinner windows. --- ---
----    +-----------------+
----    |    Previewer    |
----    |    Previewer    |
----    |    Previewer    |
----    +-----------------+
----    |     Result      |
----    |     Result      |
----    |     Result      |
----    +-----------------+
----    |     Prompt      |
----    +-----------------+
+--- ┌──────────────────────────────────────────────────┐
+--- │                                                  │
+--- │    ┌────────────────────────────────────────┐    │
+--- │    |                 Preview                |    │
+--- │    |                 Preview                |    │
+--- │    |                 Preview                |    │
+--- │    └────────────────────────────────────────┘    │
+--- │    ┌────────────────────────────────────────┐    │
+--- │    |                 Result                 |    │
+--- │    |                 Result                 |    │
+--- │    └────────────────────────────────────────┘    │
+--- │    ┌────────────────────────────────────────┐    │
+--- │    |                 Prompt                 |    │
+--- │    └────────────────────────────────────────┘    │
+--- │                                                  │
+--- └──────────────────────────────────────────────────┘
 --- 
-layout_strategies.vertical = function(self, max_columns, max_lines) - local layout_config = validate_layout_config(self.layout_config or {}, { - width_padding = "How many cells to pad the width", - height_padding = "How many cells to pad the height", - preview_height = "(Resolvable): Determine preview height", - mirror = "Flip the locations of the results and prompt windows", - scroll_speed = "The speed when scrolling through the previewer", - }) +---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("vertical") } +--- +layout_strategies.vertical = make_documented_layout("vertical", vim.tbl_extend("error", shared_options, { + preview_cutoff = "When lines are less than this value, the preview will be disabled", + preview_height = { "Change the height of Telescope's preview window", "See |resolver.resolve_height()|" }, +}), function(self, max_columns, max_lines, layout_config) local initial_options = p_window.get_initial_window_options(self) local preview = initial_options.preview local results = initial_options.results local prompt = initial_options.prompt - local width_padding = resolve.resolve_width( - layout_config.width_padding or math.ceil((1 - self.window.width) * 0.5 * max_columns) - )(self, max_columns, max_lines) + local width_opt = layout_config.width + local picker_width = resolve.resolve_width(width_opt)(self,max_columns,max_lines) + local width_padding = math.floor((max_columns - picker_width)/2) - local width = max_columns - width_padding * 2 - if not self.previewer then - preview.width = 0 + local height_opt = layout_config.height + local picker_height = resolve.resolve_height(height_opt)(self,max_columns,max_lines) + local height_padding = math.floor((max_lines - picker_height)/2) + + if self.previewer and max_lines >= layout_config.preview_cutoff then + preview.width = picker_width else - preview.width = width + preview.width = 0 end - results.width = width - prompt.width = width - - -- Height - local height_padding = math.max( - 1, - resolve.resolve_height(layout_config.height_padding or 3)(self, max_columns, max_lines) - ) - local picker_height = max_lines - 2 * height_padding + results.width = picker_width + prompt.width = picker_width local preview_total = 0 preview.height = 0 - if self.previewer then + if self.previewer and max_lines >= layout_config.preview_cutoff then preview.height = resolve.resolve_height( - layout_config.preview_height or (max_lines - 15) + layout_config.preview_height or 0.5 )(self, max_columns, picker_height) preview_total = preview.height + 2 @@ -332,36 +470,38 @@ layout_strategies.vertical = function(self, max_columns, max_lines) end return { - preview = self.previewer and preview.width > 0 and preview, + preview = self.previewer and preview.height > 0 and preview, results = results, prompt = prompt } -end +end) ---- Swap between `horizontal` and `vertical` strategies based on the window width ---- - Supports `vertical` or `horizontal` features +--- Flex layout swaps between `horizontal` and `vertical` strategies based on the window width +--- - Supports |layout_strategies.vertical| or |layout_strategies.horizontal| features --- ---- Uses: ---- - flip_columns ---- - flip_lines -layout_strategies.flex = function(self, max_columns, max_lines) - local layout_config = self.layout_config or {} - +---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("flex") } +--- +layout_strategies.flex = make_documented_layout('flex', vim.tbl_extend("error", shared_options, { + flip_columns = "The number of columns required to move to horizontal mode", + flip_lines = "The number of lines required to move to horizontal mode", + vertical = "Options to pass when switching to vertical layout", + horizontal = "Options to pass when switching to horizontal layout", +}), function(self, max_columns, max_lines, layout_config) local flip_columns = layout_config.flip_columns or 100 local flip_lines = layout_config.flip_lines or 20 if max_columns < flip_columns and max_lines > flip_lines then - -- TODO: This feels a bit like a hack.... cause you wouldn't be able to pass this to flex easily. - self.layout_config = (config.values.layout_defaults or {})['vertical'] - return layout_strategies.vertical(self, max_columns, max_lines) + return layout_strategies.vertical(self, max_columns, max_lines, layout_config.vertical) else - self.layout_config = (config.values.layout_defaults or {})['horizontal'] - return layout_strategies.horizontal(self, max_columns, max_lines) + return layout_strategies.horizontal(self, max_columns, max_lines, layout_config.horizontal) end -end +end) -layout_strategies.current_buffer = function(self, _, _) - local initial_options = self:_get_initial_window_options() +layout_strategies.current_buffer = make_documented_layout('current_buffer', { + -- No custom options. + -- height, width ignored +}, function(self, _, _, _) + local initial_options = p_window.get_initial_window_options(self) local window_width = vim.api.nvim_win_get_width(0) local window_height = vim.api.nvim_win_get_height(0) @@ -414,19 +554,20 @@ layout_strategies.current_buffer = function(self, _, _) results = results, prompt = prompt, } -end - -layout_strategies.bottom_pane = function(self, max_columns, max_lines) - local layout_config = validate_layout_config(self.layout_config or {}, { - height = "The height of the layout", - }) +end) +--- Bottom pane can be used to create layouts similar to "ivy". +--- +--- For an easy ivy configuration, see |themes.get_ivy()| +layout_strategies.bottom_pane = make_documented_layout('bottom_pane', vim.tbl_extend("error", shared_options, { + -- No custom options... +}), function(self, max_columns, max_lines, layout_config) local initial_options = p_window.get_initial_window_options(self) local results = initial_options.results local prompt = initial_options.prompt local preview = initial_options.preview - local result_height = layout_config.height or 25 + local result_height = resolve.resolve_height(layout_config.height)(self,max_columns,max_lines) or 25 local prompt_width = max_columns local col = 0 @@ -477,6 +618,8 @@ layout_strategies.bottom_pane = function(self, max_columns, max_lines) width = result_width, }), } -end +end) + +layout_strategies._validate_layout_config = validate_layout_config return layout_strategies diff --git a/lua/telescope/themes.lua b/lua/telescope/themes.lua index f2af1a7f7b..939f79aa5a 100644 --- a/lua/telescope/themes.lua +++ b/lua/telescope/themes.lua @@ -27,16 +27,26 @@ function themes.get_dropdown(opts) opts = opts or {} local theme_opts = { - -- WIP: Decide on keeping these names or not. theme = "dropdown", - sorting_strategy = "ascending", - layout_strategy = "center", results_title = false, preview_title = "Preview", - preview_cutoff = 1, -- Preview should always show (unless previewer = false) - width = 80, - results_height = 15, + + sorting_strategy = "ascending", + layout_strategy = "center", + layout_config = { + preview_cutoff = 1, -- Preview should always show (unless previewer = false) + + width = function(_, max_columns, _) + return math.min(max_columns - 3, 80) + end, + + height = function(_, _, max_lines) + return math.min(max_lines - 4, 15) + end, + }, + + border = true, borderchars = { { "─", "│", "─", "│", "╭", "╮", "╯", "╰"}, prompt = {"─", "│", " ", "│", "╭", "╮", "│", "│"}, diff --git a/lua/tests/automated/layout_strategies_spec.lua b/lua/tests/automated/layout_strategies_spec.lua new file mode 100644 index 0000000000..a4d2c50e15 --- /dev/null +++ b/lua/tests/automated/layout_strategies_spec.lua @@ -0,0 +1,92 @@ +-- local tester = require('telescope.pickers._test') +local config = require('telescope.config') +local resolve = require('telescope.config.resolve') +local layout_strats = require('telescope.pickers.layout_strategies') + +local validate_layout_config = layout_strats._validate_layout_config + +local eq = assert.are.same + +describe('layout_strategies', function() + it('should have validator', function() + assert(validate_layout_config, "Has validator") + end) + + local test_height = function(should, output, input, opts) + opts = opts or {} + + local max_columns, max_lines = opts.max_columns or 100, opts.max_lines or 100 + it(should, function() + local layout_config = validate_layout_config("horizontal", { height = true }, { height = input }) + + eq(output, resolve.resolve_height(layout_config.height)({}, max_columns, max_lines)) + end) + end + + test_height('should handle numbers', 10, 10) + + test_height('should handle percentage: 100', 10, 0.1, { max_lines = 100 }) + test_height('should handle percentage: 110', 11, 0.1, { max_lines = 110 }) + + test_height('should call functions: simple', 5, function() return 5 end) + test_height('should call functions: percentage', 15, function(_, _, lines) return 0.1 * lines end, { max_lines = 150 }) + + local test_defaults_key = function(should, key, strat, output, ours, theirs, override) + ours = ours or {} + theirs = theirs or {} + override = override or {} + + it(should, function() + config.clear_defaults() + config.set_defaults({layout_config=theirs}, {layout_config={ours,'description'}}) + local layout_config = validate_layout_config(strat, layout_strats._configurations[strat], override) + eq(output, layout_config[key]) + end) + end + + test_defaults_key("should use ours if theirs and override don't give the key", + 'height','horizontal',50, + {height=50}, {width=100}, {width=120} + ) + + test_defaults_key("should use ours if theirs and override don't give the key for this strategy", + 'height','horizontal',50, + {height=50}, {vertical={height=100}}, {vertical={height=120}} + ) + + test_defaults_key("should use theirs if override doesn't give the key", + 'height','horizontal',100, + {height=50}, {height=100}, {width=120} + ) + + test_defaults_key("should use override if key given", + 'height','horizontal',120, + {height=50}, {height=100}, {height=120} + ) + + test_defaults_key("should use override if key given for this strategy", + 'height','horizontal',120, + {height=50}, {height=100}, {horizontal={height=120}} + ) + + test_defaults_key("should use theirs if override doesn't give key (even if ours has strategy specific)", + 'height','horizontal',100, + {horizontal={height=50}}, {height=100}, {width=120} + ) + + test_defaults_key("should use override (even if ours has strategy specific)", + 'height','horizontal',120, + {horizontal={height=50}}, {height=100}, {height=120} + ) + + test_defaults_key("should use override (even if theirs has strategy specific)", + 'height','horizontal',120, + {height=50}, {horizontal={height=100}}, {height=120} + ) + + test_defaults_key("should use override (even if ours and theirs have strategy specific)", + 'height','horizontal',120, + {horizontal={height=50}}, {horizontal={height=100}}, {height=120} + ) + +end) diff --git a/lua/tests/automated/pickers/find_files_spec.lua b/lua/tests/automated/pickers/find_files_spec.lua index b3af1d6f79..b01dc51acf 100644 --- a/lua/tests/automated/pickers/find_files_spec.lua +++ b/lua/tests/automated/pickers/find_files_spec.lua @@ -35,8 +35,11 @@ describe('builtin.find_files', function() }, vim.tbl_extend("force", { disable_devicons = true, sorter = require('telescope.sorters').get_fzy_sorter(), - results_height = max_results, layout_strategy = 'center', + layout_config = { + height = max_results, + width = 0.9, + }, }, vim.fn.json_decode([==[%s]==]))) ]], vim.fn.json_encode(configuration))) end) @@ -57,8 +60,11 @@ describe('builtin.find_files', function() }, vim.tbl_extend("force", { disable_devicons = true, sorter = require('telescope.sorters').get_fzy_sorter(), - results_height = 5, layout_strategy = 'center', + layout_config = { + height = max_results, + width = 0.9, + }, }, vim.fn.json_decode([==[%s]==]))) ]], expected, vim.fn.json_encode(configuration))) end) diff --git a/scripts/gendocs.lua b/scripts/gendocs.lua index 53423f278f..cd53a30035 100644 --- a/scripts/gendocs.lua +++ b/scripts/gendocs.lua @@ -1,4 +1,5 @@ -- Setup telescope with defaults +if RELOAD then RELOAD('telescope') end require('telescope').setup() local docgen = require('docgen') @@ -15,6 +16,7 @@ docs.test = function() "./lua/telescope/actions/state.lua", "./lua/telescope/actions/set.lua", "./lua/telescope/previewers/init.lua", + "./lua/telescope/config/resolve.lua", "./lua/telescope/themes.lua", }