feat: Consistent and sensible layout_config (#922)

* feat: Consistent and sensible layout_config

* [docgen] Update doc/telescope.txt
skip-checks: true

* [WIP]: Thu 17 Jun 2021 03:36:44 PM EDT

* [WIP]: Thu 17 Jun 2021 03:38:11 PM EDT

* layout_default -> layout_defaults

* remove options from bug repot

* Conni2461 suggestions: part 1

* [docgen] Update doc/telescope.txt
skip-checks: true

* Conni2461 suggestions: part 2

* [docgen] Update doc/telescope.txt
skip-checks: true

* Linting

* Improve deprecation checks

- Move `layout_defaults` handling to `deprecated.lua`
- Check for "layout keys" outside of `layout_config` on `setup`

* fixup: Just add a few more words

Co-authored-by: Luke Kershaw <35707277+l-kershaw@users.noreply.github.com>
Co-authored-by: Github Actions <actions@github>
This commit is contained in:
TJ DeVries
2021-07-01 02:41:58 -07:00
committed by GitHub
parent e5bd4963da
commit 5a53ec5c2f
16 changed files with 1300 additions and 529 deletions

View File

@@ -37,7 +37,7 @@
--- mappings = {
--- i = {
--- ["<c-d>"] = require("telescope.actions").delete_buffer,
--- -- or right hand side can also be a the name of the action as string
--- -- or right hand side can also be the name of the action as string
--- ["<c-d>"] = "delete_buffer",
--- },
--- n = {

View File

@@ -1,11 +1,14 @@
local strings = require('plenary.strings')
local strings = require "plenary.strings"
local deprecated = require "telescope.deprecated"
local sorters = require "telescope.sorters"
local if_nil = vim.F.if_nil
-- Keep the values around between reloads
_TelescopeConfigurationValues = _TelescopeConfigurationValues or {}
_TelescopeConfigurationPickers = _TelescopeConfigurationPickers or {}
local function first_non_null(...)
local n = select('#', ...)
local n = select("#", ...)
for i = 1, n do
local value = select(i, ...)
@@ -15,7 +18,26 @@ local function first_non_null(...)
end
end
local sorters = require('telescope.sorters')
-- 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
-- TODO: Add other major configuration points here.
-- selection_strategy
@@ -27,18 +49,279 @@ config.descriptions = {}
config.pickers = _TelescopeConfigurationPickers
function config.set_pickers(pickers)
pickers = pickers or {}
pickers = if_nil(pickers, {})
for k, v in pairs(pickers) do
config.pickers[k] = v
end
end
function config.set_defaults(defaults)
defaults = defaults or {}
local layout_config_defaults = {
width = 0.8,
height = 0.9,
horizontal = {
prompt_position = "bottom",
preview_cutoff = 120,
},
vertical = {
preview_cutoff = 40,
},
center = {
preview_cutoff = 40,
},
}
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.
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 = " " }
))
-- 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 = {
"descending",
[[
Determines the direction "better" results are sorted towards.
Available options are:
- "descending" (default)
- "ascending"]],
},
selection_strategy = {
"reset",
[[
Determines how the cursor acts after each sort iteration.
Available options are:
- "reset" (default)
- "follow"
- "row"]],
},
scroll_strategy = {
"cycle",
[[
Determines what happens you try to scroll past view of the picker.
Available options are:
- "cycle" (default)
- "limit"]],
},
layout_strategy = {
"horizontal",
[[
Determines the default layout of Telescope pickers.
See |telescope.layout| for details of the available strategies.
Default: 'horizontal']],
},
layout_config = { layout_config_defaults, layout_config_description },
winblend = { 0 },
prompt_prefix = { "> ", [[
Will be shown in front of the prompt.
Default: '> ']]
},
selection_caret = { "> ", [[
Will be shown in front of the selection.
Default: '> ']]
},
entry_prefix = { " ", [[
Prefix in front of each result entry. Current selection not included.
Default: ' ']]
},
initial_mode = { "insert" },
border = { true, [[
Boolean defining if borders are added to Telescope windows.
Default: true]]
},
borderchars = { { "", "", "", "", "", "", "", "" } },
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,
},
dynamic_preview_title = {
false,
[[
Will change the title of the preview window dynamically, where it
is supported. Means the preview window will for example show the
full filename.
Default: false
]],
},
-- Builtin configuration
-- List that will be executed.
-- Last argument will be the search term (passed in during execution)
vimgrep_arguments = {
{ "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" },
},
use_less = { true },
color_devicons = { true },
set_env = { nil },
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 = {
["<esc>"] = require('telescope.actions').close,
},
}
To disable a keymap, put [map] = false
So, to not map "<C-n>", just put
...,
["<C-n>"] = false,
...,
Into your config.
otherwise, just set the mapping to the function that you want it to be.
...,
["<C-i>"] = require('telescope.actions').select_default,
...,
If the function you want is part of `telescope.actions`, then you can simply give a string.
For example, the previous option is equivalent to:
...,
["<C-i>"] = "select_default",
...,
You can also add other mappings using tables with `type = "command"`.
For example:
...,
["jj"] = { "<esc>", type = "command" },
["kk"] = { "<cmd>echo \"Hello, World!\"<cr>", type = "command" },)
...,
]],
},
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.
]],
},
generic_sorter = { sorters.get_generic_fuzzy_sorter },
prefilter_sorter = { sorters.prefilter },
file_sorter = { sorters.get_fuzzy_file },
file_ignore_patterns = { nil },
file_previewer = {
function(...)
return require("telescope.previewers").vim_buffer_cat.new(...)
end,
},
grep_previewer = {
function(...)
return require("telescope.previewers").vim_buffer_vimgrep.new(...)
end,
},
qflist_previewer = {
function(...)
return require("telescope.previewers").vim_buffer_qflist.new(...)
end,
},
buffer_previewer_maker = {
function(...)
return require("telescope.previewers").buffer_previewer_maker(...)
end,
},
}
-- @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 = if_nil(user_defaults, {})
tele_defaults = if_nil(tele_defaults, telescope_defaults)
-- Check if using layout keywords outside of `layout_config`
deprecated.picker_window_options(user_defaults)
-- Check if using `layout_defaults` instead of `layout_config`
user_defaults = deprecated.layout_configuration(user_defaults)
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(
if_nil(user_defaults[name], {}),
vim.tbl_deep_extend("keep", if_nil(config.values[name], {}), if_nil(default_val, {}))
)
end
return first_non_null(user_defaults[name], config.values[name], default_val)
end
local function set(name, default_val, description)
@@ -51,114 +334,13 @@ function config.set_defaults(defaults)
end
end
set("sorting_strategy", "descending", [[
Determines the direction "better" results are sorted towards.
for key, info in pairs(tele_defaults) do
set(key, info[1], info[2])
end
Available options are:
- "descending" (default)
- "ascending"]])
set("selection_strategy", "reset", [[
Determines how the cursor acts after each sort iteration.
Available options are:
- "reset" (default)
- "follow"
- "row"]])
set("scroll_strategy", "cycle", [[
Determines what happens you try to scroll past view of the picker.
Available options are:
- "cycle" (default)
- "limit"]])
set("layout_strategy", "horizontal")
set("layout_defaults", {})
set("width", 0.75)
set("winblend", 0)
set("prompt_position", "bottom")
set("preview_cutoff", 120)
set("results_height", 1)
set("results_width", 0.8)
set("prompt_prefix", "> ", [[
Will be shown in front of the prompt.
Default: '> ']])
set("selection_caret", "> ", [[
Will be shown in front of the selection.
Default: '> ']])
set("entry_prefix", " ", [[
Prefix in front of each result entry. Current selection not included.
Default: ' ']])
set("initial_mode", "insert")
set("border", {})
set("borderchars", { '', '', '', '', '', '', '', ''})
set("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 "<C-n>", just put
--
-- ...,
-- ["<C-n>"] = false,
-- ...,
--
-- Into your config.
--
-- Otherwise, just set the mapping to the function that you want it to be.
--
-- ...,
-- ["<C-i>"] = 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("dynamic_preview_title", false, [[
Will change the title of the preview window dynamically, where it
is supported. Means the preview window will for example show the
full filename.
Default: false
]])
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)
local M = {}
M.get = get
return M
end
function config.clear_defaults()
@@ -169,5 +351,4 @@ end
config.set_defaults()
return config

