diff --git a/README.md b/README.md index 0e4dc10..7cc865a 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ vim.api.nvim_set_keymap('n', '', | `help_tags` | help tags | | `man_pages` | man pages | | `colorschemes` | color schemes | +| `highlights` | highlight groups | | `commands` | neovim commands | | `command_history` | command history | | `search_history` | search history | diff --git a/doc/fzf-lua.txt b/doc/fzf-lua.txt index 6a84e5e..8eeebf3 100644 --- a/doc/fzf-lua.txt +++ b/doc/fzf-lua.txt @@ -239,6 +239,7 @@ MISC *fzf-lua-misc* | `help_tags` | help tags | | `man_pages` | man pages | | `colorschemes` | color schemes | +| `highlights` | highlight groups | | `commands` | neovim commands | | `command_history` | command history | | `search_history` | search history | diff --git a/lua/fzf-lua/config.lua b/lua/fzf-lua/config.lua index 64d4449..1412a46 100644 --- a/lua/fzf-lua/config.lua +++ b/lua/fzf-lua/config.lua @@ -419,6 +419,10 @@ M.globals.colorschemes = { width = 0.50, }, } +M.globals.highlights = { + prompt = 'highlights> ', + previewer = { _ctor = previewers.builtin.highlights, }, + } M.globals.helptags = { prompt = 'Help> ', actions = { diff --git a/lua/fzf-lua/init.lua b/lua/fzf-lua/init.lua index 4d3f388..0f234a0 100644 --- a/lua/fzf-lua/init.lua +++ b/lua/fzf-lua/init.lua @@ -106,6 +106,7 @@ M.blines = require'fzf-lua.providers.buffers'.blines M.help_tags = require'fzf-lua.providers.helptags'.helptags M.man_pages = require'fzf-lua.providers.manpages'.manpages M.colorschemes = require'fzf-lua.providers.colorschemes'.colorschemes +M.highlights = require'fzf-lua.providers.colorschemes'.highlights M.tags = require'fzf-lua.providers.tags'.tags M.btags = require'fzf-lua.providers.tags'.btags diff --git a/lua/fzf-lua/previewer/builtin.lua b/lua/fzf-lua/previewer/builtin.lua index d3dcb9b..2e89fc5 100644 --- a/lua/fzf-lua/previewer/builtin.lua +++ b/lua/fzf-lua/previewer/builtin.lua @@ -854,4 +854,72 @@ function Previewer.tags:set_cursor_hl(entry) end) end +Previewer.highlights = Previewer.base:extend() + +function Previewer.highlights:should_clear_preview(_) + return false +end + +function Previewer.highlights:gen_winopts() + local winopts = { + wrap = self.win.preview_wrap, + number = false + } + return vim.tbl_extend("keep", winopts, self.winopts) +end + +function Previewer.highlights:new(o, opts, fzf_win) + Previewer.highlights.super.new(self, o, opts, fzf_win) + self.ns_previewer = vim.api.nvim_create_namespace("fzf-lua.previewer.hl") + return self +end + +function Previewer.highlights:close() + Previewer.highlights.super.close(self) + self.tmpbuf = nil +end + +function Previewer.highlights:populate_preview_buf(entry_str) + if not self.tmpbuf then + local output = vim.split(vim.fn.execute "highlight", "\n") + local hl_groups = {} + for _, v in ipairs(output) do + if v ~= "" then + if v:sub(1, 1) == " " then + local part_of_old = v:match "%s+(.*)" + hl_groups[#hl_groups] = hl_groups[#hl_groups] .. part_of_old + else + table.insert(hl_groups, v) + end + end + end + + self.tmpbuf = self:get_tmp_buffer() + vim.api.nvim_buf_set_lines(self.tmpbuf, 0, -1, false, hl_groups) + for k, v in ipairs(hl_groups) do + local startPos = string.find(v, "xxx", 1, true) - 1 + local endPos = startPos + 3 + local hlgroup = string.match(v, "([^ ]*)%s+.*") + pcall(vim.api.nvim_buf_add_highlight, self.tmpbuf, 0, hlgroup, k - 1, startPos, endPos) + end + self:set_preview_buf(self.tmpbuf) + end + + local selected_hl = utils.strip_ansi_coloring(entry_str) + pcall(vim.api.nvim_buf_clear_namespace, self.tmpbuf, self.ns_previewer, 0, -1) + pcall(api.nvim_win_call, self.win.preview_winid, function() + -- start searching at line 1 in case we + -- didn't reload the buffer (same file) + api.nvim_win_set_cursor(0, {1, 0}) + fn.clearmatches() + fn.search(selected_hl.." ", "W") + if self.win.winopts.hl.search then + fn.matchadd(self.win.winopts.hl.search, selected_hl.." ") + end + self.orig_pos = api.nvim_win_get_cursor(0) + utils.zz() + end) + self.win:update_scrollbar() +end + return Previewer diff --git a/lua/fzf-lua/previewer/init.lua b/lua/fzf-lua/previewer/init.lua index 5cbcb2d..2e7dcc7 100644 --- a/lua/fzf-lua/previewer/init.lua +++ b/lua/fzf-lua/previewer/init.lua @@ -16,5 +16,6 @@ Previewer.builtin.man_pages = function() return require 'fzf-lua.previewer.built Previewer.builtin.marks = function() return require 'fzf-lua.previewer.builtin'.marks end Previewer.builtin.jumps = function() return require 'fzf-lua.previewer.builtin'.jumps end Previewer.builtin.tags = function() return require 'fzf-lua.previewer.builtin'.tags end +Previewer.builtin.highlights = function() return require 'fzf-lua.previewer.builtin'.highlights end return Previewer diff --git a/lua/fzf-lua/providers/colorschemes.lua b/lua/fzf-lua/providers/colorschemes.lua index 7627ca8..11611e2 100644 --- a/lua/fzf-lua/providers/colorschemes.lua +++ b/lua/fzf-lua/providers/colorschemes.lua @@ -1,4 +1,5 @@ local core = require "fzf-lua.core" +local utils = require "fzf-lua.utils" local shell = require "fzf-lua.shell" local config = require "fzf-lua.config" local actions = require "fzf-lua.actions" @@ -57,4 +58,68 @@ M.colorschemes = function(opts) end +M.highlights = function(opts) + opts = config.normalize_opts(opts, config.globals.highlights) + if not opts then return end + + local contents = function (cb) + + local highlights = vim.fn.getcompletion('', 'highlight') + + local function add_entry(hl, co) + -- translate the highlight using ansi escape sequences + local x = utils.ansi_from_hl(hl, hl) + cb(x, function(err) + if co then coroutine.resume(co) end + if err then + -- error, close fzf pipe + cb(nil, function() end) + end + end) + if co then coroutine.yield() end + end + + local function populate(entries, fn, co) + for _, e in ipairs(entries) do + fn(e, co) + end + + cb(nil, function() + if co then coroutine.resume(co) end + end) + end + + local coroutinify = (opts.coroutinify==nil) and false or opts.coroutinify + + if not coroutinify then + populate(highlights, add_entry) + else + coroutine.wrap(function() + -- Unable to coroutinify, hl functions fail inside coroutines with: + -- E5560: vimL function must not be called in a lua loop callback + -- and using 'vim.schedule' + -- attempt to yield across C-call boundary + populate(highlights, + function(x, co) + vim.schedule(function() + add_entry(x, co) + end) + end, + coroutine.running()) + coroutine.yield() + end)() + end + + end + + opts.fzf_opts['--no-multi'] = '' + + core.fzf_wrap(opts, contents, function(selected) + + if not selected then return end + actions.act(opts.actions, selected) + + end)() +end + return M diff --git a/lua/fzf-lua/utils.lua b/lua/fzf-lua/utils.lua index 1f45080..3fe93ff 100644 --- a/lua/fzf-lua/utils.lua +++ b/lua/fzf-lua/utils.lua @@ -300,6 +300,51 @@ for color, escseq in pairs(M.ansi_colors) do M.add_ansi_code(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 + +local function synIDattr(hl, w) + return vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(hl)), w) +end + +function M.ansi_from_hl(hl, s) + if vim.fn.hlexists(hl) == 1 then + -- https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#rgb-colors + -- Set foreground color as RGB: 'ESC[38;2;{r};{g};{b}m' + -- Set background color as RGB: 'ESC[48;2;{r};{g};{b}m' + local what = { + ['fg'] = { rgb = true, code = 38 }, + ['bg'] = { rgb = true, code = 48 }, + ['bold'] = { code = 1 }, + ['italic'] = { code = 3 }, + ['underline'] = { code = 4 }, + ['inverse'] = { code = 7 }, + ['reverse'] = { code = 7 }, + ['strikethrough'] = { code = 9 }, + } + for w, p in pairs(what) do + if p.rgb then + local hexcol = synIDattr(hl, w) + local r, g, b = hex2rgb(hexcol) + if r and g and b then + local escseq = ('[%d;2;%d;%d;%dm'):format(p.code, r, g, b) + s = ("%s%s%s"):format(escseq, s, M.ansi_colors.clear) + end + else + local value = synIDattr(hl, w) + if value and tonumber(value)==1 then + local escseq = ('[%dm'):format(p.code) + s = ("%s%s%s"):format(escseq, s, M.ansi_colors.clear) + end + end + end + end + return s +end function M.strip_ansi_coloring(str) if not str then return str end