diff --git a/Makefile b/Makefile index 9bf4a56b..cca7b2d6 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,12 @@ PLENARY-REPO=https://github.com/nvim-lua/plenary.nvim.git .PHONY: test test: - nvim --headless --noplugin -u tests/minimal_init.vim -c "lua require('plenary.test_harness').test_directory('tests/', { minimal_init = 'tests/minimal_init.vim', sequential = true, timeout = 120000 })" + nvim --headless --noplugin -u tests/minimal_init.lua -c "lua require('plenary.test_harness').test_directory('tests/', { minimal_init = 'tests/minimal_init.lua', sequential = true, timeout = 120000 })" +# run with `make FILE=tests/path_spec.lua test-file` .PHONY: test-file test-file: - nvim --headless --noplugin -u tests/minimal_init.vim -c "lua require('plenary.busted').run(vim.loop.cwd()..'/'..[[$(FILE)]])" + nvim --headless --noplugin -u tests/minimal_init.lua -c "lua require('plenary.busted').run(vim.loop.cwd()..'/'..[[$(FILE)]])" .PHONY: plenary @@ -18,5 +19,5 @@ plenary: git clone $(PLENARY-REPO) $(PLENARY-DIR); \ else \ echo "Updating plenary.nvim..."; \ - git -C $(PLENARY-DIR) pull; \ + git -C $(PLENARY-DIR) pull --rebase; \ fi diff --git a/README.md b/README.md index 68d73954..2a0cd33f 100644 --- a/README.md +++ b/README.md @@ -1290,15 +1290,7 @@ require'fzf-lua'.setup { -- manpages = { previewer = "man_native" }, -- helptags = { previewer = "help_native" }, -- - -- optional override of file extension icon colors - -- available colors (terminal): - -- clear, bold, black, red, green, yellow - -- blue, magenta, cyan, grey, dark_grey, white - file_icon_colors = { - ["sh"] = "green", - }, - -- padding can help kitty term users with - -- double-width icon rendering + -- padding can help kitty term users with double-width icon rendering file_icon_padding = '', -- uncomment if your terminal/font does not support unicode character -- 'EN SPACE' (U+2002), the below sets it to 'NBSP' (U+00A0) instead diff --git a/doc/fzf-lua.txt b/doc/fzf-lua.txt index 7c1542a0..38318ace 100644 --- a/doc/fzf-lua.txt +++ b/doc/fzf-lua.txt @@ -1382,15 +1382,7 @@ open an issue and I'll be more than happy to help.** -- manpages = { previewer = "man_native" }, -- helptags = { previewer = "help_native" }, -- - -- optional override of file extension icon colors - -- available colors (terminal): - -- clear, bold, black, red, green, yellow - -- blue, magenta, cyan, grey, dark_grey, white - file_icon_colors = { - ["sh"] = "green", - }, - -- padding can help kitty term users with - -- double-width icon rendering + -- padding can help kitty term users with double-width icon rendering file_icon_padding = '', -- uncomment if your terminal/font does not support unicode character -- 'EN SPACE' (U+2002), the below sets it to 'NBSP' (U+00A0) instead @@ -1491,4 +1483,4 @@ I missed your name feel free to contact me and I'll add it below: as baseline for the builtin previewer and his must have plugin nvim-bqf -vim:tw=78:ts=8:ft=help:norl: \ No newline at end of file +vim:tw=78:ts=8:ft=help:norl: diff --git a/lua/fzf-lua/config.lua b/lua/fzf-lua/config.lua index eb253817..ef633d4a 100644 --- a/lua/fzf-lua/config.lua +++ b/lua/fzf-lua/config.lua @@ -1,77 +1,10 @@ local path = require "fzf-lua.path" local utils = require "fzf-lua.utils" local actions = require "fzf-lua.actions" +local devicons = require "fzf-lua.devicons" local M = {} -if utils.__HAS_DEVICONS then - M._has_devicons, M._devicons = pcall(require, "nvim-web-devicons") - - -- get the devicons module path - M._devicons_path = M._has_devicons and M._devicons and M._devicons.setup - and path.normalize(debug.getinfo(M._devicons.setup, "S").source:gsub("^@", "")) -end - -M._diricon_escseq = function() - local hlgroup = utils.map_get(M, "__resume_data.opts.hls.dir_icon") or M.globals.__HLS.dir_icon - local _, escseq = utils.ansi_from_hl(hlgroup) - return escseq -end - --- get icons proxy for the headless instance -M._devicons_geticons = function() - if not M._has_devicons or not M._devicons or not M._devicons.get_icons then - return - end - -- force refresh if `bg` changed from dark/light (#855) - if M.__DEVICONS and vim.o.bg == M.__DEVICONS_BG then - return M.__DEVICONS - end - -- save the current background - M.__DEVICONS_BG = vim.o.bg - -- rpc request cannot return a table that has mixed elements - -- of both indexed items and key value, it will fail with - -- "Cannot convert given lua table" - -- NOTES: - -- (1) devicons.get_icons() returns the default icon in [1] - -- (2) we cannot rely on having either .name or .color (#817) - local all_devicons = M._devicons.get_icons() - if not all_devicons or vim.tbl_isempty(all_devicons) then - -- something is wrong with devicons - -- can't use `error` due to fast event - print("[Fzf-lua] error: devicons.get_icons() is nil or empty!") - return - end - -- We only need the name, icon and color properties - local default_icon = all_devicons[1] or {} - M.__DEVICONS = { - [""] = { - name = default_icon.name or "Default", - icon = default_icon.icon or "", - color = default_icon.color or "#6d8086", - } - } - for k, v in pairs(all_devicons) do - -- skip all indexed (numeric) entries - if type(k) == "string" then - M.__DEVICONS[k] = { - name = v.name or k, - icon = v.icon or "", - color = v.color or (function() - -- some devicons customizations remove `info.color` - -- retrieve the color from the highlight group (#801) - local hlgroup = "DevIcon" .. (v.name or k) - local hexcol = utils.hexcol_from_hl(hlgroup, "fg") - if hexcol and #hexcol > 0 then - return hexcol - end - end)(), - } - end - end - return M.__DEVICONS -end - -- set this so that make_entry won't get nil err when setting remotely M.__resume_data = {} @@ -283,9 +216,6 @@ function M.normalize_opts(opts, globals, __resume_key) if v == "" then opts.fzf_opts[k] = true end end - -- Disable devicons if not available - opts.file_icons = utils.__HAS_DEVICONS and opts.file_icons or nil - -- Execlude file icons from the fuzzy matching (#1080) if opts.file_icons and opts._fzf_nth_devicons and not opts.fzf_opts["--delimiter"] then opts.fzf_opts["--nth"] = opts.fzf_opts["--nth"] or "-1.." @@ -327,7 +257,13 @@ function M.normalize_opts(opts, globals, __resume_key) -- also check if we need to override 'opts.prompt' from cli args -- if we don't override 'opts.prompt' 'FzfWin.save_query' will -- fail to remove the prompt part from resume saved query (#434) - for _, s in ipairs({ "fzf_args", "fzf_cli_args", "fzf_raw_args" }) do + for _, s in ipairs({ + "fzf_args", + "fzf_cli_args", + "fzf_raw_args", + "file_icon_padding", + "dir_icon", + }) do if opts[s] == nil then opts[s] = M.globals[s] end @@ -521,17 +457,22 @@ function M.normalize_opts(opts, globals, __resume_key) opts.winopts.split = nil end + if devicons.plugin_loaded() then + -- refresh icons, does nothing if "vim.o.bg" didn't change + devicons.load({ + icon_padding = opts.file_icon_padding, + dir_icon = { icon = opts.dir_icon, color = utils.hexcol_from_hl(opts.hls.dir_icon, "fg") } + }) + elseif opts.file_icons then + -- Disable devicons if not available + utils.warn("nvim-web-devicons isn't available, disabling 'file_icons'.") + opts.file_icons = nil + end + -- libuv.spawn_nvim_fzf_cmd() pid callback opts._set_pid = M.set_pid opts._get_pid = M.get_pid - -- setup devicons terminal highlight groups does nothing unless - -- neovim `bg` is changed, call via utils/loadstring to prevent - -- circular require and also make sure "make_entry.lua" isn't - -- loaded before devicons vars are setup by this module - -- TODO: cleanup the background update and devicons load logic - utils.setup_devicon_term_hls() - -- mark as normalized opts._normalized = true diff --git a/lua/fzf-lua/core.lua b/lua/fzf-lua/core.lua index 711ba883..17a7c4e4 100644 --- a/lua/fzf-lua/core.lua +++ b/lua/fzf-lua/core.lua @@ -6,6 +6,7 @@ local actions = require "fzf-lua.actions" local win = require "fzf-lua.win" local libuv = require "fzf-lua.libuv" local shell = require "fzf-lua.shell" +local devicons = require "fzf-lua.devicons" local make_entry = require "fzf-lua.make_entry" local base64 = require "fzf-lua.lib.base64" local serpent = require "fzf-lua.lib.serpent" @@ -635,7 +636,7 @@ M.mt_cmd_wrapper = function(opts) t.g = {} for k, v in pairs({ ["_fzf_lua_server"] = vim.g.fzf_lua_server, - ["_devicons_path"] = config._devicons_path, + ["_devicons_path"] = devicons.plugin_path(), ["_devicons_setup"] = config._devicons_setup, }) do t.g[k] = v diff --git a/lua/fzf-lua/defaults.lua b/lua/fzf-lua/defaults.lua index bbad6cce..c7206c58 100644 --- a/lua/fzf-lua/defaults.lua +++ b/lua/fzf-lua/defaults.lua @@ -967,10 +967,7 @@ M.defaults.complete_line = { complete = true } M.defaults.file_icon_padding = "" -M.defaults.file_icon_colors = {} - -M.defaults.dir_icon = "" -M.defaults.dir_icon_color = "#519aba" +M.defaults.dir_icon = "" M.defaults.__HLS = { normal = "FzfLuaNormal", diff --git a/lua/fzf-lua/devicons.lua b/lua/fzf-lua/devicons.lua new file mode 100644 index 00000000..4d882ddf --- /dev/null +++ b/lua/fzf-lua/devicons.lua @@ -0,0 +1,253 @@ +local path = require "fzf-lua.path" +local utils = require "fzf-lua.utils" + +-- Our "copy" of the devicons library functions so we can load the library +-- from the headless instance and better support edge cases like multi-part +-- extension names (#1053) +-- Not that it makes much difference at this point but this also lowers the +-- minimum requirements of neovim 0.7 as we no longer need to run setup +-- and fail due to using the newer highlight creation APIs +local M = {} + +M.plugin_loaded = function() + return M.__HAS_DEVICONS +end + +M.plugin_path = function() + return M.__DEVICONS_PATH +end + +M.load_devicons_plugin = function() + if M.plugin_loaded() then return end + -- limit devicons support to nvim >=0.8, although official support is >=0.7 + -- running setup on 0.7 errs with "W18: Invalid character in group name" + if utils.__HAS_NVIM_07 then + M.__HAS_DEVICONS, M.__DEVICONS_LIB = pcall(require, "nvim-web-devicons") + if M.__HAS_DEVICONS then + M.__DEVICONS_PATH = path.parent(path.parent(path.normalize( + debug.getinfo(M.__DEVICONS_LIB.setup, "S").source:gsub("^@", "")))) + end + end +end + +-- Load devicons at least once on require +M.load_devicons_plugin() + +M.load_devicons_fzflua_server = function() + local res = nil + 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", + "return require'fzf-lua.devicons'.STATE", + {}) + vim.fn.chanclose(chan_id) + end) + if not ok or type(res) ~= "table" then + io.stderr:write(string.format("RPC error getting fzf_lua:devicons:STATE: %s\n", errmsg)) + return + else + M.STATE = res + end +end + +M.load_icons = function() + if not M.plugin_loaded() then return end + if M.STATE and M.STATE.icons + -- Refresh if `bg` changed from dark/light (#855) + and (not M.STATE.bg or vim.o.bg == M.STATE.bg) then + return + end + -- save the current background + M.STATE.bg = vim.o.bg + -- rpc request cannot return a table that has mixed elements + -- of both indexed items and key value, it will fail with + -- "Cannot convert given lua table" + -- NOTES: + -- (1) devicons.get_icons() returns the default icon in [1] + -- (2) we cannot rely on having either .name or .color (#817) + local ok, all_devicons = pcall(function() + M.__DEVICONS_LIB.refresh() -- reloads light|dark theme + return M.__DEVICONS_LIB.get_icons() + end) + if not ok or not all_devicons or vim.tbl_isempty(all_devicons) then + -- something is wrong with devicons + -- can't use `error` due to fast event + print("[Fzf-lua] error: devicons.get_icons() is nil or empty!") + return + end + local theme + if vim.o.background == "light" then + ok, theme = pcall(require, "nvim-web-devicons.icons-light") + else + ok, theme = pcall(require, "nvim-web-devicons.icons-default") + end + if not ok or type(theme) ~= "table" or not theme.icons_by_filename then + print("[Fzf-lua] error: devicons.theme is nil or empty!") + return + end + local icons_by_filename = theme.icons_by_filename or {} + local icons_by_file_extension = theme.icons_by_file_extension or {} + if type(all_devicons[1]) == "table" then + M.STATE.default_icon.icon = all_devicons[1].icon or M.STATE.default_icon.icon + M.STATE.default_icon.color = all_devicons[1].color or M.STATE.default_icon.color + end + M.STATE.icons = { + by_filename = {}, -- full filename (path.tail) lookup + by_ext = {}, -- simple extension lookup + by_ext_2part = {}, -- 2-part extensions, e.g. "foo.test.js" + -- lookup table to indicate extension has potentially has better match + -- in the 2part for example, ".js" will send us looking for "test.js" + ext_has_2part = {}, + + } + for k, v in pairs(all_devicons) do + -- skip all indexed (numeric) entries + if type(k) == "string" then + local info = { + -- NOTE: we no longer need name since we use the RGB color directly + -- name = v.name or k, + icon = v.icon or "", + color = v.color or (function() + -- some devicons customizations remove `info.color` + -- retrieve the color from the highlight group (#801) + local hlgroup = "DevIcon" .. (v.name or k) + local hexcol = utils.hexcol_from_hl(hlgroup, "fg") + if hexcol and #hexcol > 0 then + return hexcol + end + end)(), + } + -- NOTE: entries like "R" can appear in both icons by filename/extension + if icons_by_filename[k] then + M.STATE.icons.by_filename[k] = info + end + if icons_by_file_extension[k] then + if k:match(".+%.") then + M.STATE.icons.by_ext_2part[k] = info + M.STATE.icons.ext_has_2part[path.extension(k)] = true + else + M.STATE.icons.by_ext[k] = info + end + end + -- if not icons_by_file_extension[k] and not icons_by_filename[k] then + -- print("icons_by_operating_system", k) + -- end + end + end + return M.STATE.icons +end + +---@param filepath string +---@param extensionOverride string? +---@return string, string? +M.get_devicon = function(filepath, extensionOverride) + if not M.STATE or not M.STATE.icons then + return unpack({ "", nil }) + end + + if path.ends_with_separator(filepath) then + -- path is directory + return M.STATE.dir_icon.icon, M.STATE.dir_icon.color + end + + local icon, color + local filename = path.tail(filepath) + local ext = extensionOverride or path.extension(filename, true) + + -- lookup directly by filename + local by_filename = M.STATE.icons.by_filename[filename] + if by_filename then + icon, color = by_filename.icon, by_filename.color + end + + -- check for `ext` as extension can be nil, e.g. "dockerfile" + -- lookup by 2 part extensions, e.g. "foo.test.tsx" + if ext and not icon and M.STATE.icons.ext_has_2part[ext] then + local ext2 = path.extension(filename:sub(1, #filename - #ext - 1)) + if ext2 then + local by_ext_2part = M.STATE.icons.by_ext_2part[ext2 .. "." .. ext] + if by_ext_2part then + icon, color = by_ext_2part.icon, by_ext_2part.color + end + end + end + + -- finally lookup by "one-part" extension (i.e. no dots in ext) + if ext and not icon then + local by_ext = M.STATE.icons.by_ext[ext] + if by_ext then + icon, color = by_ext.icon, by_ext.color + end + end + + -- Default icon/color, we never return nil + icon = icon or M.STATE.default_icon.icon + color = color or M.STATE.default_icon.color + + if M.STATE.icon_padding then + icon = icon .. M.STATE.icon_padding + end + + return icon, color +end + +M.load = function(opts) + opts = opts or {} + + M.STATE = vim.tbl_deep_extend("force", M.STATE or {}, { + icon_padding = type(opts.icon_padding) == "string" and opts.icon_padding or nil, + dir_icon = vim.tbl_extend("force", { icon = "", color = nil }, opts.dir_icon or {}), + default_icon = + vim.tbl_extend("force", { icon = "", color = "#6d8086" }, opts.default_icon or {}), + }) + + -- Check if we're running from the headless instance, attempt to load our + -- icons with the RPC response of `get_icons` from the main fzf-lua instance + ---@diagnostic disable-next-line: undefined-field + if vim.g.fzf_lua_is_headless and not _G._fzf_lua_server and not _G._devicons_path then + local errmsg = "fzf-lua fatal: '_G._fzf_lua_server', '_G._devicons_path' both nil\n" + io.stderr:write(errmsg) + print(errmsg) + return + end + if vim.g.fzf_lua_is_headless and _G._fzf_lua_server then + -- headless instance, fzf-lua server exists, attempt + -- to load icons from main neovim instance + M.load_devicons_fzflua_server() + return + end + if vim.g.fzf_lua_is_headless and _G._devicons_path then + -- headless instance, no fzf-lua server was specified + -- but we got devicon's lib path, add to runtime path + -- so `load_devicons_plugin` can find the library + vim.opt.runtimepath:append(_G._devicons_path) + end + -- Attempt to load devicons plugin + M.load_devicons_plugin() + + -- Load custom overrides before loading icons + if vim.g.fzf_lua_is_headless + ---@diagnostic disable-next-line: undefined-field + and _G._devicons_setup and vim.loop.fs_stat(_G._devicons_setup) then + ---@diagnostic disable-next-line: undefined-field + local file = loadfile(_G._devicons_setup) + if file then pcall(file) end + end + + -- Load the devicons iconset + M.load_icons() +end + +-- For testing +M.unload = function() + M.STATE = nil + M.__HAS_DEVICONS = nil + M.__DEVICONS_LIB = nil + M.__DEVICONS_PATH = nil + M.load_devicons_plugin() +end + +return M diff --git a/lua/fzf-lua/fzf.lua b/lua/fzf-lua/fzf.lua index 3a5cfc85..9ef50bfb 100644 --- a/lua/fzf-lua/fzf.lua +++ b/lua/fzf-lua/fzf.lua @@ -301,7 +301,7 @@ function M.raw_fzf(contents, fzf_cli_args, opts) -- or grep<->live_grep toggle) the current mode will be "t" which is Terminal (INSERT) -- mode but our interface is still opened in NORMAL mode, either `startinsert` is not -- working (as it's technically already in INSERT) or creating a new terminal buffer - -- within the same window starts in NORMAL mode while returning the wrong `nvim_get_mode + -- within the same window starts in NORMAL mode while returning the wrong `nvim_get_mode` if utils.__HAS_NVIM_06 and vim.api.nvim_get_mode().mode == "t" then utils.feed_keys_termcodes("i") else diff --git a/lua/fzf-lua/libuv.lua b/lua/fzf-lua/libuv.lua index ae44ea88..3db2e2e9 100644 --- a/lua/fzf-lua/libuv.lua +++ b/lua/fzf-lua/libuv.lua @@ -13,6 +13,9 @@ local __FILE__ = debug.getinfo(1, "S").source:gsub("^@", "") -- effects (as 'vim.g.fzf_lua_directory=nil'). Run an additional -- check if we are running headless with 'vim.api.nvim_list_uis' if not vim.g.fzf_lua_directory and #vim.api.nvim_list_uis() == 0 then + -- global var indicating a headless instance + vim.g.fzf_lua_is_headless = true + -- prepend this folder first, so our modules always get first -- priority over some unknown random module with the same name package.path = (";%s/?.lua;"):format(vim.fn.fnamemodify(__FILE__, ":h")) diff --git a/lua/fzf-lua/make_entry.lua b/lua/fzf-lua/make_entry.lua index bec51d01..c01b40a6 100644 --- a/lua/fzf-lua/make_entry.lua +++ b/lua/fzf-lua/make_entry.lua @@ -3,26 +3,9 @@ local M = {} 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 = nil --- Supports multi-part extensions --- e.g. --- "file.js" -> "js" --- "file.test.js" -> "test.js" --- "file.spec.js" -> "spec.js" -local function get_extension_from_file_name(file_name) - local name, extension = file_name:match("(.+)%.(.+)$") - - if name and extension then - local preExtension = name:match(".+%.(.+)$") - if preExtension then - return (preExtension .. "." .. extension):lower() - else - return extension:lower() - end - end -end - -- attempt to load the current config -- should fail if we're running headless do @@ -30,19 +13,12 @@ do if ok then config = module end end --- These globals are set by spawn.fn_transform loadstring ----@diagnostic disable-next-line: undefined-field -M._fzf_lua_server = _G._fzf_lua_server ----@diagnostic disable-next-line: undefined-field -M._devicons_path = _G._devicons_path ----@diagnostic disable-next-line: undefined-field -M._devicons_setup = _G._devicons_setup - local function load_config_section(s, datatype, optional) if config then local val = utils.map_get(config, s) return type(val) == datatype and val or nil - elseif M._fzf_lua_server then + ---@diagnostic disable-next-line: undefined-field + elseif _G._fzf_lua_server then -- load config from our running instance local res = nil local is_bytecode = false @@ -56,7 +32,8 @@ local function load_config_section(s, datatype, optional) exec_str = ("return require'fzf-lua'.config.%s"):format(s) end local ok, errmsg = pcall(function() - local chan_id = vim.fn.sockconnect("pipe", M._fzf_lua_server, { rpc = true }) + ---@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) @@ -72,146 +49,12 @@ local function load_config_section(s, datatype, optional) end end --- NOT NEEDED SINCE RESUME DATA REFACTOR --- local function set_config_section(s, data) --- if M._fzf_lua_server then --- -- save config in our running instance --- local ok, errmsg = pcall(function() --- local chan_id = vim.fn.sockconnect("pipe", M._fzf_lua_server, { rpc = true }) --- vim.rpcrequest(chan_id, "nvim_exec_lua", ([[ --- local data = select(1, ...) --- require'fzf-lua'.config.%s = data --- ]]):format(s), { data }) --- vim.fn.chanclose(chan_id) --- end) --- if not ok then --- io.stderr:write(("Error setting remote config section '%s': %s\n") --- :format(s, errmsg)) --- end --- return ok --- elseif config then --- local keys = utils.strsplit(s, ".") --- local iter = config --- for i = 1, #keys do --- iter = iter[keys[i]] --- if not iter then break end --- if i == #keys - 1 then --- iter[keys[i + 1]] = data --- return iter --- end --- end --- end --- end - --- Setup the terminal colors codes for nvim-web-devicons colors -M.setup_devicon_term_hls = function() - if M.__HL_BG and vim.o.bg == M.__HL_BG then - -- already setup for the current `bg`, do nothing (#893) - -- this was already taken care of for multiprocess in - -- `config._devicons_geticons` in #855 - return - end - local icons = M._devicons and M._devicons.get_icons() or M._devicons_map - if not icons then - return - end - -- save the current neovim background - M.__HL_BG = vim.o.bg - - local function hex(hexstr) - local r, g, b = hexstr:match(".(..)(..)(..)") - r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16) - return r, g, b - end - - for k, info in pairs(icons) do - -- info.name can be missing (#817) - local name = info.name or type(k) == "string" and k - if name then - local hlgroup = "DevIcon" .. name - -- some devicons customizations remove `info.color` - -- retrieve the color from the highlight group (#801) - local hexcol = info.color or utils.hexcol_from_hl(hlgroup, "fg") - if hexcol and #hexcol > 0 then - local r, g, b = hex(hexcol) - utils.cache_ansi_escseq(hlgroup, string.format("[38;2;%s;%s;%sm", r, g, b)) - end - end - end -end - --- cache directory icon coloring escape sequence -M.__DIR_ICON = nil -M.__DIR_ICON_HL = "FzfLuaDirIcon" - -M.setup_directory_icon = function() - M.__DIR_ICON = config.globals.dir_icon - -- `M._diricon_escseq` cab be nil if hlgroup is cleared or non-existent - local escseq = M._diricon_escseq or config._diricon_escseq and config._diricon_escseq() - utils.cache_ansi_escseq(M.__DIR_ICON_HL, escseq) -end - -local function load_devicons() - if config and config._has_devicons then - -- file was called from the primary instance - -- acquire nvim-web-devicons from config - M._devicons = config._devicons - elseif M._fzf_lua_server and load_config_section("_has_devicons", "boolean") then - -- file was called from a headless instance - -- load nvim-web-devicons via the RPC to the main instance - M._devicons_map = load_config_section("_devicons_geticons()", "table") - M._diricon_escseq = load_config_section("_diricon_escseq()", "string") - end - if not M._devicons and not M._devicons_map - and M._devicons_path and vim.loop.fs_stat(M._devicons_path) then - -- file was called from a headless instance - -- fallback load nvim-web-devicons manually - -- add nvim-web-devicons path to `package.path` - -- so `require("nvim-web-devicons")` can find it - package.path = (";%s/?.lua;"):format(vim.fn.fnamemodify(M._devicons_path, ":h")) - .. package.path - M._devicons = require("nvim-web-devicons") - -- WE NO LONGER USE THIS, LEFT FOR DOCUMENTATION - -- loading with 'require' is needed, 'loadfile' - -- cannot load a custom setup function as it's - -- considered a separate instance and the inner - -- 'require' in the setup file will create an - -- additional 'nvim-web-devicons' instance - --[[ local file = loadfile(M._devicons_path) - M._devicons = file and file() ]] - -- did caller specify a custom setup function? - -- must be called before the next step as `setup` - -- is ignored when called the second time - M._devicons_setup = M._devicons_setup and vim.fn.expand(M._devicons_setup) - if M._devicons and M._devicons_setup and vim.loop.fs_stat(M._devicons_setup) then - local file = loadfile(M._devicons_setup) - if file then file() end - end - end - if M._devicons and M._devicons.setup and not M._devicons.has_loaded() then - -- if the caller has devicons lazy loaded - -- running without calling setup will generate an error: - -- nvim-web-devicons.lua:972: E5560: - -- nvim_command must not be called in a lua loop callback - -- running in a pcall to avoid panic with neovim <= 0.6 - -- due to usage of new highlighting API introduced with v0.7 - pcall(M._devicons.setup) - end - -- Setup devicon terminal ansi color codes - M.setup_devicon_term_hls() - M.setup_directory_icon() -end - --- Load remote config and devicons -pcall(load_devicons) - if not config then local _config = { globals = { git = {}, files = {}, grep = {} } } _config.globals.git.icons = load_config_section("globals.git.icons", "table") or {} - _config.globals.dir_icon = load_config_section("globals.dir_icon", "string") - _config.globals.file_icon_colors = load_config_section("globals.file_icon_colors", "table") or {} - _config.globals.file_icon_padding = load_config_section("globals.file_icon_padding", "string") - _config.globals.files.git_status_cmd = load_config_section("globals.files.git_status_cmd", "table") + _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 = @@ -224,42 +67,6 @@ if not config then config = _config end --- by default the extension will be derived from `file`, but you can --- override it by passing `extensionOverride` -M.get_devicon = function(file, extensionOverride) - local ext = extensionOverride or get_extension_from_file_name(file) - - local icon, hl - if path.ends_with_separator(file) then - icon, hl = M.__DIR_ICON, M.__DIR_ICON_HL - elseif M._devicons then - icon, hl = M._devicons.get_icon(file, ext, { default = true }) - elseif M._devicons_map then - -- Lookup first by name, then by ext (devicons `strict=true`) - -- "" is added by fzf-lua and is thus guaranteed - local info = M._devicons_map[file:lower()] - or M._devicons_map[ext] - or M._devicons_map[""] - icon, hl = info.icon, "DevIcon" .. info.name - else - icon, hl = "", "dark_grey" - end - - -- allow user override of the color - local override = config.globals.file_icon_colors - and config.globals.file_icon_colors[ext] - if override then - hl = override - end - - if config.globals.file_icon_padding and - #config.globals.file_icon_padding > 0 then - icon = icon .. config.globals.file_icon_padding - end - - return icon, hl -end - M.get_diff_files = function(opts) local diff_files = {} local cmd = opts.git_status_cmd or config.globals.files.git_status_cmd @@ -427,8 +234,7 @@ M.preprocess = function(opts) end if opts.file_icons then - -- refersh the directory icon hlgroup - M.setup_directory_icon() + devicons.load() end if opts.git_icons then @@ -542,13 +348,9 @@ M.file = function(x, opts) ret[#ret + 1] = utils.nbsp end if opts.file_icons then - local filename = path.tail(origpath) - icon, hl = M.get_devicon(filename) - if opts.color_icons then - -- extra workaround for issue #119 (or similars) - -- use default if we can't find the highlight ansi - local fn = utils.ansi_codes[hl] or utils.ansi_codes["dark_grey"] - icon = fn(icon) + 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 diff --git a/lua/fzf-lua/path.lua b/lua/fzf-lua/path.lua index 84225148..2369980e 100644 --- a/lua/fzf-lua/path.lua +++ b/lua/fzf-lua/path.lua @@ -199,14 +199,14 @@ function M.relative_to(path, relative_to) end ---@param path string ----@return string -function M.extension(path) - for i = #path, 1, -1 do - if string_byte(path, i) == M.dot_byte then - return path:sub(i + 1) +---@return string? +function M.extension(path, no_tail) + local file = no_tail and path or M.tail(path) + for i = #file, 1, -1 do + if string_byte(file, i) == M.dot_byte then + return file:sub(i + 1) end end - return "" end ---@param paths string[] diff --git a/lua/fzf-lua/providers/buffers.lua b/lua/fzf-lua/providers/buffers.lua index 0ed3b8f7..d89522af 100644 --- a/lua/fzf-lua/providers/buffers.lua +++ b/lua/fzf-lua/providers/buffers.lua @@ -5,6 +5,7 @@ local shell = require "fzf-lua.shell" local config = require "fzf-lua.config" local base64 = require "fzf-lua.lib.base64" local make_entry = require "fzf-lua.make_entry" +local devicons = require "fzf-lua.devicons" local M = {} @@ -142,17 +143,11 @@ local function gen_buffer_entry(opts, buf, max_bufnr, cwd) local buficon = "" local hl = "" if opts.file_icons then - if utils.is_term_bufname(buf.info.name) then - -- get shell-like icon for terminal buffers - buficon, hl = make_entry.get_devicon(buf.info.name, "sh") - else - local filename = path.tail(buf.info.name) - buficon, hl = make_entry.get_devicon(filename) - end - if opts.color_icons then - -- fallback to "grey" color (#817) - local fn = utils.ansi_codes[hl] or utils.ansi_codes["dark_grey"] - buficon = fn(buficon) + buficon, hl = devicons.get_devicon(buf.info.name, + -- shell-like icon for terminal buffers + utils.is_term_bufname(buf.info.name) and "sh" or nil) + if hl and opts.color_icons then + buficon = utils.ansi_from_rgb(hl, buficon) end end local max_bufnr_w = 3 + #tostring(max_bufnr) + utils.ansi_escseq_len(bufnrstr) @@ -258,10 +253,9 @@ M.buffer_lines = function(opts) end bufname = path.basename(filepath) if opts.file_icons then - local filename = path.tail(bufname) - buficon, hl = make_entry.get_devicon(filename) - if opts.color_icons then - buficon = utils.ansi_codes[hl](buficon) + buficon, hl = devicons.get_devicon(bufname) + if hl and opts.color_icons then + buficon = utils.ansi_from_rgb(hl, buficon) end end if not bufname or #bufname == 0 then diff --git a/lua/fzf-lua/providers/git.lua b/lua/fzf-lua/providers/git.lua index 1cd03dec..0124b593 100644 --- a/lua/fzf-lua/providers/git.lua +++ b/lua/fzf-lua/providers/git.lua @@ -49,8 +49,10 @@ M.status = function(opts) local contents if opts.multiprocess then - -- git status does not require preprocessing - opts.__mt_preprocess = [[return true]] + -- git status does not require preprocessing if not loading devicons + opts.__mt_preprocess = opts.file_icons + and [[return require("fzf-lua.devicons").load()]] + or [[return true]] opts.__mt_transform = [[return require("make_entry").git_status]] contents = core.mt_cmd_wrapper(opts) else diff --git a/lua/fzf-lua/providers/nvim.lua b/lua/fzf-lua/providers/nvim.lua index 73b45837..0ddb6e53 100644 --- a/lua/fzf-lua/providers/nvim.lua +++ b/lua/fzf-lua/providers/nvim.lua @@ -5,6 +5,7 @@ local libuv = require "fzf-lua.libuv" local shell = require "fzf-lua.shell" local config = require "fzf-lua.config" local make_entry = require "fzf-lua.make_entry" +local devicons = require "fzf-lua.devicons" local M = {} @@ -163,10 +164,9 @@ M.tagstack = function(opts) local bufname = path.HOME_to_tilde(path.relative_to(tag.filename, vim.loop.cwd())) local buficon, hl if opts.file_icons then - local filename = path.tail(bufname) - buficon, hl = make_entry.get_devicon(filename) - if opts.color_icons then - buficon = utils.ansi_codes[hl](buficon) + buficon, hl = devicons.get_devicon(bufname) + if hl and opts.color_icons then + buficon = utils.ansi_from_rgb(hl, buficon) end end -- table.insert(entries, ("%s)%s[%s]%s%s%s%s:%s:%s: %s %s"):format( diff --git a/lua/fzf-lua/utils.lua b/lua/fzf-lua/utils.lua index 56ed6461..a1d08ea5 100644 --- a/lua/fzf-lua/utils.lua +++ b/lua/fzf-lua/utils.lua @@ -442,6 +442,60 @@ function M.map_tolower(m) return ret end +local function hex2rgb(hexcol) + local r, g, b = hexcol:match("#(%x%x)(%x%x)(%x%x)") + if not r or not g or not b then return end + r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16) + return r, g, b +end + +-- auto genreate ansi escape sequence from RGB or neovim highlights +--[[ M.ansi_auto = setmetatable({}, { + -- __index metamethod only gets called when the item does not exist + -- we use this to auto-cache the ansi escape sequence + __index = function(self, k) + print("get", k) + local escseq + -- if not an existing highlight group lookup + -- in the neovim colormap and convert to RGB + if not k:match("^#") and vim.fn.hlexists(k) ~= 1 then + local col = M.COLORMAP()[k:sub(1, 1):upper() .. k:sub(2):lower()] + if col then + -- format as 6 digit hex for hex2rgb() + k = ("#%06x"):format(col) + end + end + if k:match("#%x%x%x%x%x%x") then -- index is RGB + -- cache the sequence as all lowercase + k = k:lower() + local v = rawget(self, k) + if v then return v end + local r, g, b = hex2rgb(k) + escseq = string.format("[38;2;%d;%d;%dm", r, g, b) + else -- index is neovim hl + _, escseq = M.ansi_from_hl(k, "foo") + print("esc", k, escseq) + end + -- We always set the item, if not RGB and hl isn't valid + -- create a dummy function that returns the string instead + local v = type(escseq) == "string" and #escseq > 0 + and function(s) + if type(s) ~= "string" or #s == 0 then return "" end + return escseq .. s .. M.ansi_escseq.clear + end + or function(s) return s end + rawset(self, k, v) + return v + end, + __newindex = function(self, k, v) + assert(false, + string.format("modifying the ansi cache directly isn't allowed [index: %s]", k)) + -- rawset doesn't trigger __new_index, otherwise stack overflow + -- we never get here but this masks the "unused local" warnings + rawset(self, k, v) + end +}) ]] + M.ansi_codes = {} M.ansi_escseq = { -- the "\x1b" esc sequence causes issues @@ -476,13 +530,6 @@ for color, escseq in pairs(M.ansi_escseq) do M.cache_ansi_escseq(color, escseq) end -local function hex2rgb(hexcol) - local r, g, b = hexcol:match("#(..)(..)(..)") - if not r or not g or not b then return end - r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16) - return r, g, b -end - -- Helper func to test for invalid (cleared) highlights function M.is_hl_cleared(hl) -- `vim.api.nvim_get_hl_by_name` is deprecated since v0.9.0 @@ -538,7 +585,7 @@ end function M.ansi_from_rgb(rgb, s) local r, g, b = hex2rgb(rgb) if r and g and b then - return string.format("[38;2;%d;%d;%dm%s%s", r, g, b, s, M.ansi_escseq.clear) + return string.format("[38;2;%d;%d;%dm%s%s", r, g, b, s, "") end return s end @@ -695,10 +742,6 @@ function M.setup_highlights() pcall(loadstring("require'fzf-lua'.setup_highlights()")) end -function M.setup_devicon_term_hls() - pcall(loadstring("require'fzf-lua.make_entry'.setup_devicon_term_hls()")) -end - ---@param fname string ---@param name string|nil ---@param silent boolean diff --git a/scripts/headless_fd.sh b/scripts/headless_fd.sh new file mode 100755 index 00000000..dc9ad621 --- /dev/null +++ b/scripts/headless_fd.sh @@ -0,0 +1,158 @@ +#!/bin/sh + +BASEDIR=$(cd "$(dirname "$0")" ; pwd -P) + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options" + echo "-h, --help Show this help" + echo "-d, --debug Debug level [0|1|2|false|true|v]" + echo "" + echo "Display Options" + echo "-c, --cwd Working Directory" + echo "-x, --cmd Executed Command (default: fd --color=never)" + echo "-g, --git-icons Git icons [0|1|false|true] (default:false)" + echo "-f, --file-icons File icons [0|1|false|true] (default:true)" + echo "--color Color icons [0|1|false|true] (default:true)" +} + +# saner programming env: these switches turn some bugs into errors +set -o noclobber -o nounset + +# -allow a command to fail with !’s side effect on errexit +# -use return value from ${PIPESTATUS[0]}, because ! hosed $? +getopt --test > /dev/null +if [ $? -ne 4 ]; then + echo '`getopt --test` failed in this environment.' + exit 1 +fi + +OPTIONS=hd:c:x:f:g: +LONGOPTS=help,debug:,cwd:,file-icons:,git-icons:,color:,cmd: + +PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") +if [ $? -ne 0 ]; then + # e.g. return value is 1 + # then getopt has complained about wrong arguments to stdout + usage; + exit 2 +fi +# read getopt’s output this way to handle the quoting right: +eval set -- "$PARSED" + +debug="false" +cwd= cmd= +git_icons="false" +file_icons="true" +color_icons="true" + +# now enjoy the options in order and nicely split until we see -- +while true; do + case "$1" in + -h|--help) + usage; + exit 0 + ;; + -d|--debug) + case $2 in + "2"|"v"|"verbose") + debug="v" + ;; + "1"|"true") + debug="true" + ;; + *) + debug="false" + ;; + esac + shift 2 + ;; + -c|--cwd) + cwd="$2" + shift 2 + ;; + -x|--cmd) + cmd="$2" + shift 2 + ;; + -f|--file-icons) + case $2 in + "0"|"false") + file_icons="false" + ;; + *) + file_icons="true" + ;; + esac + shift 2 + ;; + -g|--git-icons) + case $2 in + "0"|"false") + git_icons="false" + ;; + *) + git_icons="true" + ;; + esac + shift 2 + ;; + --color) + case $2 in + "0"|"false") + color_icons="false" + ;; + *) + color_icons="true" + ;; + esac + shift 2 + ;; + --) + shift + break + ;; + *) + # never get here! + echo "error: error while parsing command line arguments" + usage; + exit 3 + ;; + esac +done + +# handle non-option arguments +if [ $# -gt 0 ]; then + echo "error: unrecgonized option" + usage; + exit 4 +fi + +VIMRUNTIME=/usr/share/nvim/runtime \ +/usr/bin/nvim -n --headless --clean --cmd "lua loadfile( + [[${BASEDIR}/../lua/fzf-lua/libuv.lua]])().spawn_stdio( + -- opts + { + g = { + --_fzf_lua_server = [[/run/user/1000/fzf-lua.1710687343.12851.1]], + _devicons_path = [[${XDG_DATA_HOME:-$HOME/.local/share}/nvim/lazy/nvim-web-devicons]], + _devicons_setup = [[${XDG_CONFIG_HOME:-$HOME/.config}/nvim/lua/plugins/devicons/setup.lua]], + }, + _base64 = false, + debug = [[$debug]] == [[v]] and [[v]] or $debug, + file_icons = ${file_icons}, + git_icons = ${git_icons}, + color_icons = ${color_icons}, + cmd = [[${cmd:-fd --color=never}]], + cwd = vim.fn.expand([[${cwd:-$BASEDIR}]]), + }, + -- fn_transform + [==[ + return require(\"make_entry\").file + ]==], + -- fn_preprocess + [==[ + return require(\"make_entry\").preprocess + ]==] +)" diff --git a/tests/devicons_spec.lua b/tests/devicons_spec.lua new file mode 100644 index 00000000..007935c6 --- /dev/null +++ b/tests/devicons_spec.lua @@ -0,0 +1,107 @@ +local fzf = require("fzf-lua") +local path = fzf.path + +describe("Testing devicons module", function() + -- add devicons path from lazy to runtime so our module can load nvim-web-devicons + local devicons_path = path.join({ vim.fn.stdpath("data"), "lazy", "nvim-web-devicons" }) + vim.opt.runtimepath:append(devicons_path) + local theme = require("nvim-web-devicons.icons-default") + local devicons = require("fzf-lua.devicons") + devicons.load() + -- remove from runtime so we can test the headless runtime append + vim.opt.runtimepath:remove(devicons_path) + + it(string.format("load_icons (%s)", vim.g.fzf_lua_server), function() + _G._devicons_path = nil + _G._fzf_lua_server = vim.g.fzf_lua_server + vim.g.fzf_lua_is_headless = true + devicons.load() + local state = devicons.STATE + local icons = devicons.STATE.icons + assert.are.same(state.default_icon, { icon = "", color = "#6d8086" }) + assert.are.same(state.dir_icon, { icon = "", color = nil }) + assert.is.True(vim.tbl_count(icons.ext_has_2part) > 4) + assert.is.True(vim.tbl_count(icons.by_ext_2part) > 8) + assert.are.equal(vim.tbl_count(icons.by_filename), vim.tbl_count(theme.icons_by_filename)) + assert.are.equal(vim.tbl_count(icons.by_ext) + vim.tbl_count(icons.by_ext_2part), + vim.tbl_count(theme.icons_by_file_extension)) + end) + it("get_icons (headless: devicons path)", function() + _G._devicons_path = devicons_path + _G._fzf_lua_server = nil + vim.g.fzf_lua_is_headless = true + devicons.unload() + devicons.load() + local state = devicons.STATE + local icons = devicons.STATE.icons + assert.are.same(state.default_icon, { icon = "", color = "#6d8086" }) + assert.are.same(state.dir_icon, { icon = "", color = nil }) + assert.is.True(vim.tbl_count(icons.ext_has_2part) > 4) + assert.is.True(vim.tbl_count(icons.by_ext_2part) > 8) + assert.are.equal(vim.tbl_count(icons.by_filename), vim.tbl_count(theme.icons_by_filename)) + assert.are.equal(vim.tbl_count(icons.by_ext) + vim.tbl_count(icons.by_ext_2part), + vim.tbl_count(theme.icons_by_file_extension)) + end) + it("get_icons (main thread)", function() + _G._devicons_path = nil + _G._fzf_lua_server = nil + vim.g.fzf_lua_is_headless = nil + devicons.unload() + devicons.load() + local state = devicons.STATE + local icons = devicons.STATE.icons + assert.are.same(state.default_icon, { icon = "", color = "#6d8086" }) + assert.are.same(state.dir_icon, { icon = "", color = nil }) + assert.is.True(vim.tbl_count(icons.ext_has_2part) > 4) + assert.is.True(vim.tbl_count(icons.by_ext_2part) > 8) + assert.are.equal(vim.tbl_count(icons.by_filename), vim.tbl_count(theme.icons_by_filename)) + assert.are.equal(vim.tbl_count(icons.by_ext) + vim.tbl_count(icons.by_ext_2part), + vim.tbl_count(theme.icons_by_file_extension)) + end) + it("get_icon (dark)", function() + vim.o.background = "dark" + devicons.load() + assert.are.same({ devicons.get_devicon("") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon(".") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("f.abc") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("f.") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon(".f") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("foo") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("foo/") }, { "", nil }) + -- by filename + assert.are.same({ devicons.get_devicon(".editorconfig") }, { "", "#fff2f2" }) + assert.are.same({ devicons.get_devicon("/path/.bashrc") }, { "", "#89e051" }) + -- by 2-part extension + assert.are.same({ devicons.get_devicon("foo.bar.jsx") }, { "", "#20c2e3" }) + assert.are.same({ devicons.get_devicon("foo.spec.jsx") }, { "", "#20c2e3" }) + assert.are.same({ devicons.get_devicon("foo.config.ru") }, { "", "#701516" }) + -- by 1-part extensions + assert.are.same({ devicons.get_devicon("foo.lua") }, { "", "#51a0cf" }) + assert.are.same({ devicons.get_devicon("foo.py") }, { "", "#ffbc03" }) + assert.are.same({ devicons.get_devicon("foo.r") }, { "󰟔", "#2266ba" }) + assert.are.same({ devicons.get_devicon("foo.R") }, { "󰟔", "#2266ba" }) + end) + it("get_icon (light)", function() + vim.o.background = "light" + devicons.load() + assert.are.same({ devicons.get_devicon("") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon(".") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("f.abc") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("f.") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon(".f") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("foo") }, { "", "#6d8086" }) + assert.are.same({ devicons.get_devicon("foo/") }, { "", nil }) + -- by filename + assert.are.same({ devicons.get_devicon(".editorconfig") }, { "", "#333030" }) + assert.are.same({ devicons.get_devicon("/path/.bashrc") }, { "", "#447028" }) + -- by 2-part extension + assert.are.same({ devicons.get_devicon("foo.bar.jsx") }, { "", "#158197" }) + assert.are.same({ devicons.get_devicon("foo.spec.jsx") }, { "", "#158197" }) + assert.are.same({ devicons.get_devicon("foo.config.ru") }, { "", "#701516" }) + -- by 1-part extensions + assert.are.same({ devicons.get_devicon("foo.lua") }, { "", "#366b8a" }) + assert.are.same({ devicons.get_devicon("foo.py") }, { "", "#805e02" }) + assert.are.same({ devicons.get_devicon("foo.r") }, { "󰟔", "#1a4c8c" }) + assert.are.same({ devicons.get_devicon("foo.R") }, { "󰟔", "#1a4c8c" }) + end) +end) diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua new file mode 100644 index 00000000..e0286fe5 --- /dev/null +++ b/tests/minimal_init.lua @@ -0,0 +1,6 @@ +vim.opt.hidden = true +vim.opt.swapfile = false + +vim.opt.runtimepath:append("../plenary.nvim") +vim.opt.runtimepath:append("../fzf-lua") +vim.cmd("runtime! plugin/plenary.vim") diff --git a/tests/minimal_init.vim b/tests/minimal_init.vim deleted file mode 100644 index d2e11884..00000000 --- a/tests/minimal_init.vim +++ /dev/null @@ -1,6 +0,0 @@ -set hidden -set noswapfile - -set rtp+=../plenary.nvim -set rtp+=../fzf-lua -runtime! plugin/plenary.vim diff --git a/tests/path_spec.lua b/tests/path_spec.lua index 6c0cce78..08cce0ef 100644 --- a/tests/path_spec.lua +++ b/tests/path_spec.lua @@ -139,6 +139,8 @@ describe("Testing path module", function() utils.__IS_WINDOWS = false assert.are.equal(path.tail(""), "") assert.are.equal(path.tail("/"), "/") + assert.are.equal(path.tail("foo"), "foo") + assert.are.equal(path.tail(".foo"), ".foo") assert.are.equal(path.tail("/foo"), "foo") assert.are.equal(path.tail("/foo/"), "foo/") assert.are.equal(path.tail("/foo/bar"), "bar") @@ -148,6 +150,8 @@ describe("Testing path module", function() assert.are.equal(path.tail(""), "") assert.are.equal(path.tail("/"), "/") assert.are.equal(path.tail([[\]]), [[\]]) + assert.are.equal(path.tail("foo"), "foo") + assert.are.equal(path.tail(".foo"), ".foo") assert.are.equal(path.tail([[c:\foo]]), "foo") assert.are.equal(path.tail([[c:\foo\]]), [[foo\]]) assert.are.equal(path.tail([[c:\foo\bar]]), "bar") @@ -264,9 +268,14 @@ describe("Testing path module", function() it("Extension", function() utils.__IS_WINDOWS = false - assert.is.same(path.extension("/some/path/foobar"), "") + assert.are.equal(path.extension("/some/path/foobar"), nil) + assert.are.equal(path.extension("/some/pa.th/foobar"), nil) + assert.is.same(path.extension("/some/path/foobar."), "") assert.is.same(path.extension("/some/path/foo.bar"), "bar") assert.is.same(path.extension("/some/path/foo.bar.baz"), "baz") + assert.is.same(path.extension("/some/path/.foobar"), "foobar") + -- override that doesn't "tail" the path + assert.are.equal(path.extension("/some/pa.th/foobar", true), "th/foobar") end) it("Join", function()