feat: layout anchor (#1582)
* feat: add `anchor` option to some `layout_strategies` * tests: improve tests for `resolve_height/width`
This commit is contained in:
@@ -1609,6 +1609,9 @@ layout_strategies.horizontal() *layout_strategies.horizontal()*
|
|||||||
└──────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────┘
|
||||||
|
|
||||||
`picker.layout_config` shared options:
|
`picker.layout_config` shared options:
|
||||||
|
- anchor:
|
||||||
|
- Which edge/corner to pin the picker to
|
||||||
|
- See |resolver.resolve_anchor_pos()|
|
||||||
- height:
|
- height:
|
||||||
- How tall to make Telescope's entire layout
|
- How tall to make Telescope's entire layout
|
||||||
- See |resolver.resolve_height()|
|
- See |resolver.resolve_height()|
|
||||||
@@ -1634,6 +1637,9 @@ layout_strategies.center() *layout_strategies.center()*
|
|||||||
remaining space above. Particularly useful for creating dropdown menus (see
|
remaining space above. Particularly useful for creating dropdown menus (see
|
||||||
|telescope.themes| and |themes.get_dropdown()|`).
|
|telescope.themes| and |themes.get_dropdown()|`).
|
||||||
|
|
||||||
|
Note that the `anchor` option can only pin this layout to the left or right
|
||||||
|
edges.
|
||||||
|
|
||||||
┌──────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────┐
|
||||||
│ ┌────────────────────────────────────────┐ │
|
│ ┌────────────────────────────────────────┐ │
|
||||||
│ | Preview | │
|
│ | Preview | │
|
||||||
@@ -1652,6 +1658,9 @@ layout_strategies.center() *layout_strategies.center()*
|
|||||||
└──────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────┘
|
||||||
|
|
||||||
`picker.layout_config` shared options:
|
`picker.layout_config` shared options:
|
||||||
|
- anchor:
|
||||||
|
- Which edge/corner to pin the picker to
|
||||||
|
- See |resolver.resolve_anchor_pos()|
|
||||||
- height:
|
- height:
|
||||||
- How tall to make Telescope's entire layout
|
- How tall to make Telescope's entire layout
|
||||||
- See |resolver.resolve_height()|
|
- See |resolver.resolve_height()|
|
||||||
@@ -1713,6 +1722,9 @@ layout_strategies.vertical() *layout_strategies.vertical()*
|
|||||||
└──────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────┘
|
||||||
|
|
||||||
`picker.layout_config` shared options:
|
`picker.layout_config` shared options:
|
||||||
|
- anchor:
|
||||||
|
- Which edge/corner to pin the picker to
|
||||||
|
- See |resolver.resolve_anchor_pos()|
|
||||||
- height:
|
- height:
|
||||||
- How tall to make Telescope's entire layout
|
- How tall to make Telescope's entire layout
|
||||||
- See |resolver.resolve_height()|
|
- See |resolver.resolve_height()|
|
||||||
@@ -1740,6 +1752,9 @@ layout_strategies.flex() *layout_strategies.flex()*
|
|||||||
|
|
||||||
|
|
||||||
`picker.layout_config` shared options:
|
`picker.layout_config` shared options:
|
||||||
|
- anchor:
|
||||||
|
- Which edge/corner to pin the picker to
|
||||||
|
- See |resolver.resolve_anchor_pos()|
|
||||||
- height:
|
- height:
|
||||||
- How tall to make Telescope's entire layout
|
- How tall to make Telescope's entire layout
|
||||||
- See |resolver.resolve_height()|
|
- See |resolver.resolve_height()|
|
||||||
@@ -1812,6 +1827,20 @@ resolver.resolve_width() *resolver.resolve_width()*
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
resolver.resolve_anchor_pos() *resolver.resolve_anchor_pos()*
|
||||||
|
Calculates the adjustment required to move the picker from the middle of
|
||||||
|
the screen to an edge or corner.
|
||||||
|
The `anchor` can be any of the following strings:
|
||||||
|
- "", "CENTER", "NW", "N", "NE", "E", "SE", "S", "SW", "W" The anchors
|
||||||
|
have the following meanings:
|
||||||
|
- "" or "CENTER":
|
||||||
|
the picker will remain in the middle of the screen.
|
||||||
|
- Compass directions:
|
||||||
|
the picker will move to the corresponding edge/corner e.g. "NW" -> "top
|
||||||
|
left corner", "E" -> "right edge", "S" -> "bottom edge"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
*telescope.actions*
|
*telescope.actions*
|
||||||
|
|||||||
@@ -213,6 +213,35 @@ resolver.resolve_width = function(val)
|
|||||||
error("invalid configuration option for width:" .. tostring(val))
|
error("invalid configuration option for width:" .. tostring(val))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Calculates the adjustment required to move the picker from the middle of the screen to
|
||||||
|
--- an edge or corner. <br>
|
||||||
|
--- The `anchor` can be any of the following strings:
|
||||||
|
--- - "", "CENTER", "NW", "N", "NE", "E", "SE", "S", "SW", "W"
|
||||||
|
--- The anchors have the following meanings:
|
||||||
|
--- - "" or "CENTER":<br>
|
||||||
|
--- the picker will remain in the middle of the screen.
|
||||||
|
--- - Compass directions:<br>
|
||||||
|
--- the picker will move to the corresponding edge/corner
|
||||||
|
--- e.g. "NW" -> "top left corner", "E" -> "right edge", "S" -> "bottom edge"
|
||||||
|
resolver.resolve_anchor_pos = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
anchor = anchor:upper()
|
||||||
|
local pos = { 0, 0 }
|
||||||
|
if anchor == "CENTER" then
|
||||||
|
return pos
|
||||||
|
end
|
||||||
|
if anchor:find "W" then
|
||||||
|
pos[1] = math.ceil((p_width - max_columns) / 2) + 1
|
||||||
|
elseif anchor:find "E" then
|
||||||
|
pos[1] = math.ceil((max_columns - p_width) / 2) - 1
|
||||||
|
end
|
||||||
|
if anchor:find "N" then
|
||||||
|
pos[2] = math.ceil((p_height - max_lines) / 2) + 1
|
||||||
|
elseif anchor:find "S" then
|
||||||
|
pos[2] = math.ceil((max_lines - p_height) / 2) - 1
|
||||||
|
end
|
||||||
|
return pos
|
||||||
|
end
|
||||||
|
|
||||||
-- Win option always returns a table with preview, results, and prompt.
|
-- Win option always returns a table with preview, results, and prompt.
|
||||||
-- It handles many different ways. Some examples are as follows:
|
-- It handles many different ways. Some examples are as follows:
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -107,6 +107,13 @@ local get_valid_configuration_keys = function(strategy_config)
|
|||||||
return valid_configuration_keys
|
return valid_configuration_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local adjust_pos = function(pos, ...)
|
||||||
|
for _, opts in ipairs { ... } do
|
||||||
|
opts.col = opts.col and opts.col + pos[1]
|
||||||
|
opts.line = opts.line and opts.line + pos[2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--@param strategy_name string: the name of the layout_strategy we are validating for
|
--@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 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 values table: table containing all of the non-default options we want to set
|
||||||
@@ -187,6 +194,7 @@ local shared_options = {
|
|||||||
mirror = "Flip the location of the results/prompt and preview windows",
|
mirror = "Flip the location of the results/prompt and preview windows",
|
||||||
scroll_speed = "The number of lines to scroll through the previewer",
|
scroll_speed = "The number of lines to scroll through the previewer",
|
||||||
prompt_position = { "Where to place prompt window.", "Available Values: 'bottom', 'top'" },
|
prompt_position = { "Where to place prompt window.", "Available Values: 'bottom', 'top'" },
|
||||||
|
anchor = { "Which edge/corner to pin the picker to", "See |resolver.resolve_anchor_pos()|" },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Used for generating vim help documentation.
|
-- Used for generating vim help documentation.
|
||||||
@@ -368,6 +376,9 @@ layout_strategies.horizontal = make_documented_layout(
|
|||||||
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
|
||||||
|
adjust_pos(anchor_pos, prompt, results, preview)
|
||||||
|
|
||||||
if tbln then
|
if tbln then
|
||||||
prompt.line = prompt.line + 1
|
prompt.line = prompt.line + 1
|
||||||
results.line = results.line + 1
|
results.line = results.line + 1
|
||||||
@@ -388,6 +399,8 @@ layout_strategies.horizontal = make_documented_layout(
|
|||||||
--- Particularly useful for creating dropdown menus
|
--- Particularly useful for creating dropdown menus
|
||||||
--- (see |telescope.themes| and |themes.get_dropdown()|`).
|
--- (see |telescope.themes| and |themes.get_dropdown()|`).
|
||||||
---
|
---
|
||||||
|
--- Note that the `anchor` option can only pin this layout to the left or right edges.
|
||||||
|
---
|
||||||
--- <pre>
|
--- <pre>
|
||||||
--- ┌──────────────────────────────────────────────────┐
|
--- ┌──────────────────────────────────────────────────┐
|
||||||
--- │ ┌────────────────────────────────────────┐ │
|
--- │ ┌────────────────────────────────────────┐ │
|
||||||
@@ -475,7 +488,12 @@ layout_strategies.center = make_documented_layout(
|
|||||||
preview.height = 0
|
preview.height = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
results.col, preview.col, prompt.col = 0, 0, 0 -- all centered
|
local width_padding = math.floor((max_columns - width) / 2) + bs + 1
|
||||||
|
results.col, preview.col, prompt.col = width_padding, width_padding, width_padding
|
||||||
|
|
||||||
|
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
|
||||||
|
anchor_pos[2] = 0 -- only use horizontal anchoring
|
||||||
|
adjust_pos(anchor_pos, prompt, results, preview)
|
||||||
|
|
||||||
if tbln then
|
if tbln then
|
||||||
prompt.line = prompt.line + 1
|
prompt.line = prompt.line + 1
|
||||||
@@ -514,7 +532,11 @@ layout_strategies.center = make_documented_layout(
|
|||||||
--- </pre>
|
--- </pre>
|
||||||
layout_strategies.cursor = make_documented_layout(
|
layout_strategies.cursor = make_documented_layout(
|
||||||
"cursor",
|
"cursor",
|
||||||
vim.tbl_extend("error", shared_options, {
|
vim.tbl_extend("error", {
|
||||||
|
width = shared_options.width,
|
||||||
|
height = shared_options.height,
|
||||||
|
scroll_speed = shared_options.scroll_speed,
|
||||||
|
}, {
|
||||||
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
|
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",
|
preview_cutoff = "When columns are less than this value, the preview will be disabled",
|
||||||
}),
|
}),
|
||||||
@@ -661,7 +683,8 @@ layout_strategies.vertical = make_documented_layout(
|
|||||||
prompt.height = 1
|
prompt.height = 1
|
||||||
results.height = height - preview.height - prompt.height - h_space
|
results.height = height - preview.height - prompt.height - h_space
|
||||||
|
|
||||||
results.col, preview.col, prompt.col = 0, 0, 0 -- all centered
|
local width_padding = math.floor((max_columns - width) / 2) + bs + 1
|
||||||
|
results.col, preview.col, prompt.col = width_padding, width_padding, width_padding
|
||||||
|
|
||||||
local height_padding = math.floor((max_lines - height) / 2)
|
local height_padding = math.floor((max_lines - height) / 2)
|
||||||
if not layout_config.mirror then
|
if not layout_config.mirror then
|
||||||
@@ -689,6 +712,9 @@ layout_strategies.vertical = make_documented_layout(
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
|
||||||
|
adjust_pos(anchor_pos, prompt, results, preview)
|
||||||
|
|
||||||
if tbln then
|
if tbln then
|
||||||
prompt.line = prompt.line + 1
|
prompt.line = prompt.line + 1
|
||||||
results.line = results.line + 1
|
results.line = results.line + 1
|
||||||
|
|||||||
@@ -59,10 +59,142 @@ describe("telescope.config.resolve", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe("resolve_height/width", function()
|
describe("resolve_height/width", function()
|
||||||
eq(10, resolve.resolve_height(0.1)(nil, 24, 100))
|
local test_sizes = {
|
||||||
eq(2, resolve.resolve_width(0.1)(nil, 24, 100))
|
{ 24, 100 },
|
||||||
|
{ 35, 125 },
|
||||||
|
{ 60, 59 },
|
||||||
|
{ 100, 40 },
|
||||||
|
}
|
||||||
|
it("should handle percentages", function()
|
||||||
|
local percentages = { 0.1, 0.33333, 0.5, 0.99 }
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
for _, p in ipairs(percentages) do
|
||||||
|
eq(math.floor(s[1] * p), resolve.resolve_width(p)(nil, unpack(s)))
|
||||||
|
eq(math.floor(s[2] * p), resolve.resolve_height(p)(nil, unpack(s)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
eq(10, resolve.resolve_width(10)(nil, 24, 100))
|
it("should handle fixed size", function()
|
||||||
eq(24, resolve.resolve_width(50)(nil, 24, 100))
|
local fixed = { 5, 8, 13, 21, 34 }
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
for _, f in ipairs(fixed) do
|
||||||
|
eq(math.min(f, s[1]), resolve.resolve_width(f)(nil, unpack(s)))
|
||||||
|
eq(math.min(f, s[2]), resolve.resolve_height(f)(nil, unpack(s)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should handle functions", function()
|
||||||
|
local func = function(_, max_columns, max_lines)
|
||||||
|
if max_columns < 45 then
|
||||||
|
return math.min(max_columns, max_lines)
|
||||||
|
elseif max_columns < max_lines then
|
||||||
|
return max_columns * 0.8
|
||||||
|
else
|
||||||
|
return math.min(max_columns, max_lines) * 0.5
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
eq(func(nil, unpack(s)), resolve.resolve_height(func)(nil, unpack(s)))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should handle padding", function()
|
||||||
|
local func = function(_, max_columns, max_lines)
|
||||||
|
return math.floor(math.min(max_columns * 0.6, max_lines * 0.8))
|
||||||
|
end
|
||||||
|
local pads = { 0.1, 5, func }
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
for _, p in ipairs(pads) do
|
||||||
|
eq(s[1] - 2 * resolve.resolve_width(p)(nil, unpack(s)), resolve.resolve_width { padding = p }(nil, unpack(s)))
|
||||||
|
eq(
|
||||||
|
s[2] - 2 * resolve.resolve_height(p)(nil, unpack(s)),
|
||||||
|
resolve.resolve_height { padding = p }(nil, unpack(s))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("resolve_anchor_pos", function()
|
||||||
|
local test_sizes = {
|
||||||
|
{ 6, 7, 8, 9 },
|
||||||
|
{ 10, 20, 30, 40 },
|
||||||
|
{ 15, 15, 16, 16 },
|
||||||
|
{ 17, 19, 23, 31 },
|
||||||
|
{ 21, 18, 26, 24 },
|
||||||
|
{ 50, 100, 150, 200 },
|
||||||
|
}
|
||||||
|
|
||||||
|
it([[should not adjust when "CENTER" or "" is the anchor]], function()
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
eq({ 0, 0 }, resolve.resolve_anchor_pos("", unpack(s)))
|
||||||
|
eq({ 0, 0 }, resolve.resolve_anchor_pos("center", unpack(s)))
|
||||||
|
eq({ 0, 0 }, resolve.resolve_anchor_pos("CENTER", unpack(s)))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it([[should end up at top when "N" in the anchor]], function()
|
||||||
|
local top_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
eq(1, pos[2] + math.floor((max_lines - p_height) / 2))
|
||||||
|
end
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
top_test("NW", unpack(s))
|
||||||
|
top_test("N", unpack(s))
|
||||||
|
top_test("NE", unpack(s))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it([[should end up at left when "W" in the anchor]], function()
|
||||||
|
local left_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
eq(1, pos[1] + math.floor((max_columns - p_width) / 2))
|
||||||
|
end
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
left_test("NW", unpack(s))
|
||||||
|
left_test("W", unpack(s))
|
||||||
|
left_test("SW", unpack(s))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it([[should end up at bottom when "S" in the anchor]], function()
|
||||||
|
local bot_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
eq(max_lines - 1, pos[2] + p_height + math.floor((max_lines - p_height) / 2))
|
||||||
|
end
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
bot_test("SW", unpack(s))
|
||||||
|
bot_test("S", unpack(s))
|
||||||
|
bot_test("SE", unpack(s))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it([[should end up at right when "E" in the anchor]], function()
|
||||||
|
local right_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||||
|
eq(max_columns - 1, pos[1] + p_width + math.floor((max_columns - p_width) / 2))
|
||||||
|
end
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
right_test("NE", unpack(s))
|
||||||
|
right_test("E", unpack(s))
|
||||||
|
right_test("SE", unpack(s))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it([[should ignore casing of the anchor]], function()
|
||||||
|
local case_test = function(a1, a2, p_width, p_height, max_columns, max_lines)
|
||||||
|
local pos1 = resolve.resolve_anchor_pos(a1, p_width, p_height, max_columns, max_lines)
|
||||||
|
local pos2 = resolve.resolve_anchor_pos(a2, p_width, p_height, max_columns, max_lines)
|
||||||
|
eq(pos1, pos2)
|
||||||
|
end
|
||||||
|
for _, s in ipairs(test_sizes) do
|
||||||
|
case_test("ne", "NE", unpack(s))
|
||||||
|
case_test("w", "W", unpack(s))
|
||||||
|
case_test("sW", "sw", unpack(s))
|
||||||
|
case_test("cEnTeR", "CeNtEr", unpack(s))
|
||||||
|
end
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user