aboutsummaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorDefaultGen <gravel.justness270@slmail.me>2024-06-16 05:23:09 -0400
committerDefaultGen <gravel.justness270@slmail.me>2024-06-16 05:23:09 -0400
commit23a89932e57b50a8e9064b9c7ba329e62c88bc44 (patch)
tree4175dd9fd7e19b90d420a7ed1422f95e8612ea08 /lua
parentbba00f7bc470ed1c9ba53cdce3bcca88b3a11a5f (diff)
downloaddenote-fzf-lua-23a89932e57b50a8e9064b9c7ba329e62c88bc44.tar.gz
First working fzf-lua implementation
Diffstat (limited to 'lua')
-rw-r--r--lua/denote-fzf-lua.lua112
-rw-r--r--lua/denote-fzf-lua/config.lua52
-rwxr-xr-xlua/denote-fzf-lua/scripts/fallback_search_files.sh36
-rwxr-xr-xlua/denote-fzf-lua/scripts/rg.sh4
-rwxr-xr-xlua/denote-fzf-lua/scripts/search_files.sh36
5 files changed, 240 insertions, 0 deletions
diff --git a/lua/denote-fzf-lua.lua b/lua/denote-fzf-lua.lua
new file mode 100644
index 0000000..1013afb
--- /dev/null
+++ b/lua/denote-fzf-lua.lua
@@ -0,0 +1,112 @@
+local M = {}
+
+local config = require("denote-fzf-lua.config")
+local fzflua = require('fzf-lua')
+
+
+--- Check for dependencies and optional dependencies
+function M.has_prereqs()
+ if vim.fn.executable('fzf') ~= 1 then return "no" end
+ if vim.fn.executable('rg') ~= 1 then return "no" 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('xsv') ~= 1 then return "partial" end
+ return "yes"
+end
+
+---Returns the full path of the appropriate script
+function M.get_script_path()
+ 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()
+ if dependencies == "yes" then
+ script_path = script_path .. "search_files.sh"
+ elseif dependencies == "partial" then
+ script_path = script_path .. "fallback_search_files.sh"
+ else
+ error("Missing dependencies: fzf and rg")
+ return false
+ end
+ return script_path
+end
+
+---@param options table of user options
+---Format the --with-nth argument for fzf (which fields are shown)
+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
+ fzf_with_nth = fzf_with_nth .. i .. ','
+ end
+ end
+ return fzf_with_nth:sub(1, -2)
+end
+
+-- TODO: This is absolutely wrecked
+function M.open_file(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
+ local file = t.path .. t.year .. t.month .. t.day .. "T" .. t.hour .. t.min .. t.sec .. t.sig .. t.title .. t.tags .. t.ext
+ vim.api.nvim_command('edit ' .. file)
+end
+
+---@param options table of user options
+---Opens a window to search filenames
+function M.files(options)
+ local script_path = M.get_script_path()
+ if not script_path then return end
+ options.fzf.opts["--with-nth"] = M.format_fzf_with_nth(options)
+ fzflua.fzf_exec(script_path .. " " .. options.dir,
+ {
+ preview = options.fzf.preview,
+ actions = options.fzf.actions,
+ fzf_opts = options.fzf.opts,
+ })
+ -- local fzf_args = M.format_fzf_with_nth(options) .. " " .. options.fzf.args
+ -- vim.api.nvim_command('edit ' .. stdout)
+end
+
+---@param options table of user options
+function M.load_cmd(options)
+ vim.api.nvim_create_user_command("DenoteSearch", function(opts)
+ if opts.fargs[1] == "files" then
+ M.files(options)
+ elseif opts.fargs[1] == "contents" then
+ M.conents(options)
+ else
+ error("Unsupported operation " .. opts.fargs[1])
+ end
+ end, {
+ nargs = 1,
+ complete = function()
+ return {"files", "contents"}
+ end,
+ })
+end
+
+---@param options? table user configuration
+function M.setup(options)
+ config.options = vim.tbl_deep_extend("force", config.defaults, options or {})
+ M.load_cmd(config.options)
+end
+
+return M
diff --git a/lua/denote-fzf-lua/config.lua b/lua/denote-fzf-lua/config.lua
new file mode 100644
index 0000000..6520ef8
--- /dev/null
+++ b/lua/denote-fzf-lua/config.lua
@@ -0,0 +1,52 @@
+local M = {}
+
+M.defaults = {
+ dir = "~/notes",
+ window = {
+ width_percent = 80,
+ height_percent = 80,
+ x_offset = 0,
+ y_offset = 0,
+ },
+ fzf = {
+ opts = {
+ ['--reverse'] = true,
+ ['--no-info'] = true,
+ ['--no-separator'] = true,
+ ['--no-hscroll'] = true,
+ ['--preview-window'] = "bottom:50%",
+ ['-i'] = true,
+ ['--delimiter'] = "\\s{2,}",
+ ['--header-lines'] = 1
+ },
+ search_fields = {
+ path = false,
+ date = true,
+ time = false,
+ sig = false,
+ title = true,
+ tags = true,
+ ext = false,
+ },
+ -- This recombines the chopped up filename from ./scripts/search_files.sh and outputs it to cat
+ -- TODO: Rewrite this with a lua function
+ preview = [[\
+sig={4}; sig=$(echo $sig | sd "\W" "" | sd " " "="); 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} | \
+sd "(?P<path>\/.*\/)?\|(?P<d1>\d\d\d\d)-(?P<d2>\d\d)-(?P<d3>\d\d)\|(?P<t1>\d\d):(?P<t2>\d\d):(?P<t3>\d\d)\|.*" "\$path\$d1\$d2\${d3}T\$t1\$t2\$t3$sig$title$tags$ext" |\
+xargs cat]],
+ -- selected[1] is the chopped up filename with 2+ spaces between fields
+ actions = {
+ ['default'] = function(selected, opts)
+ require'denote-fzf-lua'.open_file(selected[1])
+ end
+ }
+ }
+}
+
+M.options = M.defaults
+
+return M
diff --git a/lua/denote-fzf-lua/scripts/fallback_search_files.sh b/lua/denote-fzf-lua/scripts/fallback_search_files.sh
new file mode 100755
index 0000000..a617b64
--- /dev/null
+++ b/lua/denote-fzf-lua/scripts/fallback_search_files.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/sh
+find $1 |\
+sed -E "/^(.*\/)?([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])(==[^=\-_\.]+)?(--[^\-_\.]+)?(__[^\.]+)?(\.[a-z0-9]+)$/!d" |\
+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])(==([^=\-_\.]+))?(--([^\-_\.]+))?(__([^\.]+))?(\.[a-z0-9]+)$/\1|\2|\4|\6|\8|\9/" |\
+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'
+
+# I am sorry for this. If I learn an equally fast, more readable way to implement this in Lua I will.
+# 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)
+# 3. sed splits the Denote filename into | delimited fields
+# 4. tr replaces sig, title, tags special characters with spaces
+# 5. sed splits the date and time nicely
+# 6. sed replaces blank fields with a period (column doesn't work with null fields)
+# 7. column pretty prints the filenames in a table
+# 8. fzf --delimiter breaks the table up into fields on 2+ space
+# fzf --nth and --with-nth (args passed in $2) determine which fields are shown
+# fzf --preview takes all the fzf fields {1}..{7} and recombines them into a filename, passed to cat
+# fzf --bind recombines the filename and just outputs it
diff --git a/lua/denote-fzf-lua/scripts/rg.sh b/lua/denote-fzf-lua/scripts/rg.sh
new file mode 100755
index 0000000..2cfc400
--- /dev/null
+++ b/lua/denote-fzf-lua/scripts/rg.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/sh
+rg --color=always --line-number --no-heading --smart-case "${@:1:$#-1}" ${*: -1}
+
+
diff --git a/lua/denote-fzf-lua/scripts/search_files.sh b/lua/denote-fzf-lua/scripts/search_files.sh
new file mode 100755
index 0000000..67367af
--- /dev/null
+++ b/lua/denote-fzf-lua/scripts/search_files.sh
@@ -0,0 +1,36 @@
+#/usr/bin/sh
+fd '^\d{8}T\d{6}(==[a-z0-9=]+)?(\-\-[a-z0-9-]+)?(__[a-z0-9_]+)?\.\w+$' $1 |\
+sd '(?P<path>\/.*\/)?(?P<datetime>\d{8}T\d{6})(==(?P<sig>[a-z0-9=]+))?(--(?P<title>[a-z0-9-]+))(__(?P<tags>[a-z0-9_]+))?(?P<ext>\.\w+)' '$path,$datetime,$sig,$title,$tags,$ext' |\
+tr '\=\-_' ' ' |\
+sd '(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})' '$1-$2-$3,$4:$5:$6' |\
+sd ',,' ',.,' |\
+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 | sd "\W" "" | sd " " "="); if [ ! -z ${sig} ] ; then sig="==${sig}"; fi; \
+# title={5}; title=$(echo $title | sd " " "-"); if [ ! -z ${title} ] ; then title="--${title}"; fi; \
+# tags={6}; tags=$(echo $tags | sd "\." "" | sd " " "_"); if [ ! -z ${tags} ] ; then tags="__${tags}"; fi; \
+# ext={7}; \
+# echo {1}\|{2}\|{3}\|{4}\|{5}\|{6}\|{7} | \
+# sd "(?P<path>\/.*\/)?\|(?P<d1>\d\d\d\d)-(?P<d2>\d\d)-(?P<d3>\d\d)\|(?P<t1>\d\d):(?P<t2>\d\d):(?P<t3>\d\d)\|.*" "\$path\$d1\$d2\${d3}T\$t1\$t2\$t3$sig$title$tags$ext" |\
+# xargs cat' \
+# --bind='enter:become(\
+# sig={4}; sig=$(echo $sig | sd "\W" "" | sd " " "="); if [ ! -z ${sig} ] ; then sig="==${sig}"; fi; \
+# title={5}; title=$(echo $title | sd " " "-"); if [ ! -z ${title} ] ; then title="--${title}"; fi; \
+# tags={6}; tags=$(echo $tags | sd "\." "" | sd " " "_"); if [ ! -z ${tags} ] ; then tags="__${tags}"; fi; \
+# ext={7}; \
+# echo {1}\|{2}\|{3}\|{4}\|{5}\|{6}\|{7} | \
+# sd "(?P<prefix>\/.*\/)?\|(?P<d1>\d\d\d\d)-(?P<d2>\d\d)-(?P<d3>\d\d)\|(?P<t1>\d\d):(?P<t2>\d\d):(?P<t3>\d\d)\|.*" "\$prefix\$d1\$d2\${d3}T\$t1\$t2\$t3$sig$title$tags$ext")+abort'
+#
+# I am sorry for this. If I learn an equally fast, more readable way to implement this in Lua I will.
+# 1. echo table headers (for xsv)
+# 2. fd all Denote files in $1 directory
+# 3. sd chops the Denote filename fields into a CSV
+# 4. tr replaces special characters in sig, title, and tags with spaces
+# 5. sd formats the date and time as YYYY-MM-DD HH:MM:SS
+# 6. sd replaces blank fields with . (ensures fzf can count fields correctly)
+# 7. xsv to print the fields in tabular format
+# 8. fzf the resulting table including $2 args list
+# 9 fzf --delimiter breaks the table up into fields on 2+ space
+# fzf --nth and --with-nth (args passed in $2) determine which fields are shown
+# fzf --preview takes all the fzf fields {1}..{7} and recombines them into a filename, passed to cat
+# fzf --bind recombines the filename and just outputs it