|
| 1 | +--@class telescope |
| 2 | +local M = {} |
| 3 | + |
| 4 | +local pickers = require("telescope.pickers") |
| 5 | +local finders = require("telescope.finders") |
| 6 | +local conf = require("telescope.config").values |
| 7 | +local actions = require("telescope.actions") |
| 8 | +local action_state = require("telescope.actions.state") |
| 9 | +local previewers = require("telescope.previewers") |
| 10 | +local putils = require("telescope.previewers.utils") |
| 11 | + |
| 12 | +local utils = require("taskfile.utils") |
| 13 | +local ui = require("taskfile.ui") |
| 14 | + |
| 15 | +local C = utils.const |
| 16 | + |
| 17 | +-- cache constants |
| 18 | +local TASK_NAME_DESC_GAP = C.TASK_NAME_DESC_GAP |
| 19 | +local MIN_PREVIEW_HEIGHT = C.MIN_PREVIEW_HEIGHT |
| 20 | +local MIN_PREVIEW_WIDTH = C.MIN_PREVIEW_WIDTH |
| 21 | +local NO_WRAP_WIDTH = C.NO_WRAP_WIDTH |
| 22 | +local TELESCOPE_HEIGHT = C.TELESCOPE_HEIGHT |
| 23 | +local TELESCOPE_WIDTH = C.TELESCOPE_WIDTH |
| 24 | +local SELECTION_CARET = C.SELECTION_CARET |
| 25 | + |
| 26 | +M.const = C |
| 27 | + |
| 28 | +---@class TelescopeLayoutConfig |
| 29 | +---@field width integer Total window width |
| 30 | +---@field height integer Total window height |
| 31 | +---@field preview_width? integer Horizontal: Specific width for preview |
| 32 | +---@field preview_height? integer Vertical: Specific height for preview |
| 33 | +---@field prompt_position? "top"|"bottom" |
| 34 | +---@field mirror? boolean If true, mirrors the layout (prompt/list on top in vertical) |
| 35 | +---@field label_width? integer Cached max width of task labels |
| 36 | + |
| 37 | +--- Calculate layout to match native behavior |
| 38 | +---@param opts table Taskfile configuration |
| 39 | +---@param tasks table List of tasks |
| 40 | +---@return string strategy, TelescopeLayoutConfig layout_conf |
| 41 | +local function get_layout_config(opts, tasks) |
| 42 | + opts = opts or {} |
| 43 | + local list_conf = (opts.windows and opts.windows.list) or {} |
| 44 | + |
| 45 | + local strategy = opts.layout or "horizontal" |
| 46 | + |
| 47 | + local total_width, total_height, _, _ = utils.calculate_dimensions(list_conf.width, list_conf.height) |
| 48 | + local label_width = utils.max_task_label_length(tasks) |
| 49 | + |
| 50 | + ---@type TelescopeLayoutConfig |
| 51 | + local layout_conf = { |
| 52 | + width = total_width, |
| 53 | + height = total_height, |
| 54 | + label_width = label_width, |
| 55 | + } |
| 56 | + |
| 57 | + if strategy == "vertical" then |
| 58 | + layout_conf.mirror = true |
| 59 | + layout_conf.prompt_position = "bottom" |
| 60 | + |
| 61 | + local available_content_h = math.max(0, total_height - TELESCOPE_HEIGHT) |
| 62 | + |
| 63 | + local list_h = |
| 64 | + ui.calculate_list_height(tasks, list_conf.height_ratio, available_content_h, total_width, label_width) |
| 65 | + local preview_h = available_content_h - list_h |
| 66 | + |
| 67 | + if preview_h < MIN_PREVIEW_HEIGHT then |
| 68 | + preview_h = MIN_PREVIEW_HEIGHT |
| 69 | + end |
| 70 | + |
| 71 | + layout_conf.preview_height = preview_h |
| 72 | + else |
| 73 | + layout_conf.prompt_position = "bottom" |
| 74 | + |
| 75 | + local available_content_w = math.max(0, total_width - TELESCOPE_WIDTH) |
| 76 | + local list_w = ui.calculate_list_width(tasks, list_conf.width_ratio, available_content_w, label_width) |
| 77 | + |
| 78 | + local preview_w = available_content_w - list_w |
| 79 | + layout_conf.preview_width = math.max(MIN_PREVIEW_WIDTH, preview_w) |
| 80 | + end |
| 81 | + |
| 82 | + return strategy, layout_conf |
| 83 | +end |
| 84 | + |
| 85 | +local function task_previewer() |
| 86 | + return previewers.new_buffer_previewer({ |
| 87 | + title = "Preview", |
| 88 | + define_preview = function(self, entry) |
| 89 | + local cmd = "task " .. entry.value.name .. " --dry" |
| 90 | + local output = vim.fn.system(cmd) |
| 91 | + local lines = vim.split(output, "\n") |
| 92 | + local cleaned_lines = utils.clean_dry_output(lines) |
| 93 | + |
| 94 | + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, cleaned_lines) |
| 95 | + putils.highlighter(self.state.bufnr, "sh") |
| 96 | + end, |
| 97 | + }) |
| 98 | +end |
| 99 | + |
| 100 | +M.pick_task = function(tasks, run_callback, plugin_opts) |
| 101 | + local strategy, layout_conf = get_layout_config(plugin_opts, tasks) |
| 102 | + |
| 103 | + pickers |
| 104 | + .new({}, { |
| 105 | + prompt_title = "Find Task", |
| 106 | + results_title = "Tasks", |
| 107 | + selection_caret = SELECTION_CARET, |
| 108 | + |
| 109 | + finder = finders.new_table({ |
| 110 | + results = tasks, |
| 111 | + entry_maker = function(task) |
| 112 | + local name = task.name or "" |
| 113 | + local desc = task.desc or "" |
| 114 | + |
| 115 | + -- we pass NO_WRAP_WIDTH to effectively disable wrapping. |
| 116 | + local formatted_lines = |
| 117 | + utils.format_task_lines(name, desc, layout_conf.label_width, NO_WRAP_WIDTH, TASK_NAME_DESC_GAP) |
| 118 | + |
| 119 | + return { |
| 120 | + value = task, |
| 121 | + display = formatted_lines[1], |
| 122 | + ordinal = name .. " " .. desc, |
| 123 | + } |
| 124 | + end, |
| 125 | + }), |
| 126 | + |
| 127 | + sorter = conf.generic_sorter({}), |
| 128 | + previewer = task_previewer(), |
| 129 | + |
| 130 | + layout_strategy = strategy, |
| 131 | + layout_config = layout_conf, |
| 132 | + |
| 133 | + attach_mappings = function(prompt_bufnr) |
| 134 | + actions.select_default:replace(function() |
| 135 | + actions.close(prompt_bufnr) |
| 136 | + local selection = action_state.get_selected_entry() |
| 137 | + if selection and selection.value then |
| 138 | + run_callback(selection.value.name) |
| 139 | + end |
| 140 | + end) |
| 141 | + return true |
| 142 | + end, |
| 143 | + }) |
| 144 | + :find() |
| 145 | +end |
| 146 | + |
| 147 | +return M |
0 commit comments