current commit
260916.2 · chore(types): fix emmylua 0.25.1 lint warnings
local M = {}

---@diagnostic disable-next-line: deprecated
local uv = vim.uv or vim.loop
local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils"
local libuv = require "fzf-lua.libuv"
local devicons = require "fzf-lua.devicons"
local config ---@module 'fzf-lua.config'

-- attempt to load the current config
-- should fail if we're running headless
do
  local ok, module = pcall(require, "fzf-lua.config")
  if ok then config = module end
end

-- load config from our running instance
local function load_config()
  ---@diagnostic disable-next-line: undefined-field
  if not _G._fzf_lua_server then return end
  local ok, res = utils.rpcexec(_G._fzf_lua_server, "nvim_exec_lua",
    [[return FzfLua.libuv.serialize(FzfLua.config)]], {})
  if not ok then error(res) end ---@cast res string
  return libuv.deserialize(res --[[@as string]])
end

local function load_config_section(s, datatype, optional)
  if not _G._fzf_lua_is_headless then
    local val = utils.map_get(config, s)
    return type(val) == datatype and val or nil
    ---@diagnostic disable-next-line: undefined-field
  elseif _G._fzf_lua_server then
    -- load config from our running instance
    local is_bytecode = false
    local exec_str, exec_opts = nil, nil
    if datatype == "function" then
      is_bytecode = true
      exec_opts = { s, datatype }
      exec_str = "return require'fzf-lua'.config.bytecode(...)"
    else
      exec_opts = {}
      exec_str = ("return require'fzf-lua'.config.%s"):format(s)
    end
    local ok, errmsg = pcall(function()
      ---@diagnostic disable-next-line: undefined-field
      local chan_id = vim.fn.sockconnect("pipe", _G._fzf_lua_server, { rpc = true })
      res = vim.rpcrequest(chan_id, "nvim_exec_lua", exec_str, exec_opts)
      vim.fn.chanclose(chan_id)
    end)
    if ok and is_bytecode then
      ok, res = pcall(loadstring, res)
    end
    ---@diagnostic disable-next-line: undefined-field
    if _G._debug == "v" or _G._debug == 2 then
      ---@diagnostic disable-next-line: undefined-field
      io.stdout:write(("[DEBUG] [load_config] %s = %s" .. (_G._EOL or "\n"))
        :format(s, not ok and errmsg or res))
    end
    if not ok and not optional then
      io.stderr:write(("Error loading remote config section '%s': %s\n"):format(s, errmsg))
    elseif ok and type(res) == datatype then
      return res
    end
  end
end


local opts2 = setmetatable({}, {
  __index = function(_, k)
    if k == "fn_transform_cmd" then
      return load_config_section("__resume_data.opts.fn_transform_cmd", "function", true)
    end
    return utils.map_get(config, "__resume_data.opts." .. k)
  end
})

if _G._fzf_lua_is_headless then
  local _config = load_config() or {} ---@module 'fzf-lua.config'
  ---@diagnostic disable-next-line: missing-fields
  _config.globals = { git = {}, files = {}, grep = {} }
  _config.globals.git.icons = load_config_section("globals.git.icons", "table") or {}
  _config.globals.files.git_status_cmd =
      load_config_section("globals.files.git_status_cmd", "table")
      or { "git", "-c", "color.status=false", "--no-optional-locks", "status", "--porcelain=v1" }

  -- prioritize `opts.rg_glob_fn` over globals
  _config.globals.grep.rg_glob_fn = opts2.rg_glob_fn or
      load_config_section("globals.grep.rg_glob_fn", "function", true)

  _config.globals.nbsp = load_config_section("globals.nbsp", "string")
  if _config.globals.nbsp then utils.nbsp = _config.globals.nbsp end

  config = _config

  -- Compat global with known modules so we can use it in callbacks (fn_transform, etc)
  _G.FzfLua = {
    make_entry = M,
    config = config,
    path = path,
    utils = utils,
    libuv = libuv,
    devicons = devicons,
  }