View File

@@ -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,80 @@ 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,
-- 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,
-- 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,
-- 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.
[function(val) return type(val) == 'function' end] = function(selector, val)
-- Booleans
_resolve_map[function(val) return val == false end] = function(_, val)
return function(...)
return val
end,
}
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
_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}
-- 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
--- Converts input to a function that returns the height.
--- The input must take one of four forms:
--- 1. 0 <= number < 1 <br>
--- This means total height as a percentage.
--- 2. 1 <= number <br>
--- This means total height as a fixed number.
--- 3. function <br>
--- Must have signature:
--- function(self, max_columns, max_lines): number
--- 4. table of the form: {padding = `foo`} <br>
--- where `foo` has one of the previous three forms. <br>
--- 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 +177,23 @@ 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 <br>
--- This means total width as a percentage.
--- 2. 1 <= number <br>
--- This means total width as a fixed number.
--- 3. function <br>
--- Must have signature:
--- function(self, max_columns, max_lines): number
--- 4. table of the form: {padding = `foo`} <br>
--- where `foo` has one of the previous three forms. <br>
--- 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
@@ -148,8 +204,8 @@ resolver.resolve_width = function(val)
error('invalid configuration option for width:' .. tostring(val))
end
--- Win option always returns a table with preview, results, and prompt.
--- It handles many different ways. Some examples are as follows:
-- Win option always returns a table with preview, results, and prompt.
-- It handles many different ways. Some examples are as follows:
--
-- -- Disable
-- borderschars = false

