From 1fa2709de5b4f2249a39a951139665d44aadd278 Mon Sep 17 00:00:00 2001 From: Brean-dev Date: Sun, 24 Aug 2025 11:59:32 +0000 Subject: [PATCH] fix(telescope): guard layout_config width and improve picker sizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(telescope): guard layout_config width and improve picker sizing - Prevent nil index error when `config.layout_config[config.layout_strategy]` is not defined (common on newer Telescope versions). - Resolve picker width using a safe precedence chain: telescope_opts.width → telescope_opts.layout_config.width → config.width → config.layout_config.width → per-strategy width → fallback 0.8. - Clamp absolute widths to terminal width to prevent overflow. - Derive column widths from actual picker width instead of vim.o.columns for better alignment. - Guard against missing entry fields (tags, section, description, cheatcode) to avoid crashes on malformed entries. References: - Telescope deprecation of config.width: https://github.com/nvim-telescope/telescope.nvim/commit/5a53ec5c2fdab10ca8775d3979b1a85e63d57953 - Layout config refactor: https://github.com/nvim-telescope/telescope.nvim/pull/1039 --- lua/cheatsheet/telescope/init.lua | 215 +++++++++++++++++------------- 1 file changed, 126 insertions(+), 89 deletions(-) diff --git a/lua/cheatsheet/telescope/init.lua b/lua/cheatsheet/telescope/init.lua index 96b04e0..af15a7e 100644 --- a/lua/cheatsheet/telescope/init.lua +++ b/lua/cheatsheet/telescope/init.lua @@ -1,13 +1,13 @@ -local actions = require('telescope.actions') -local actions_state = require('telescope.actions.state') -local finders = require('telescope.finders') -local pickers = require('telescope.pickers') +local actions = require("telescope.actions") +local actions_state = require("telescope.actions.state") +local finders = require("telescope.finders") +local pickers = require("telescope.pickers") -local config = require('telescope.config').values -local entry_display = require('telescope.pickers.entry_display') +local config = require("telescope.config").values +local entry_display = require("telescope.pickers.entry_display") -local cheatsheet = require('cheatsheet') -local utils = require('cheatsheet.utils') +local cheatsheet = require("cheatsheet") +local utils = require("cheatsheet.utils") local M = {} @@ -18,87 +18,124 @@ local M = {} -- - Edit user cheatsheet in new buffer -- - Yank the cheatcode M.pick_cheat = function(telescope_opts, opts) - telescope_opts = telescope_opts or {} - - pickers.new( - telescope_opts, { - prompt_title = 'Cheat', - finder = finders.new_table { - results = cheatsheet.get_cheats(opts), - entry_maker = function(entry) - -- Calculate the width of each column dynamically so that both - -- the description and cheatcode is readable on small terminals too. - -- This whole logic can be avoided if the cheatcode is shown first and - -- a small width for the respective cheatcode column is used. - -- But the cheatcode is what we *don't* know and the description is - -- what we already know. So show description first for better UX. - - -- * config.width was deprecated in favor of config.layout_config.width - -- https://github.com/nvim-telescope/telescope.nvim/commit/5a53ec5c2fdab10ca8775d3979b1a85e63d57953 - -- * config.layout_config was changed to move width and height to individual layout_strategy configs - -- https://github.com/nvim-telescope/telescope.nvim/pull/1039/files#diff-4936325bfc521d7cffc09fe0156becd4a4ba2ed169431c94bd63669fe0cc1a2aL79-L80 - local cols = vim.o.columns - local width = config.width - or config.layout_config.width - or config.layout_config[config.layout_strategy].width - or cols - local tel_win_width - -- width = 80 -> column width, width = 0.7 -> ratio - if width > 1 then - tel_win_width = width - else - tel_win_width = math.floor(cols * width) - end - local cheatcode_width = math.floor(cols * 0.25) - local section_width = 10 - - -- NOTE: the width calculating logic is not exact, but approx enough - local displayer = entry_display.create { - separator = " ▏", - items = { - { width = section_width }, -- section - { - width = tel_win_width - cheatcode_width - - section_width, - }, -- description - { remaining = true }, -- cheatcode - }, - } - - local function make_display(ent) - return displayer { - -- text, highlight group - { ent.value.section, "cheatMetadataSection" }, - { ent.value.description, "cheatDescription" }, - { ent.value.cheatcode, "cheatCode" }, - } - end - - local tags = table.concat(entry.tags, ' ') - - return { - value = entry, - -- generate the string that user sees as an item - display = make_display, - -- queries are matched against ordinal - ordinal = string.format( - '%s %s %s %s', entry.section, entry.description, - tags, entry.cheatcode - ), - } - end, - }, - attach_mappings = function(prompt_bufnr, map) - local mappings = require('cheatsheet.config').options.telescope_mappings - for keybind, action in pairs(mappings) do - map('i', keybind, function() action(prompt_bufnr) end) - end - - return true - end, - sorter = config.generic_sorter(telescope_opts), - } - ):find() + telescope_opts = telescope_opts or {} + + pickers + .new(telescope_opts, { + prompt_title = "Cheat", + finder = finders.new_table({ + results = cheatsheet.get_cheats(opts), + entry_maker = function(entry) + -- Calculate the width of each column dynamically so that both + -- the description and cheatcode is readable on small terminals too. + -- This whole logic can be avoided if the cheatcode is shown first and + -- a small width for the respective cheatcode column is used. + -- But the cheatcode is what we *don't* know and the description is + -- what we already know. So show description first for better UX. + + -- * config.width was deprecated in favor of config.layout_config.width + -- https://github.com/nvim-telescope/telescope.nvim/commit/5a53ec5c2fdab10ca8775d3979b1a85e63d57953 + -- * config.layout_config was changed to move width and height to individual layout_strategy configs + -- https://github.com/nvim-telescope/telescope.nvim/pull/1039/files#diff-4936325bfc521d7cffc09fe0156becd4a4ba2ed169431c94bd63669fe0cc1a2aL79-L80 + -- + -- PR NOTE (Issue 1 - nil index crash): + -- On newer Telescope versions, `config.layout_config[config.layout_strategy]` + -- may be nil unless the user explicitly configures that strategy. Indexing `.width` + -- on nil raised "attempt to index a nil value" here. We now resolve width using a + -- defensive precedence order and only index per-strategy tables when they exist. + + -- columns in the current Neovim UI + local cols = vim.o.columns + + -- Resolve layout strategy (opts → global → default). Keep current behavior but add fallback. + local strategy = telescope_opts.layout_strategy or config.layout_strategy or "horizontal" + + -- Pull per-strategy layout tables safely (opts first, then global). + local per_strategy = (telescope_opts.layout_config and telescope_opts.layout_config[strategy]) + or (config.layout_config and config.layout_config[strategy]) + + -- Accept width from (newest → oldest) with sane default. + -- We keep compatibility with deprecated fields (`config.width`, `layout_config.width`) + -- and also honor per-strategy `width` when present. + local width = telescope_opts.width + or (telescope_opts.layout_config and telescope_opts.layout_config.width) + or config.width + or (config.layout_config and config.layout_config.width) + or (per_strategy and per_strategy.width) + or 0.8 -- default ratio if nothing set + + local tel_win_width + -- width = 80 -> absolute columns, width = 0.7 -> ratio of UI columns + if type(width) == "number" and width > 1 then + -- PR NOTE (Issue 2 - oversize windows on small terminals): + -- Clamp absolute widths to available columns to avoid overflow on narrow terminals. + tel_win_width = math.min(math.floor(width), cols) + else + tel_win_width = math.floor(cols * width) + end + + -- PR NOTE (Issue 3 - sizing by vim.o.columns instead of actual picker width): + -- Previously column widths were derived from `vim.o.columns` while the picker might be narrower. + -- We now size columns from `tel_win_width` (the picker width) for more accurate layout. + local cheatcode_width = math.floor(tel_win_width * 0.25) + local section_width = 10 + + -- NOTE: the width calculating logic is not exact, but approx enough + local displayer = entry_display.create({ + separator = " ▏", + items = { + { width = section_width }, -- section + { + width = tel_win_width - cheatcode_width - section_width, + }, -- description + { remaining = true }, -- cheatcode + }, + }) + + local function make_display(ent) + -- PR NOTE (Issue 4 - defensive rendering): + -- Guard against missing fields so a bad entry never crashes rendering. + local e = ent.value or {} + return displayer({ + -- text, highlight group + { e.section or "", "cheatMetadataSection" }, + { e.description or "", "cheatDescription" }, + { e.cheatcode or "", "cheatCode" }, + }) + end + + -- PR NOTE (Issue 5 - entry.tags may be nil): + -- Some sources may omit tags. Guard before concatenation. + local tags = (entry.tags and #entry.tags > 0) and table.concat(entry.tags, " ") or "" + + return { + value = entry, + -- generate the string that user sees as an item + display = make_display, + -- queries are matched against ordinal + ordinal = string.format( + "%s %s %s %s", + entry.section or "", + entry.description or "", + tags, + entry.cheatcode or "" + ), + } + end, + }), + attach_mappings = function(prompt_bufnr, map) + local mappings = require("cheatsheet.config").options.telescope_mappings + for keybind, action in pairs(mappings) do + map("i", keybind, function() + action(prompt_bufnr) + end) + end + + return true + end, + sorter = config.generic_sorter(telescope_opts), + }) + :find() end return M