aboutsummaryrefslogtreecommitdiff
path: root/lua/denote-fzf-lua.lua
blob: 977bb23524140e1276f0e4c1c1e0810252c2a4cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
local M = {}

local config = require("denote-fzf-lua.config")
local fzflua = require("fzf-lua")
local builtin = require("fzf-lua.previewer.builtin")

local FIELDS = { "path", "date", "time", "sig", "title", "keywords", "ext" }
local lua_dir = debug.getinfo(1, "S").source:match("@?(.*/)")
local script_dir = lua_dir .. "denote-fzf-lua/scripts/"

local function format_field(field, delim)
  if field == "." then return "" end
  return delim .. delim .. field:gsub("%s", delim)
end

function M.format_fzf_with_nth(options)
  local indices = {}
  for i, field in ipairs(FIELDS) do
    if options.search_fields[field] then
      table.insert(indices, i)
    end
  end
  return table.concat(indices, ",")
end

function M.has_prereqs(force_slow)
  if vim.fn.executable("fzf") ~= 1 then return "no" end
  if force_slow then return "partial" end
  for _, cmd in ipairs({ "fd", "sd", "qsv" }) do
    if vim.fn.executable(cmd) ~= 1 then return "partial" end
  end
  return "yes"
end

function M.get_script(force_slow)
  local prereqs = M.has_prereqs(force_slow)
  if prereqs == "no" then
    error("Missing dependency: fzf")
    return
  elseif prereqs == "yes" then
    return script_dir .. "search_files.sh"
  else
    return script_dir .. "fallback_search_files.sh"
  end
end

function M.reconstruct_filename(row)
  if not row or row == "" then return "" end
  row = row:gsub("%s+$", "")
  local parts = {}
  row = row:gsub("%s%s+", "|")
  parts.path, parts.year, parts.month, parts.day, parts.hour, parts.min, parts.sec, parts.sig, parts.title, parts.keywords, parts.ext =
    row:match("^(.*/)|(%d%d%d%d)%-(%d%d)%-(%d%d)|(%d%d):(%d%d):(%d%d)|([^|]+)|([^|]+)|([^|]+)|(%..+)")
  if not parts.path then return "" end
  local sig      = format_field(parts.sig, "=")
  local title    = format_field(parts.title, "-")
  local keywords = format_field(parts.keywords, "_")
  return parts.path .. parts.year .. parts.month .. parts.day .. "T" .. parts.hour .. parts.min .. parts.sec .. sig .. title .. keywords .. parts.ext
end

M.reconstruct_previewer = builtin.buffer_or_file:extend()

function M.reconstruct_previewer:new(o, opts, fzf_win)
  M.reconstruct_previewer.super.new(self, o, opts, fzf_win)
  setmetatable(self, M.reconstruct_previewer)
  return self
end

function M.reconstruct_previewer:parse_entry(entry_str)
  return {
    path = M.reconstruct_filename(entry_str),
    line = 1,
    col = 1,
  }
end

function M.search_files(options)
  local script = M.get_script(options.force_slow_mode)
  if not script then return end

  local opts = {}
  opts.previewer = M.reconstruct_previewer
  opts.fzf_opts = vim.deepcopy(options.fzf_lua_opts.fzf_opts)
  opts.fzf_opts["--with-nth"] = M.format_fzf_with_nth(options)
  opts.fzf_opts["--header-lines"] = 1
  opts.fzf_opts["--delimiter"] = "\\s{2,}"
  opts.actions = {
    default = function(selected)
      local filename = M.reconstruct_filename(selected[1])
      vim.api.nvim_command("edit " .. vim.fn.fnameescape(filename))
    end,
  }
  local dir = vim.fn.expand(vim.g.denote_dir or options.dir)
  fzflua.fzf_exec(script .. " " .. dir, opts)
end

function M.search_contents(options)
  if vim.fn.executable("rg") ~= 1 then
    vim.notify("denote-fzf-lua: ripgrep is required for content search", vim.log.levels.WARN)
    return
  end
  local opts = {}
  opts.cwd = vim.fn.expand(vim.g.denote_dir or options.dir)
  opts.previewer = "builtin"
  opts.fzf_opts = options.fzf_lua_opts.fzf_opts
  opts.actions = fzflua.defaults.actions.files
  fzflua.fzf_live(function(query)
    local q = type(query) == "table" and table.concat(query, " ") or query or ""
    return "rg --column --color=always -i -- " .. vim.fn.shellescape(q)
  end, opts)
end

function M.load_cmd(options)
  vim.api.nvim_create_user_command("DenoteSearch", function(opts)
    if opts.fargs[1] == "files" then
      M.search_files(options)
    elseif opts.fargs[1] == "contents" then
      M.search_contents(options)
    else
      error("Unsupported operation " .. opts.fargs[1])
    end
  end, {
    nargs = 1,
    complete = function()
      return { "files", "contents" }
    end,
  })
end

function M.setup(options)
  options = vim.tbl_deep_extend("force", config.defaults, options or {})
  fzflua.setup(options.fzf_lua_opts)
  M.load_cmd(options)
end

return M