end
assert(config.globals, "missing gloabals in fzf-lua config")

---@param opts table
---@return table<string, [string, true?]>
M.get_diff_files = function(opts)
  local diff_files = {}
  local cmd = opts.git_status_cmd or config.globals.files.git_status_cmd ---@cast cmd string[]
  if not cmd then return {} end
  local start = uv.hrtime()
  local ok, status, err = pcall(utils.io_systemlist, path.git_cwd(cmd, opts)) ---@cast status string[]
  local seconds = (uv.hrtime() - start) / 1e9
  if seconds >= 0.5 and opts.silent ~= true then
    local exec_str = string.format([[require"fzf-lua".utils.warn(]] ..
      [["'git status' took %.2f seconds, consider using `git_icons=false` in this repository or use `silent=true` to supress this message.")]]
      , seconds)
    if not _G._fzf_lua_is_headless then
      assert(loadstring(exec_str))()
    else
      ---@diagnostic disable-next-line: undefined-field
      local chan_id = vim.fn.sockconnect("pipe", _G._fzf_lua_server, { rpc = true })
      vim.rpcrequest(chan_id, "nvim_exec_lua", exec_str, {})
      vim.fn.chanclose(chan_id)
    end
  end
  if ok and err == 0 then
    for _, line in ipairs(status) do
      local icon = line:match("[MUDARCT?]+")
      local file = line:match("[^ ]*$")
      if icon and file then
        -- Extract first char, staged if not space or ? (32 or 63)
        local first = #line > 0 and string.byte(line, 1)
        local is_staged = first ~= 32 and first ~= 63 or nil
        diff_files[file] = { icon:gsub("%?%?", "?"), is_staged }
      end
    end
  end

  return diff_files
end

---@param query string
---@param opts table
---@return string search_query
---@return string? glob_args
---@return string[]? globs
M.glob_parse = function(query, opts)
  if not query or not query:find(opts.glob_separator) then
    return query, nil
  end
  local rg_glob_fn = opts.rg_glob_fn or config.globals.grep.rg_glob_fn
  if rg_glob_fn then
    return rg_glob_fn(query, opts)
  end
  local glob_args = ""
  local search_query, glob_str = query:match("(.*)" .. opts.glob_separator .. "(.*)")
  local globs = vim.split(glob_str or "", "%s+", { trimempty = true })
  for _, s in ipairs(globs) do
    if #s > 0 then
      glob_args = glob_args .. ("%s %s "):format(opts.glob_flag, libuv.shellescape(s))
    end
  end
  return search_query or "", glob_args, globs
end

