nvim-telescope / telescope.nvim

Find, Filter, Preview, Pick. All lua, all the time.
MIT License
15.38k stars 824 forks source link

[Request] Open a picker with a given width and height - e.g. spell_suggest #3245

Closed Ajaymamtora closed 1 month ago

Ajaymamtora commented 1 month ago

Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

I use the nui hybrid layout from the recipe section and I use most pickers taking up most of the screen. I dont want certain pickers to do this however, for example if I trigger spell suggest I want it to open a smaller picker.

Describe the solution you'd like A clear and concise description of what you want to happen.

Allow for configuring specific sizes on open based on picker, e.g. when calling setup:

...
     pickers = {
      spell_suggest = {
        layout_config = {
          width = 0.3,
          height = 0.3,
        },
      },
....

Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.

I can't figure out any alternatives. I tried checking the picker in create_layout but the only unique info I could find was the title of the picker which is a terrible way of doing it.

Additional context Add any other context or screenshots about the feature request here.

Can't see anything while trying to suggest a spelling:

image

But I do want a large picker when finding files for example:

image

Ajaymamtora commented 1 month ago

Solved:

-- Update the hybrid layout code
function M.nui_hybrid_layout_fn(picker)
  local Layout = require("nui.layout")
  local Popup = require("nui.popup")

  local telescope = require("telescope")
  local TSLayout = require("telescope.pickers.layout")

  local function make_popup(options)
    local popup = Popup(options)
    function popup.border:change_title(title)
      popup.border.set_text(popup.border, "top", title)
    end
    return TSLayout.Window(popup)
  end

  local border = {
    results = {
      top_left = "┌",
      top = "─",
      top_right = "┬",
      right = "│",
      bottom_right = "",
      bottom = "",
      bottom_left = "",
      left = "│",
    },
    results_patch = {
      minimal = {
        top_left = "┌",
        top_right = "┐",
      },
      horizontal = {
        top_left = "┌",
        top_right = "┬",
      },
      vertical = {
        top_left = "├",
        top_right = "┤",
      },
    },
    prompt = {
      top_left = "├",
      top = "─",
      top_right = "┤",
      right = "│",
      bottom_right = "┘",
      bottom = "─",
      bottom_left = "└",
      left = "│",
    },
    prompt_patch = {
      minimal = {
        bottom_right = "┘",
      },
      horizontal = {
        bottom_right = "┴",
      },
      vertical = {
        bottom_right = "┘",
      },
    },
    preview = {
      top_left = "┌",
      top = "─",
      top_right = "┐",
      right = "│",
      bottom_right = "┘",
      bottom = "─",
      bottom_left = "└",
      left = "│",
    },
    preview_patch = {
      minimal = {},
      horizontal = {
        bottom = "─",
        bottom_left = "",
        bottom_right = "┘",
        left = "",
        top_left = "",
      },
      vertical = {
        bottom = "",
        bottom_left = "",
        bottom_right = "",
        left = "│",
        top_left = "┌",
      },
    },
  }

  local results = make_popup({
    focusable = false,
    border = {
      style = border.results,
      text = {
        top = picker.results_title,
        top_align = "center",
      },
    },
    win_options = {
      winhighlight = "Normal:Normal",
    },
  })

  local prompt = make_popup({
    enter = true,
    border = {
      style = border.prompt,
      text = {
        top = picker.prompt_title,
        top_align = "center",
      },
    },
    win_options = {
      winhighlight = "Normal:Normal",
    },
  })

  local preview = make_popup({
    focusable = false,
    border = {
      style = border.preview,
      text = {
        top = picker.preview_title,
        top_align = "center",
      },
    },
  })

  local box_by_kind = {
    vertical = function()
      if picker.previewer then
        return Layout.Box({
          Layout.Box(preview, { grow = 1 }),
          Layout.Box(results, { grow = 1 }),
          Layout.Box(prompt, { size = 3 }),
        }, { dir = "col" })
      else
        return Layout.Box({
          Layout.Box(results, { grow = 1 }),
          Layout.Box(prompt, { size = 3 }),
        }, { dir = "col" })
      end
    end,
    horizontal = function()
      if picker.previewer then
        return Layout.Box({
          Layout.Box({
            Layout.Box(results, { grow = 1 }),
            Layout.Box(prompt, { size = 3 }),
          }, { dir = "col", size = "50%" }),
          Layout.Box(preview, { size = "50%" }),
        }, { dir = "row" })
      else
        return Layout.Box({
          Layout.Box(results, { grow = 1 }),
          Layout.Box(prompt, { size = 3 }),
        }, { dir = "col" })
      end
    end,
    minimal = Layout.Box({
      Layout.Box(results, { grow = 1 }),
      Layout.Box(prompt, { size = 3 }),
    }, { dir = "col" }),
  }

  local function get_box()
    local strategy = picker.layout_strategy
    if strategy == "vertical" or strategy == "horizontal" then
      return box_by_kind[strategy](), strategy
    end

    local height, width = vim.o.lines, vim.o.columns
    local box_kind = "horizontal"
    if width < 150 then
      box_kind = "vertical"
      if height < 40 then
        box_kind = "minimal"
      end
    end
    return (box_by_kind[box_kind] and box_by_kind[box_kind]()) or box_by_kind.minimal, box_kind
  end

  local function prepare_layout_parts(layout, box_type)
    layout.results = results
    results.border:set_style(border.results_patch[box_type])

    layout.prompt = prompt
    prompt.border:set_style(border.prompt_patch[box_type])

    if box_type == "minimal" or not picker.previewer then
      layout.preview = nil
    else
      layout.preview = preview
      preview.border:set_style(border.preview_patch[box_type])
    end
  end

  local function get_layout_size(box_kind)
    return picker.layout_config[box_kind == "minimal" and "vertical" or box_kind].size
  end

  local box, box_kind = get_box()
  local layout = Layout({
    relative = "editor",
    position = "50%",
    size = get_layout_size(box_kind),
  }, box)

  layout.picker = picker

  prepare_layout_parts(layout, box_kind)

  local layout_update = layout.update
  function layout:update()
    local box, box_kind = get_box()
    prepare_layout_parts(layout, box_kind)
    layout_update(self, { size = get_layout_size(box_kind) }, box)
  end

  return TSLayout(layout)
end

...
-- call like this
M.small_pickers = {
  "spell_suggest",
}

--- Calls a Telescope builtin picker with custom options for small pickers
---@param pickername string The name of the Telescope builtin picker
---@param opts table|nil Optional table of options to pass to the picker
function M.builtin(pickername, opts)
  if not pickername then
    return
  end

  local small_layout = {
    layout_strategy = "vertical",
    previewer = false,
    layout_config = {
      previewer = false,
      vertical = {
        previewer = false,
        size = {
          height = "40%",
          width = "40%",
        },
      },
    },
  }

  local builtin = require("telescope.builtin")[pickername]

  if vim.tbl_contains(M.small_pickers, pickername) then
    opts = vim.tbl_deep_extend("force", opts or {}, small_layout)
  end

  builtin(opts)
end