View File

@@ -0,0 +1,59 @@
local log = require "telescope.log"
local deprecated = {}
deprecated.picker_window_options = function(opts)
local messages = {}
-- Deprecated: PR:922, 2021/06/25
-- 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-922'")
end
if opts.results_width then
table.insert(messages,
"'opts.results_width' actually didn't do anything. Please see ':help telescope.changelog-922'"
)
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 <Enter> to continue\n")
end
end
deprecated.layout_configuration = function(user_defaults)
if user_defaults.layout_defaults then
if user_defaults.layout_config == nil then
log.warn "Using 'layout_defaults' in setup() is deprecated. Use 'layout_config' instead."
user_defaults.layout_config = user_defaults.layout_defaults
else
error "Using 'layout_defaults' in setup() is deprecated. Remove this key and use 'layout_config' instead."
end
end
return user_defaults
end
return deprecated

View File

@@ -249,6 +249,8 @@ function make_entry.gen_from_git_stash()
end
function make_entry.gen_from_git_commits(opts)
opts = opts or {}
local displayer = entry_display.create {
separator = " ",
items = {

View File

@@ -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({
@@ -96,33 +99,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

View File

@@ -7,7 +7,7 @@
--- All layout strategies are functions with the following signature:
---
--- <pre>
--- function(picker, columns, lines)
--- function(picker, columns, lines, layout_config)
--- -- Do some calculations here...
--- return {
--- preview = preview_configuration
@@ -17,136 +17,257 @@
--- 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.
---
--- </pre>
---
--- 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')
local if_nil = vim.F.if_nil
-- 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
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)
))
end
local get_border_size = function(opts)
if opts.window.border == false then
return 0
end
return options
return 1
end
local layout_strategies = {}
layout_strategies._configurations = {}
--- Horizontal previewer
--@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 = {
-- TEMP: There are a few keys we should say are valid to start with.
preview_cutoff = true,
prompt_position = 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: 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 = if_nil(default_layout_config, 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
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 point 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 result
end
-- 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 number of lines to scroll 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 = {"<pre>", "`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:")
for _, k in ipairs(strategy_keys) do
if not shared_options[k] then
add_value(k, strategy_config[k])
end
end
table.insert(results, "</pre>")
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", if_nil(override_layout, {}), if_nil(self.layout_config, {}))
)
)
end
end
--- Horizontal layout has two columns, one for the preview
--- and one for the prompt and results.
---
--- <pre>
--- +-------------+--------------+
--- | | |
--- | Results | |
--- | | Preview |
--- | | |
--- +-------------| |
--- | Prompt | |
--- +-------------+--------------+
--- ┌──────────────────────────────────────────────────┐
---
--- │ ┌───────────────────┐┌───────────────────┐
--- ││ │ │
--- │ │ ││ │ │
--- │ ││ │ │
--- │ Results ││ │ │
--- │ ││ Preview │ │
--- │ │ ││ │ │
--- │ │ ││ │ │
--- │ └───────────────────┘│ │ │
--- │ ┌───────────────────┐│ │ │
--- │ │ Prompt ││ │ │
--- │ └───────────────────┘└───────────────────┘ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
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
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 self.previewer and max_columns >= layout_config.preview_cutoff then
preview.width = resolve.resolve_width(if_nil(layout_config.preview_width, function(_, cols)
if cols < 150 then
return math.floor(cols * 0.4)
elseif cols < 200 then
return 80
else
return 120
end
end)(self, picker_width, max_lines)
end))(self, picker_width, max_lines)
else
preview.width = 0
end
@@ -175,14 +296,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 +311,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()|`).
---
--- <pre>
--- +--------------+
--- | Preview |
--- +--------------+
--- | Prompt |
--- +--------------+
--- | Result |
--- | Result |
--- | Result |
--- +--------------+
--- ┌──────────────────────────────────────────────────┐
--- ┌────────────────────────────────────────┐ │
--- │ | Preview | │
--- | Preview | │
--- └────────────────────────────────────────┘ │
--- ┌────────────────────────────────────────┐ │
--- | Prompt | │
--- ├────────────────────────────────────────┤ │
--- │ | Result | │
--- │ | Result | │
--- │ └────────────────────────────────────────┘ │
--- │ │
--- │ │
--- │ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
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 +367,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.
---
--- <pre>
--- +-----------------+
--- | Previewer |
--- | Previewer |
--- | Previewer |
--- +-----------------+
--- | Result |
--- | Result |
--- | Result |
--- +-----------------+
--- | Prompt |
--- +-----------------+
--- ┌──────────────────────────────────────────────────┐
---
--- ┌────────────────────────────────────────┐
--- | Preview | │
--- | Preview | │
--- | Preview | │
--- └────────────────────────────────────────┘
--- ┌────────────────────────────────────────┐
--- | Result | │
--- | Result | │
--- └────────────────────────────────────────┘ │
--- │ ┌────────────────────────────────────────┐ │
--- │ | Prompt | │
--- │ └────────────────────────────────────────┘ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
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()|" },
prompt_position = { "(unimplemented, but we plan on supporting)" },
}), 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)
if_nil(layout_config.preview_height, 0.5)
)(self, max_columns, picker_height)
preview_total = preview.height + 2
@@ -332,36 +471,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 {}
local flip_columns = layout_config.flip_columns or 100
local flip_lines = layout_config.flip_lines or 20
---@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 = if_nil(layout_config.flip_columns, 100)
local flip_lines = if_nil(layout_config.flip_lines, 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 +555,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 = if_nil(resolve.resolve_height(layout_config.height)(self,max_columns,max_lines), 25)
local prompt_width = max_columns
local col = 0
@@ -477,6 +619,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

View File

@@ -302,7 +302,6 @@ previewers.git_commit_message = buffer_previewer.git_commit_message
--- The run command is `git --no-pager diff $FILE`
previewers.git_file_diff = buffer_previewer.git_file_diff
previewers.ctags = buffer_previewer.ctags
previewers.builtin = buffer_previewer.builtin
previewers.help = buffer_previewer.help

View File

@@ -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",
results_title = false,
preview_title = "Preview",
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,
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 = {"", "", " ", "", "", "", "", ""},