-- reposition args before ` -e <pattern>` or ` -- <pattern>`
-- enables "-e" and "--fixed-strings --" in `rg_opts` (#781, #794)
---@param cmd string
---@param args string
---@param relocate_pattern string?
---@return string
M.rg_insert_args = function(cmd, args, relocate_pattern)
  local patterns = {}
  for _, a in ipairs({
    { "%s+%-e",  "-e" },
    { "%s+%-%-", "--" },
  }) do
    -- if pattern was specified search for `-e <pattern>`
    -- if pattern was not specified search for `-e<SPACE>` or `-e<EOL>`
    if relocate_pattern and #relocate_pattern > 0 then
      table.insert(patterns, {
        a[1] .. "%s-" .. relocate_pattern .. "%s*",
        a[2] .. " " .. relocate_pattern,
      })
    else
      table.insert(patterns, { a[1] .. "$", a[2] })
      table.insert(patterns, { a[1] .. "%s", a[2] })
    end
  end
  -- if pattern was specified also search for `<pattern>` directly
  if relocate_pattern and #relocate_pattern > 0 then
    table.insert(patterns, { "%s+" .. relocate_pattern .. "%s*", relocate_pattern })
  end
  for _, a in ipairs(patterns) do
    if cmd:match(a[1]) then
      cmd = cmd:gsub(a[1], " ")
      return string.format("%s %s %s", cmd:gsub("%s+$", ""), args:gsub("%s+$", ""), a[2])
    end
  end
  -- cmd doesn't contain `-e` or `--` or <pattern>, concat args
  return string.format("%s %s", cmd, args)
end


---@param s string[]
---@param opts table
---@return string cmd
M.lgrep = function(s, opts)
  -- can be nil when called as fzf initial command
  local query, no_esc = (function()
    -- $FZF_QUERY sends query args as table (#2343)
    if #s > 1 then
      return table.concat(vim.tbl_map(function(x) return libuv.shellescape(x) end, s), " "), 2
    else
      return (s[1] or ""), true
    end
  end)()
  opts.no_esc = nil
  local cmd0 = FzfLua.make_entry.get_grep_cmd(opts, query, no_esc)
  if not opts.exec_empty_query and #query == 0 then
    cmd0 = FzfLua.utils.shell_nop()
  elseif opts.silent_fail ~= false then
    cmd0 = cmd0 .. " || " .. FzfLua.utils.shell_nop()
  end
  if _G._fzf_lua_is_headless or not FzfLua.core.can_transform(opts) then
    return cmd0
  else
    return "reload:" .. cmd0
  end
end

---@param paths string[]
---@param globs? string[]
---@return string[]
local glob_filter = function(paths, globs)
  -- globpath() is very slow
  local matchers
  if not globs or not vim.regex then return paths end
  local pats = vim.tbl_map(utils.nil_wrap(vim.fn.glob2regpat), globs)
  ---@cast pats string[]
  matchers = vim.tbl_map(utils.nil_wrap(vim.regex) --[[@as fun(s: string): vim.regex]], pats)
  -- matchers = vim.tbl_map(vim.F.nil_wrap(vim.glob.to_lpeg), globs)
  if #matchers == 0 then return paths end
  return vim.tbl_filter(function(p)
    local stat = vim.uv.fs_stat(p) -- "--iglob" can filter directory correctly
    return (stat and stat.type == "directory") or
        ---@diagnostic disable-next-line: redundant-parameter, call-non-callable
        vim.iter(matchers):all(function(m) return m:match_str(p) end)
  end, paths)
end

---@param opts table
---@param search_query? string
---@param no_esc? boolean|number
---@return string?
M.get_grep_cmd = function(opts, search_query, no_esc)
  opts = _G._fzf_lua_is_headless and setmetatable(vim.deepcopy(opts), { __index = opts2 }) or opts
  if opts.raw_cmd and #opts.raw_cmd > 0 then
    return opts.raw_cmd
  end
  local command, is_rg, is_grep = nil, nil, nil
  if opts.cmd and #opts.cmd > 0 then
    command = opts.cmd
  elseif vim.fn.executable("rg") == 1 then
    is_rg = true
    command = string.format("rg %s", opts.rg_opts)
  elseif utils.__IS_WINDOWS then
    utils.warn("Grep requires installing 'rg' on Windows.")
    return nil
  else
    is_grep = true
    command = string.format("grep %s", opts.grep_opts)
  end
  for k, v in pairs({
    follow = opts.toggle_follow_flag or "-L",
    hidden = opts.toggle_hidden_flag or "--hidden",
    no_ignore = opts.toggle_ignore_flag or "--no-ignore",
  }) do
    (function()
      -- Do nothing unless opt was set
      if opts[k] == nil then return end
      command = utils.toggle_cmd_flag(command, v, opts[k])
    end)()
  end

  -- save a copy of the command for `actions.toggle_ignore`
  -- TODO: both `get_grep_cmd` and `get_files_cmd` need to
  -- be reworked into a table of arguments
  opts._cmd = command

  if opts.rg_glob and not command:match("^rg") then
    if not tonumber(opts.rg_glob) and not opts.silent then
      -- Do not display the error message if using the defaults (rg_glob=1)
      utils.warn("'--glob|iglob' flags require 'rg', ignoring 'rg_glob' option.")
    end
    opts.rg_glob = false
  end

  local globs
  if opts.fn_transform_cmd then
    local new_cmd, new_query = opts.fn_transform_cmd(search_query, command, opts)
    if new_cmd then
      opts.no_esc = true
      opts.search = new_query
      return new_cmd
    end
  elseif opts.rg_glob then
    search_query = search_query or ""
    local new_query, glob_args
    new_query, glob_args, globs = M.glob_parse(search_query, opts)
    if glob_args then
      -- since the search string mixes both the query and
      -- glob separators we cannot used unescaped strings
      if not (no_esc or opts.no_esc) then
        new_query = utils.rg_escape(new_query)
        opts.no_esc = true
        opts.search = ("%s%s"):format(new_query,
          search_query:match(opts.glob_separator .. ".*"))
      end
      search_query = new_query
      command = M.rg_insert_args(command, glob_args)
    end
  end

  -- filename takes precedence over directory
  -- filespec takes precedence over all and doesn't shellescape
  -- this is so user can send a file populating command instead
  local search_path = ""
  local print_filename_flags = " --with-filename" .. (is_rg and " --no-heading" or "")
  if opts.filespec and #opts.filespec > 0 then
    search_path = opts.filespec
  elseif opts.filename and #opts.filename > 0 then
    search_path = libuv.shellescape(opts.filename)
    command = M.rg_insert_args(command, print_filename_flags)
  elseif opts.search_paths then
    local search_paths = type(opts.search_paths) == "table"
        -- NOTE: deepcopy to avoid recursive shellescapes with `actions.grep_lgrep`
        and vim.deepcopy(opts.search_paths) or { tostring(opts.search_paths) }
    -- Make paths relative, note this will not work well with resuming if changing
    -- the cwd, this is by design for perf reasons as having to deal with full paths
    -- will result in more code rouets taken in `make_entry.file`
    for i, p in ipairs(search_paths) do
      search_paths[i] = path.relative_to(path.normalize(p), utils.cwd())
    end
    search_paths = opts.rg_glob and glob_filter(search_paths, globs) or search_paths
    search_path = table.concat(vim.tbl_map(libuv.shellescape, search_paths), " ")
    if is_grep then
      -- grep requires adding `-r` to command as paths can be either file or directory
      command = M.rg_insert_args(command, print_filename_flags .. " -r")
    elseif #search_paths == 1 then
      command = M.rg_insert_args(command, print_filename_flags)
    end
    if #search_paths == 0 then -- avoid grep search current directory
      command = M.rg_insert_args(command, "--max-depth=0")
    end
  end

  search_query = search_query or ""
  if #search_query > 0 and not (no_esc or opts.no_esc) then
    -- For UI consistency, replace the saved search query with the regex
    opts.no_esc = true
    opts.search = utils.rg_escape(search_query)
    search_query = opts.search
  end

  if not opts._ctags_file then
    -- Auto add `--line-number` for grep and `--line-number --column` for rg
    -- NOTE: although rg's `--column` implies `--line-number` we still add
    -- `--line-number` since we remove `--column` when search regex is empty
    local bin = path.tail(command:match("[^%s]+"))
    local bin2flags = {
      grep = { { "--line-number", "-n" }, { "--recursive", "-r" } },
      rg = { { "--line-number", "-n" }, { "--column" } }
    }
    for _, flags in ipairs(bin2flags[bin] or {}) do
      local has_flag_group
      for _, f in ipairs(flags) do
        if command:match("^" .. utils.lua_regex_escape(f))
            or command:match("%s+" .. utils.lua_regex_escape(f))
        then
          has_flag_group = true
        end
      end
      if not has_flag_group then
        if not opts.silent then
          utils.info(
            "Added missing '%s' flag to '%s'. Add 'silent=true' to hide this message.",
            table.concat(flags, "|"), bin)
        end
        command = M.rg_insert_args(command, flags[1])
      end
    end
  end

  -- remove column numbers when search term is empty
  if not opts.no_column_hide and #search_query == 0 then
    command = command:gsub("%s%-%-column", "")
  end

  -- do not escape at all
  if not (no_esc == 2 or opts.no_esc == 2) then
    -- we need to use our own version of 'shellescape'
    -- that doesn't escape '\' on fish shell (#340)
    search_query = libuv.shellescape(search_query)
  end

  ---@param cmd string
  ---@param fzf_field_index string
  ---@return string
  local expand_query = function(cmd, fzf_field_index)
    if _G._fzf_lua_is_headless and cmd:match("<query>") then
      return (cmd:gsub("<query>", fzf_field_index))
    else
      return ("%s %s"):format(cmd, fzf_field_index)
    end
  end

  -- construct the final command
  command = expand_query(command, search_query)
  command = ("%s %s"):format(command, search_path)

  -- piped command filter, used for filtering ctags
  if opts.filter and #opts.filter > 0 then
    command = ("%s | %s"):format(command, opts.filter)
  end
  command = M.fix_windows_cmd(command)

  return command
end


M.fix_windows_cmd = function(cmd)
  if not utils.__IS_WINDOWS or type(cmd) ~= "string" or not cmd:match("!") then
    return cmd
  end
  -- https://ss64.com/nt/syntax-esc.html
  -- This changes slightly if you are running with DelayedExpansion of variables:
  -- if any part of the command line includes an '!' then CMD will escape a second
  -- time, so ^^^^ will become ^
  -- replace in sections, only double the relevant pipe sections with !
  local escaped_cmd = {}
  for _, str in ipairs(utils.strsplit(cmd, "%s+|")) do
    if str:match("!") then
      str = str:gsub('[%(%)%%!%^<>&|"]', function(x)
        return "^" .. x
      end)
      -- make sure all ! are escaped at least twice
      str = str:gsub("[^%^]%^!", function(x)
        return x:sub(1, 1) .. "^" .. x:sub(2)
      end)
    end
    table.insert(escaped_cmd, str)
  end
  return table.concat(escaped_cmd, " |")
end

---@param opts table
---@param query string
---@param cmd string
---@return string
M.expand_query = function(opts, query, cmd)
  -- live_grep replace pattern with last argument
  local argvz = "<query>"
  if cmd:match(argvz) then
    -- The NEQ condition on Windows turned out to be a real pain in the butt
    -- so I decided to move the empty query test into our cmd proxy wrapper
    -- For obvious reasons this cannot work with `live_grep_native` and thus
    -- the NEQ condition remains for the "native" version
    if not opts.exec_empty_query and query == "" then
      -- query is always be the last argument
      cmd = utils.shell_nop()
      return cmd
    end

    -- For custom command transformations (#1927)
    opts.fn_transform_cmd = opts2.fn_transform_cmd

    -- did the caller request rg with glob support?
    -- manipulation needs to be done before the argv replacement
    if opts.fn_transform_cmd then
      local new_cmd, new_query = opts.fn_transform_cmd(query, cmd:gsub(argvz, ""), opts)
      cmd = new_cmd or cmd
      query = new_query or query
    elseif opts.rg_glob then
      local search_query, glob_args = M.glob_parse(query, opts)
      if glob_args then
        -- gsub doesn't like single % on rhs
        search_query = utils.lua_escape(search_query)
        -- reset argvz so it doesn't get replaced again below
        -- insert glob args before `-- {argvz}` or `-e {argvz}` repositioned
        -- at the end of the command preceding the search query (#781, #794)
        cmd = M.rg_insert_args(cmd, glob_args, argvz)
        query = search_query
      end
    end
    -- nifty hack to avoid having to double escape quotations
    -- see my comment inside 'live_grep' initial_command code
    cmd = cmd:gsub(argvz, #query > 0 and libuv.shellescape(query) or "")
  end
  return cmd
end

M.preprocess = function(opts)
  opts.cmd = M.fix_windows_cmd(opts.cmd)

  if opts.cwd_only and not opts.cwd then
    opts.cwd = utils.cwd()
  end

  if opts.file_icons then
    devicons.load()
  end

  if opts.git_icons then
    opts.diff_files = M.get_diff_files(opts)
  end

  -- formatter `to` function
  if opts.formatter and not opts._fmt then
    opts._fmt = opts._fmt or {}
    ---@diagnostic disable-next-line: inject-field, undefined-field
    opts._fmt.to = opts2._fmt.to
    -- Attempt to load from string value `_to`
    ---@diagnostic disable-next-line: undefined-field
    if not opts._fmt.to then
      local _to = opts2._fmt._to
      if type(_to) == "string" then
        ---@diagnostic disable-next-line: inject-field
        opts._fmt.to = assert(loadstring(_to))()
      end
    end
  end

  return opts
end

M.postprocess = function(opts)
  if opts.file_icons == "mini" and devicons.PLUGIN and devicons.PLUGIN.update_state_mini then
    devicons.PLUGIN:update_state_mini()
  end
end

---@param entry table
---@param opts table
---@return string
M.lcol = function(entry, opts)
  local hl_colnr = utils.tbl_contains(opts._cached_hls or {}, "path_colnr")
      and opts.hls.path_colnr or "blue"
  local hl_linenr = utils.tbl_contains(opts._cached_hls or {}, "path_linenr")
      and opts.hls.path_linenr or "green"
  local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr)
  return string.format("%s:%s%s%s",
    -- uncomment to test URIs
    -- "file://" .. filename,
    filename, --utils.ansi_codes.magenta(filename),
    tonumber(entry.lnum) == nil and "" or (utils.ansi_codes[hl_linenr](tostring(entry.lnum)) .. ":"),
    tonumber(entry.col) == nil and "" or (utils.ansi_codes[hl_colnr](tostring(entry.col)) .. ":"),
    type(entry.text) ~= "string" and ""
    or (" " .. (opts and opts.trim_entry and vim.trim(entry.text) or entry.text)))
end

---@param x string
---@param opts table
---@return string? entry
M.file = function(x, opts)
  opts = opts or {}
  local ret = {}
  local icon, hl
  local colon_start_idx = 1
  if utils.__IS_WINDOWS then
    if string.byte(x, #x) == 13 then
      -- strip ^M added by the "dir /s/b" command
      x = x:sub(1, #x - 1)
    end
    if path.is_absolute(x) then
      -- ignore the first colon in the drive spec, e.g c:\
      colon_start_idx = 3
    end
  end
  local colon_idx = x:find(":", colon_start_idx, true) or 0
  local file_part = colon_idx > 1 and x:sub(1, colon_idx - 1) or x
  local rest_of_line = colon_idx > 1 and x:sub(colon_idx) or nil
  -- strip ansi coloring from path so we can use filters
  -- otherwise the ANSI escape sequence will get in the way
  -- TODO: we only support path modification without ANSI
  -- escape sequences, it becomes too expensive to modify
  -- and restore the path with escape sequences
  local stripped_filepath, file_is_ansi = (function()
    if opts.no_ansi_colors then
      return file_part, 0
    else
      return utils.strip_ansi_coloring(file_part)
    end
  end)()
  ---@cast stripped_filepath-?
  local filepath = stripped_filepath
  ---@cast filepath string
  -- fd v8.3 requires adding '--strip-cwd-prefix' to remove
  -- the './' prefix, will not work with '--color=always'
  -- https://github.com/sharkdp/fd/blob/master/CHANGELOG.md
  if opts.strip_cwd_prefix then
    filepath = path.strip_cwd_prefix(filepath)
  end
  if opts.render_crlf then
    filepath = path.render_crlf(filepath)
  end
  if opts.absolute_path then
    -- make path absolute
    if not path.is_absolute(filepath) then
      filepath = path.join({ opts.cwd or utils.cwd(), filepath })
    end
  else
    -- make path relative
    filepath = path.relative_to(filepath, opts.cwd or utils.cwd())
  end
  if path.is_absolute(filepath) then
    -- filter for cwd only
    if opts.cwd_only then
      if not path.is_relative_to(filepath, opts.cwd or utils.cwd()) then
        return nil
      end
    end

    if not opts.absolute_path then
      -- replace $HOME with ~
      filepath = path.HOME_to_tilde(filepath)
    end
  end
  -- only check for ignored patterns after './' was
  -- stripped and path was transformed to relative
  if opts.file_ignore_patterns then
    for _, pattern in ipairs(opts.file_ignore_patterns) do
      if #pattern > 0 and filepath:match(pattern) then
        return nil
      end
    end
  end
  -- only shorten after we're done with all the filtering
  -- save a copy for git indicator and icon lookups
  local origpath = filepath
  if opts.path_shorten then
    filepath = path.shorten(filepath, tonumber(opts.path_shorten),
      -- On Windows we want to shorten using the separator used by the `cwd` arg
      -- otherwise we might have issues "lenghening" as in the case of git which
      -- uses normalized paths (using /) for `rev-parse --show-toplevel` and `ls-files`
      utils.__IS_WINDOWS and opts.cwd and path.separator(opts.cwd))
  end
  if opts.git_icons then
    ---@type [string, true?]
    local diff_info = opts.diff_files
        and opts.diff_files[utils._if_win(path.normalize(origpath), origpath)]
    local indicators = diff_info and diff_info[1] or " "
    for i = 1, #indicators do
      icon = indicators:sub(i, i)
      local git_icon = config.globals.git.icons[icon]
      if git_icon then
        icon = git_icon.icon
        if opts.color_icons then
          -- diff_info[2] contains 'is_staged' var, only the first indicator can be "staged"
          local git_color = diff_info[2] and i == 1 and "green" or git_icon.color or "dark_grey"
          icon = utils.ansi_codes[git_color](icon)
        end
      end
      ret[#ret + 1] = icon
    end
    ret[#ret + 1] = utils.nbsp
  end
  if opts.file_icons then
    icon, hl = devicons.get_devicon(origpath)
    if hl and opts.color_icons then
      icon = utils.ansi_from_rgb(hl, icon)
    end
    ret[#ret + 1] = icon
    ret[#ret + 1] = utils.nbsp
  end
  local _fmt_postfix -- when using `path.filename_first` v2
  if opts._fmt and type(opts._fmt.to) == "function" then
    ret[#ret + 1], _fmt_postfix = opts._fmt.to(filepath, opts, { path = path, utils = utils })
  else
    ret[#ret + 1] = file_is_ansi > 0
        -- filename is ansi escape colored, replace the inner string (#819)
        -- escape `%` in path, since `string.gsub` also use it in target (#1443)
        and file_part:gsub(utils.lua_regex_escape(stripped_filepath), utils.lua_escape(filepath))
        or filepath
  end
  -- multiline is only enabled with grep-like output PATH:LINE:COL:
  if opts.multiline and rest_of_line then
    opts.multiline = tonumber(opts.multiline) or 1
    -- Sould match both colored and non colored versions of
    -- PATH:LINE:TEXT and PATH:LINE:COL:TEXT
    local ansi_num = "[%[%d;m]"
    local filespec = rest_of_line:match(string.format("^:%s-:%s-:", ansi_num, ansi_num))
        or rest_of_line:match(string.format("^:%s-:", ansi_num))
    if filespec then
      rest_of_line = filespec
          .. "\n"
          .. string.rep(" ", 4)
          .. rest_of_line:sub(#filespec + 1)
    end
  end
  ret[#ret + 1] = rest_of_line
  ret[#ret + 1] = _fmt_postfix
  return table.concat(ret)
end

M.tag = function(x, opts)
  local name, file, excmd = x:match("([^\t]+)\t([^\t]+)\t(.*)")
  if not file or not name or not excmd then return x end
  local line, tag = path.parse_ctag_excmd(excmd, true)
  -- different alignment fmt if string contains ansi coloring
  -- from rg/grep output when using `tags_grep_xxx`
  local align = 24
  if utils.has_ansi_coloring(name) then align = align + 17 end
  return string.format("%-" .. tostring(align) .. "s\t%s\t%s%s",
    name,
    M.file(file, opts),
    line and utils.ansi_codes.green(tostring(line)) .. ";" or "",
    tag or ""
  )
end

M.git_status = function(x, opts)
  local function git_iconify(icon, staged)
    local git_icon = config.globals.git.icons[icon]
    if git_icon then
      icon = git_icon.icon
      if opts.color_icons then
        icon = utils.ansi_codes[staged and "green" or git_icon.color or "dark_grey"](icon)
      end
    end
    return icon
  end
  -- unrecognizable format, return
  if not x or #x < 4 then return x end
  -- strip ansi coloring or the pattern matching fails
  -- when git config has `color.status=always` (#706)
  x = utils.strip_ansi_coloring(x)
  -- `man git-status`
  -- we are guaranteed format of: XY <text>
  -- spaced files are wrapped with quotes
  -- remove both git markers and quotes
  local f1, f2 = x:sub(4):gsub([["]], ""), nil
  -- renames separate files with '->'
  if f1:match("%s%->%s") then
    ---@diagnostic disable-next-line: assign-type-mismatch
    f1, f2 = f1:match("(.*)%s%->%s(.*)")
  end
  ---@diagnostic disable-next-line: assign-type-mismatch
  f1 = f1 and M.file(f1, opts)
  -- accommodate 'file_ignore_patterns'
  if not f1 then return end
  f2 = f2 and M.file(f2, opts)
  local staged = git_iconify(x:sub(1, 1):gsub("?", " "), true)
  local unstaged = git_iconify(x:sub(2, 2))
  local entry = ("%s%s%s%s%s"):format(
    staged, utils.nbsp, unstaged, utils.nbsp .. utils.nbsp,
    (f2 and ("%s -> %s"):format(f1, f2) or f1))
  return entry
end

M.git_hunk = function(x, opts)
  local entry
  if not opts.__git_hunk_stats then
    opts.__git_hunk_stats = { i = math.huge / 2 }
  end
  -- local ref for easy access
  local S = opts.__git_hunk_stats
  do
    (function()
      local l = utils.strip_ansi_coloring(x)
      -- Skip the first 3 header lines, e.g:
      --    diff --git a/lua/fzf-lua/defaults.lua b/lua/fzf-lua/defaults.lua
      --    index 3354405..799e467 100644
      --    --- a/lua/fzf-lua/defaults.lua
      --    +++ b/lua/fzf-lua/defaults.lua
      if l:match("^diff") then S.i = 0 end
      if S.i < 3 then
        return
      end
      -- Extract filename from the "b-line", e.g:
      --  +++ b/lua/fzf-lua/defaults.lua
      -- NOTE: prefix can also appear as {i|w} (#2151)
      --  --- i/<file>
      --  +++ w/<file>
      if S.i == 3 then
        S.filename = l:match("^%+%+%+ %l/(.*)")
        return
      end
      -- Process only lines that start with + or -
      local byte = string.byte(l, 1)
      if byte == 43 or byte == 45 then
        entry = string.format("%s:%d:%s", M.file(S.filename, opts), S.line, x)
      elseif byte == 64 then
        -- Extract line number
        S.line = tonumber(l:match("^@@ %-%d+,%d+ %+(%d+),%d+ @@"))
      end
      -- Advance line number for non-modified or added lines
      if byte == 32 or byte == 43 then
        S.line = S.line + 1
      end
    end)()
  end
  -- Next line
  S.i = S.i + 1
  return entry
end

M.zoxide = function(x, opts)
  local score, dir = x:match("(%d+%.%d+)%s+(.-)$")
  if not score then return x end
  if opts.cwd then
    dir = path.relative_to(dir, opts.cwd)
  end
  local _fmt_postfix -- when using `path.filename_first` v2
  if opts._fmt and type(opts._fmt.to) == "function" then
    dir, _fmt_postfix = opts._fmt.to(dir, opts, { path = path, utils = utils })
  end
  return string.format("%8s\t%s%s", tostring(score), dir, _fmt_postfix or "")
end

return M