|
| 1 | +local M = {} |
| 2 | + |
| 3 | +local conf = require('fff.conf') |
| 4 | +local file_picker = require('fff.file_picker') |
| 5 | + |
| 6 | +---@class FFFSnacksState |
| 7 | +---@field current_file_cache? string |
| 8 | +---@field config table FFF config |
| 9 | +M.state = { config = {} } |
| 10 | + |
| 11 | +local staged_status = { |
| 12 | + staged_new = true, |
| 13 | + staged_modified = true, |
| 14 | + staged_deleted = true, |
| 15 | + renamed = true, |
| 16 | +} |
| 17 | + |
| 18 | +local status_map = { |
| 19 | + untracked = 'untracked', |
| 20 | + modified = 'modified', |
| 21 | + deleted = 'deleted', |
| 22 | + renamed = 'renamed', |
| 23 | + staged_new = 'added', |
| 24 | + staged_modified = 'modified', |
| 25 | + staged_deleted = 'deleted', |
| 26 | + ignored = 'ignored', |
| 27 | + -- clean = "", |
| 28 | + -- clear = "", |
| 29 | + unknown = 'untracked', |
| 30 | +} |
| 31 | + |
| 32 | +--- tweaked version of `Snacks.picker.format.file_git_status` |
| 33 | +--- @type snacks.picker.format |
| 34 | +local function format_file_git_status(item, picker) |
| 35 | + local ret = {} ---@type snacks.picker.Highlight[] |
| 36 | + local status = item.status |
| 37 | + |
| 38 | + local hl = 'SnacksPickerGitStatus' |
| 39 | + if status.unmerged then |
| 40 | + hl = 'SnacksPickerGitStatusUnmerged' |
| 41 | + elseif status.staged then |
| 42 | + hl = 'SnacksPickerGitStatusStaged' |
| 43 | + else |
| 44 | + hl = 'SnacksPickerGitStatus' .. status.status:sub(1, 1):upper() .. status.status:sub(2) |
| 45 | + end |
| 46 | + |
| 47 | + local icon = picker.opts.icons.git[status.status] |
| 48 | + if status.staged then icon = picker.opts.icons.git.staged end |
| 49 | + |
| 50 | + local text_icon = status.status:sub(1, 1):upper() |
| 51 | + text_icon = status.status == 'untracked' and '?' or status.status == 'ignored' and '!' or text_icon |
| 52 | + |
| 53 | + ret[#ret + 1] = { icon, hl } |
| 54 | + ret[#ret + 1] = { ' ', virtual = true } |
| 55 | + |
| 56 | + ret[#ret + 1] = { |
| 57 | + col = 0, |
| 58 | + virt_text = { { text_icon, hl }, { ' ' } }, |
| 59 | + virt_text_pos = 'right_align', |
| 60 | + hl_mode = 'combine', |
| 61 | + } |
| 62 | + return ret |
| 63 | +end |
| 64 | + |
| 65 | +---@type snacks.picker.Config |
| 66 | +M.source = { |
| 67 | + title = 'FFFiles', |
| 68 | + finder = function(opts, ctx) |
| 69 | + -- initialization code from require('fff.picker_ui').open |
| 70 | + -- on_show does not seem to be called before finder |
| 71 | + if not M.state.current_file_cache then |
| 72 | + local current_buf = vim.api.nvim_get_current_buf() |
| 73 | + if current_buf and vim.api.nvim_buf_is_valid(current_buf) then |
| 74 | + local current_file = vim.api.nvim_buf_get_name(current_buf) |
| 75 | + if current_file ~= '' and vim.fn.filereadable(current_file) == 1 then |
| 76 | + M.state.current_file_cache = current_file |
| 77 | + else |
| 78 | + M.state.current_file_cache = nil |
| 79 | + end |
| 80 | + end |
| 81 | + end |
| 82 | + if not file_picker.is_initialized() then |
| 83 | + if not file_picker.setup() then |
| 84 | + vim.notify('Failed to initialize file picker', vim.log.levels.ERROR) |
| 85 | + return {} |
| 86 | + end |
| 87 | + end |
| 88 | + local config = conf.get() |
| 89 | + M.state.config = vim.tbl_deep_extend('force', config or {}, opts or {}) |
| 90 | + |
| 91 | + local fff_result = file_picker.search_files( |
| 92 | + ctx.filter.search, |
| 93 | + opts.limit or M.state.config.max_results, |
| 94 | + M.state.config.max_threads, |
| 95 | + M.state.current_file_cache, |
| 96 | + false |
| 97 | + ) |
| 98 | + |
| 99 | + ---@type snacks.picker.finder.Item[] |
| 100 | + local items = {} |
| 101 | + for _, fff_item in ipairs(fff_result) do |
| 102 | + ---@type snacks.picker.finder.Item |
| 103 | + local item = { |
| 104 | + text = fff_item.name, |
| 105 | + file = fff_item.path, |
| 106 | + score = fff_item.total_frecency_score, |
| 107 | + -- HACK: in original snacks implementation status is a string of |
| 108 | + -- `git status --porcelain` output |
| 109 | + status = status_map[fff_item.git_status] and { |
| 110 | + status = status_map[fff_item.git_status], |
| 111 | + staged = staged_status[fff_item.git_status] or false, |
| 112 | + unmerged = fff_item.git_status == 'unmerged', |
| 113 | + }, |
| 114 | + } |
| 115 | + items[#items + 1] = item |
| 116 | + end |
| 117 | + |
| 118 | + return items |
| 119 | + end, |
| 120 | + format = function(item, picker) |
| 121 | + ---@type snacks.picker.Highlight[] |
| 122 | + local ret = {} |
| 123 | + |
| 124 | + if item.label then |
| 125 | + ret[#ret + 1] = { item.label, 'SnacksPickerLabel' } |
| 126 | + ret[#ret + 1] = { ' ', virtual = true } |
| 127 | + end |
| 128 | + |
| 129 | + if item.status then |
| 130 | + vim.list_extend(ret, format_file_git_status(item, picker)) |
| 131 | + else |
| 132 | + ret[#ret + 1] = { ' ', virtual = true } |
| 133 | + end |
| 134 | + |
| 135 | + vim.list_extend(ret, require('snacks').picker.format.filename(item, picker)) |
| 136 | + |
| 137 | + if item.line then |
| 138 | + require('snacks').picker.highlight.format(item, item.line, ret) |
| 139 | + table.insert(ret, { ' ' }) |
| 140 | + end |
| 141 | + return ret |
| 142 | + end, |
| 143 | + on_close = function() M.state.current_file_cache = nil end, |
| 144 | + formatters = { |
| 145 | + file = { |
| 146 | + filename_first = true, |
| 147 | + }, |
| 148 | + }, |
| 149 | + live = true, |
| 150 | +} |
| 151 | + |
| 152 | +return M |
0 commit comments