aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md87
-rw-r--r--lua/denote-fzf-lua.lua157
-rw-r--r--lua/denote-fzf-lua/config.lua67
-rwxr-xr-xlua/denote-fzf-lua/scripts/fallback_search_files.sh15
4 files changed, 166 insertions, 160 deletions
diff --git a/README.md b/README.md
index e9ff351..b769f49 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,95 @@
# denote-fzf-lua
-Neovim plugin that uses `fzf-lua` to search a directory of notes formatted with the Emacs Denote file-naming scheme:
+Neovim plugin that uses [`fzf-lua`](https://github.com/ibhagwan/fzf-lua) to search a directory of notes formatted with the Emacs Denote file-naming scheme:
`DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION`
-The nice custom table for Denote files is the only unique 'feature' of this plugin. If you don't care about that you should just use `fzf-lua` on its own.
+The nice custom table for Denote files is the only unique feature of this plugin. If you don't care about that you should just configure `fzf-lua` alone however you like it.
+
+# Installation / Config
+
+Example config via [lazy.nvim](https://github.com/folke/lazy.nvim)
+
+```lua
+{
+ "DefaultGen/denote-fzf-lua",
+ opts = {
+ dir = "~/notes" -- Denote notes directory
+
+ -- Toggle which Denote fields are displayed in search
+ search_fields = {
+ path = false,
+ date = true,
+ time = false,
+ sig = false,
+ title = true,
+ tags = true,
+ ext = false,
+ },
+
+ -- OPTIONAL:
+ -- fzf-lua plugin options. Check fzf-lua docs for full details.
+ -- Use this to set custom window and fzf options (and more)
+ fzf_lua_opts = {
+ winopts = {
+ height = 0.85,
+ width = 0.80,
+ row = 0.35,
+ col = 0.50,
+ preview = {
+ layout = 'vertical', -- vertical, horizontal, or flex
+ vertical = 'down:45%', -- Alt: horizontal = 'right:50%'
+ },
+ },
+ -- Options sent to fzf. If you don't include these, it will be
+ -- set to the defaults below (which look like the screenshot)
+ fzf_opts = {
+ ['--reverse'] = true,
+ ['--no-info'] = true,
+ ['--no-separator'] = true,
+ ['--no-hscroll'] = true,
+ ['-i'] = true,
+ },
+ },
+ },
+},
+```
## Dependencies
-* `fzf` - Fuzzy finder
-* `ripgrep` - Fast `grep` replacement
-* `fd` (OPTIONAL) - Fast `find` replacement
-* `sd` (OPTIONAL) - Fast `sed` replacement
-* `qsv` (OPTIONAL) - Fast `column` replacement
+* [`ibhagwan/fzf-lua`](https://github.com/ibhagwan/fzf-lua) - Neovim plugin
+* `fzf` - Fuzzy finder
+* (OPTIONAL) `fd` - Fast `find` replacement
+* (OPTIONAL) `sd` - Fast `sed` replacement
+* (OPTIONAL) `qsv`- Fast `column` replacement
+* (OPTIONAL) `bat` - Nicer preview than `cat`
+* (OPTIONAL) `ripgrep` - Required to search note contents
```
-Arch Linux: sudo pacman -S fzf ripgrep fd sd
+Arch Linux: sudo pacman -S fzf ripgrep fd sd bat
yay -S qsv-bin
-Debian: sudo apt install fzf ripgrep fd sd
+Debian: sudo apt install fzf ripgrep fd-find sd bat
+ export PATH=/usr/lib/cargo/bin/:$PATH (to add `fd` to path)
-qsv is available here: https://github.com/jqnatividad/qsv
+qsv: https://github.com/jqnatividad/qsv
```
-If the optional dependencies are missing `denote-fzf-lua` falls back to standard Unix tools.
-
-As a rough benchmark, the Rust tools can perform a search through 10k notes in 0.1s vs. 0.2s for standard tools on my PC. For 100k notes it's 0.6s vs. 2.1s.
+If the optional dependencies are missing `denote-fzf-lua` falls back to standard Unix tools. The Rust tools are 2-3x faster. On my PC this is a difference of 0.1s vs 0.2s for 10k notes, or 0.6s vs 1.9s for 100k notes.
`qsv` is used over the smaller, more ubiquitous `xsv` because it can table format very large inputs (e.g. 100k notes) without errors.
+# :DenoteSearch command
+
+```vim
+" Searches filenames in `dir` with fzf and displays results as a table
+:DenoteSearch files
+
+" Standard rg search through file contents in `dir`.
+" Nothing special about this, it's just a convenient command that uses the same fzf-lua options
+:DenoteSearch contents
+```
+
# License
GNU AGPL (`fzf-lua` license)
diff --git a/lua/denote-fzf-lua.lua b/lua/denote-fzf-lua.lua
index 3e68c40..846361d 100644
--- a/lua/denote-fzf-lua.lua
+++ b/lua/denote-fzf-lua.lua
@@ -2,25 +2,52 @@ local M = {}
local config = require("denote-fzf-lua.config")
local fzflua = require('fzf-lua')
+local builtin = require("fzf-lua.previewer.builtin")
----@param options table of user options
---- Check for dependencies and optional dependencies
-function M.has_prereqs(options)
+--- fzf-lua custom previewer (recombines search result fields into a filename)
+M.recombine_previewer = builtin.buffer_or_file:extend()
+
+function M.recombine_previewer:new(o, opts, fzf_win)
+ M.recombine_previewer.super.new(self, o, opts, fzf_win)
+ setmetatable(self, M.recombine_previewer)
+ return self
+end
+
+function M.recombine_previewer:parse_entry(entry_str)
+ local path = entry_str:match("([^:]+:%d%d:[^:]+):?")
+ return {
+ path = M.recombine_filename(path),
+ line = 1,
+ col = 1,
+ }
+end
+
+function M.copy_table(table)
+ local new_table = {}
+ for k, v in pairs(table) do
+ new_table[k] = v
+ end
+ return new_table
+end
+
+---@param force_slow bool - If true, act as if we don't have dependencies
+---Check for dependencies and optional dependencies
+function M.has_prereqs(force_slow)
if vim.fn.executable('fzf') ~= 1 then return "no" end
- if options.force_slow_mode then return "partial" end
+ if force_slow then return "partial" end
if vim.fn.executable('fd') ~= 1 then return "partial" end
if vim.fn.executable('sd') ~= 1 then return "partial" end
if vim.fn.executable('qsv') ~= 1 then return "partial" end
return "yes"
end
----@param options table of user options
---- Sets the full path of the appropriate search script (regular or fallback)
-function M.set_script_path(options)
+---@param force_slow bool - If true, act as if we don't have dependencies
+---Sets the full path of the appropriate search script (regular or fallback)
+function M.set_script_path(force_slow)
local lua_file_path = debug.getinfo(1, "S").source:sub(2)
local lua_file_dir = vim.fn.fnamemodify(lua_file_path, ":h")
local script_path = lua_file_dir .. "/denote-fzf-lua/scripts/"
- local dependencies = M.has_prereqs(options)
+ local dependencies = M.has_prereqs(force_slow)
if dependencies == "yes" then
script_path = script_path .. "search_files.sh"
elseif dependencies == "partial" then
@@ -38,103 +65,65 @@ function M.format_fzf_with_nth(options)
local fzf_with_nth = ""
local fields = {"path", "date", "time", "sig", "title", "tags", "ext" }
for i, v in ipairs(fields) do
- if options.fzf.search_fields[v] then
+ if options.search_fields[v] then
fzf_with_nth = fzf_with_nth .. i .. ','
end
end
return fzf_with_nth:sub(1, -2)
end
----@param filename chopped up string with fields separated by |
+---@param f string - Field from search result
+---@param delim delimiter character
+---Puts a field from the search results back into Denote format (e.g. two words to --two-words)
+function M.repopulate_field(f, delim)
+ if f == "." then
+ return ""
+ else
+ return delim .. delim .. f:gsub("%s",delim)
+ end
+end
+
+---@param filename chopped up string with fields separated by multiple spaces
function M.recombine_filename(filename)
+ vim.print(filename)
local t = {}
filename = filename:gsub("%s%s+", "|")
t.path, t.year, t.month, t.day, t.hour, t.min, t.sec, t.sig, t.title, t.tags, t.ext =
filename:match("^(.*/)|(%d%d%d%d)%-(%d%d)%-(%d%d)|(%d%d):(%d%d):(%d%d)|([^|]+)|([^|]+)|([^|]+)|(%..+)")
- if t.sig == "." then
- t.sig = ""
- else
- t.sig = "==" .. t.sig:gsub("%s", "=")
- end
- if t.title == "." then
- t.title = ""
- else
- t.title = "--" .. t.title:gsub("%s", "-")
- end
- if t.tags == "." then
- t.tags = ""
- else
- t.tags = "__" .. t.tags:gsub("%s", "_")
- end
+ t.sig = M.repopulate_field(t.sig,"=")
+ t.title = M.repopulate_field(t.title,"-")
+ t.tags = M.repopulate_field(t.tags,"_")
return t.path .. t.year .. t.month .. t.day .. "T" .. t.hour .. t.min .. t.sec .. t.sig .. t.title .. t.tags .. t.ext
end
---- Sets fzf preview to bat or cat depending on program availability
-function M.set_preview_program()
- if vim.fn.executable('bat') == 1 then
- return "xargs bat -p --color=always"
- else
- return "xargs cat"
- end
-end
-
--- Sets fzf preview for contents search. $file is the filename, $line is the rg match line.
-function M.set_rg_preview()
- if vim.fn.executable('bat') == 1 then
- return "bat $file -p --color=always --highlight-line=$line"
- else
- return "cat $file"
- end
-end
-
---@param options table of user options
---Opens a window to search filenames
function M.search_files(options)
- script_path = M.set_script_path(options)
+ script_path = M.set_script_path(options.force_slow_mode)
if not script_path then return end
- options.fzf.opts["--with-nth"] = M.format_fzf_with_nth(options)
- options.fzf.opts['--header-lines'] = 1
- fzflua.fzf_exec(script_path .. " " .. options.dir,
- {
- preview = options.fzf.preview .. M.set_preview_program(),
- -- selected[1] is the chopped up filename with 2+ spaces between fields
- actions = { ['default'] = function(selected, opts)
- local filename = M.recombine_filename(selected[1])
- vim.api.nvim_command('edit ' .. filename)
- end},
- fzf_opts = options.fzf.opts,
- })
+ local opts = {}
+ opts.previewer = M.recombine_previewer
+ opts.fzf_opts = M.copy_table(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, opts)
+ local filename = M.recombine_filename(selected[1])
+ vim.api.nvim_command('edit ' .. filename)
+ end }
+ fzflua.fzf_exec(script_path .. " " .. options.dir, opts)
end
----@param f string returned from rg. "/path/filename:line..."
-function M.open_file_at_line(f)
- local filename, line = f:match("^(.-):(%d+)")
- vim.api.nvim_command('edit +' .. line .. " " .. filename)
-end
-
----@param options table
+--- Performs a standard rg search
function M.search_contents(options)
- local r = fzflua.fzf_live("rg " .. options.rg.opts .. " -- <query> " .. options.dir .. " 2>/dev/null",
- {
- preview = options.rg.fzf.preview .. M.set_rg_preview(),
- actions = { ['default'] = function(selected, opts)
- local filename_line = selected[1]
- filename_line = filename_line .. ":1" --Default :1 in case no line returned
- M.open_file_at_line(filename_line)
- end},
- fzf_opts = options.rg.fzf.opts,
- })
-end
-
-function M.search2(options)
- opts = {}
- opts.prompt = "> "
- opts.actions = fzflua.defaults.actions.files
+ local opts = {}
+ opts.cwd = options.dir
opts.previewer = "builtin"
- opts.cwd = options.dir
- opts.fzf_opts = options.rg.fzf.opts
+ opts.fzf_opts = options.fzf_lua_opts.fzf_opts
+ opts.actions = fzflua.defaults.actions.files
return fzflua.fzf_live(function(q)
- return "rg --column --color=always -- " .. vim.fn.shellescape(q or '')
+ return "rg --column --color=always -i -- " .. vim.fn.shellescape(q or '')
end, opts)
end
@@ -145,7 +134,7 @@ function M.load_cmd(options)
if opts.fargs[1] == "files" then
M.search_files(options)
elseif opts.fargs[1] == "contents" then
- M.search2(options)
+ M.search_contents(options)
else
error("Unsupported operation " .. opts.fargs[1])
end
@@ -160,7 +149,7 @@ end
---@param options? table user configuration
function M.setup(options)
options = vim.tbl_deep_extend("force", config.defaults, options or {})
- fzflua.setup({ winopts = options.winopts })
+ fzflua.setup(options.fzf_lua_opts)
M.load_cmd(options)
end
diff --git a/lua/denote-fzf-lua/config.lua b/lua/denote-fzf-lua/config.lua
index 8d466ba..fa878ce 100644
--- a/lua/denote-fzf-lua/config.lua
+++ b/lua/denote-fzf-lua/config.lua
@@ -1,30 +1,11 @@
local M = {}
M.defaults = {
- dir = "~/notes",
- force_slow_mode = false, -- Disables optional dependencies
+ dir = "~/notes", -- Notes directory
+ force_slow_mode = false, -- Disables optional dependencies (fd, sd, qsv)
- -- Set any fzf-lua winopts here (See fzf-lua documentation)
- winopts = {
- height = 0.85,
- width = 0.80,
- row = 0.35,
- col = 0.50,
- },
-
- -- Options sent to fzf (only relevant for filename search)
- fzf = {
- opts = {
- ['--reverse'] = true,
- ['--no-info'] = true,
- ['--no-separator'] = true,
- ['--no-hscroll'] = true,
- ['--preview-window'] = "bottom:50%",
- ['-i'] = true,
- ['--delimiter'] = "\\s{2,}", -- Don't change this, change search_fields.
- },
- -- Which Denote fields are displayed in the search results
- search_fields = {
+ -- Choose which fields appear in search results
+ search_fields = {
path = false,
date = true,
time = false,
@@ -32,32 +13,22 @@ M.defaults = {
title = true,
tags = true,
ext = false,
- },
- -- This recombines the chopped up filename from ./scripts/search_files.sh. The output needs to be piped to xargs bat or xargs cat.
- preview = [[date={2};time={3}; datetime=$(echo ${date}T${time} | tr -d '\-:'); \
- sig={4}; sig=$(echo $sig | tr -d '.' | tr " " "="); if [ ! -z ${sig} ] ; then sig="==${sig}"; fi; \
- title={5}; title=$(echo $title | tr -d '.' | tr ' ' '-'); if [ ! -z ${title} ] ; then title="--${title}"; fi; \
- tags={6}; tags=$(echo $tags | tr -d '.' | tr ' ' '_'); if [ ! -z ${tags} ] ; then tags="__${tags}"; fi; \
- ext={7}; \
- echo {1}${datetime}${sig}${title}${tags}${ext} | ]],
},
- -- Options for ripgrep (Only relevant for contents search)
- rg = {
- opts = "--column --color=always",
- fzf = {
- opts = {
- ['--reverse'] = true,
- ['--no-info'] = true,
- ['--no-separator'] = true,
- ['--no-hscroll'] = true,
- ['--preview-window'] = 'bottom:50%',
- ['-i'] = true,
- },
- -- Checks if $line=$file in case rg --files is used and doesn't return a line number
- -- This currently does nothing until I figure out if there's a good way to chop up filenames in the rg search
- preview = "file=$(echo {} | cut -d ':' -f1); \
- line=$(echo {} | cut -d ':' -f2); \
- if [ \"$line\" = \"$file\" ] ; then line=0; fi;",
+
+ -- Settings for fzf-lua. See fzf-lua doc for full list of options
+ fzf_lua_opts = {
+ winopts = {
+ height = 0.85,
+ width = 0.80,
+ row = 0.35,
+ col = 0.50,
+ },
+ fzf_opts = {
+ ['--reverse'] = true,
+ ['--no-info'] = true,
+ ['--no-separator'] = true,
+ ['--no-hscroll'] = true,
+ ['-i'] = true,
},
},
}
diff --git a/lua/denote-fzf-lua/scripts/fallback_search_files.sh b/lua/denote-fzf-lua/scripts/fallback_search_files.sh
index 9bc8135..430d058 100755
--- a/lua/denote-fzf-lua/scripts/fallback_search_files.sh
+++ b/lua/denote-fzf-lua/scripts/fallback_search_files.sh
@@ -6,21 +6,6 @@ tr '\=\-_' ' ' |\
sed -E 's/([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])T([0-9][0-9])([0-9][0-9])([0-9][0-9])/\1-\2-\3,\4:\5:\6/' |\
sed 's/,,/,.,/g' |\
column -t -s "," -N Path,Date,Time,Sig,Title,Tags,Ext
-# fzf --delimiter='\s{2,}' --header-lines=1 -i $2 --preview='\
-# sig={4}; sig=$(echo $sig | tr -d . | tr " " =); if [ ! -z ${sig} ] ; then sig="==${sig}"; fi; \
-# title={5}; title=$(echo $title | tr -d . | tr " " -); if [ ! -z ${title} ] ; then title="--${title}"; fi; \
-# tags={6}; tags=$(echo $tags | tr -d . | tr " " _); if [ ! -z ${tags} ] ; then tags="__${tags}"; fi; \
-# ext={7}; \
-# echo {1}\|{2}\|{3}\|{4}\|{5}\|{6}\|{7} |\
-# sed -E "s/^(.*\/)?\|([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])\|([0-9][0-9]):([0-9][0-9]):([0-9][0-9])\|.*/\1\2\3\4T\5\6\7$sig$title$tags$ext/" |\
-# xargs cat' \
-# --bind='enter:become(\
-# sig={4}; sig=$(echo $sig | tr -d . | tr " " =); if [ ! -z ${sig} ] ; then sig="==${sig}"; fi; \
-# title={5}; title=$(echo $title | tr -d . | tr " " -); if [ ! -z ${title} ] ; then title="--${title}"; fi; \
-# tags={6}; tags=$(echo $tags | tr -d . | tr " " _); if [ ! -z ${tags} ] ; then tags="__${tags}"; fi; \
-# ext={7}; \
-# echo {1}\|{2}\|{3}\|{4}\|{5}\|{6}\|{7} | \
-# sed -E "s/^(.*\/)?\|([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])\|([0-9][0-9]):([0-9][0-9]):([0-9][0-9])\|.*/\1\2\3\4T\5\6\7$sig$title$tags$ext/")+abort'
# 1. find everything in directory
# 2. sed deletes every line that isn't a Denote note (can't do this with find alone because -regex can't match optional capture groups)