diff --git a/OPTIONS.md b/OPTIONS.md index 5d81229f..7feef3d1 100644 --- a/OPTIONS.md +++ b/OPTIONS.md @@ -734,6 +734,10 @@ Location list (output of `:lopen`) Location list history (output of `:lhistory`) +#### treesitter + +Current buffer treesitter symbols + #### blines Current buffer lines diff --git a/README.md b/README.md index e626d9be..538e03fd 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,7 @@ to see detailed usage notes and a comprehensive list of all available options.** | `loclist_stack` | location stack | | `lines` | open buffers lines | | `blines` | current buffer lines | +| `treesitter` | current buffer treesitter symbols | | `tabs` | open tabs | | `args` | argument list | diff --git a/doc/fzf-lua-opts.txt b/doc/fzf-lua-opts.txt index 5136dee2..ecf70c94 100644 --- a/doc/fzf-lua-opts.txt +++ b/doc/fzf-lua-opts.txt @@ -1,4 +1,4 @@ -*fzf-lua-opts.txt* For Neovim >= 0.8.0 Last change: 2024 August 07 +*fzf-lua-opts.txt* For Neovim >= 0.8.0 Last change: 2024 September 26 ============================================================================== Table of Contents *fzf-lua-opts-table-of-contents* @@ -989,6 +989,12 @@ Location list history (output of `:lhistory`) +treesitter *fzf-lua-opts-treesitter* + +Current buffer treesitter symbols + + + blines *fzf-lua-opts-blines* Current buffer lines diff --git a/lua/fzf-lua/defaults.lua b/lua/fzf-lua/defaults.lua index d2ff07f5..be7cf743 100644 --- a/lua/fzf-lua/defaults.lua +++ b/lua/fzf-lua/defaults.lua @@ -657,6 +657,24 @@ M.defaults.blines = { _cached_hls = { "buf_name", "buf_nr", "path_linenr" }, } +M.defaults.treesitter = { + previewer = M._default_previewer_fn, + prompt = "Treesitter> ", + file_icons = false, + color_icons = false, + fzf_opts = { + ["--multi"] = true, + ["--delimiter"] = "[:]", + ["--with-nth"] = "2..", + ["--tiebreak"] = "index", + }, + line_field_index = "{2}", + _actions = function() + return M.globals.actions.buffers or M.globals.actions.files + end, + _cached_hls = { "buf_name", "buf_nr", "path_linenr", "path_colnr" }, +} + M.defaults.tags = { previewer = { _ctor = previewers.builtin.tags }, prompt = "Tags> ", diff --git a/lua/fzf-lua/init.lua b/lua/fzf-lua/init.lua index 62a18c30..a1040eb7 100644 --- a/lua/fzf-lua/init.lua +++ b/lua/fzf-lua/init.lua @@ -270,6 +270,7 @@ do tabs = { "fzf-lua.providers.buffers", "tabs" }, lines = { "fzf-lua.providers.buffers", "lines" }, blines = { "fzf-lua.providers.buffers", "blines" }, + treesitter = { "fzf-lua.providers.buffers", "treesitter" }, helptags = { "fzf-lua.providers.helptags", "helptags" }, manpages = { "fzf-lua.providers.manpages", "manpages" }, -- backward compat diff --git a/lua/fzf-lua/providers/buffers.lua b/lua/fzf-lua/providers/buffers.lua index ee2853cb..886e63e4 100644 --- a/lua/fzf-lua/providers/buffers.lua +++ b/lua/fzf-lua/providers/buffers.lua @@ -408,4 +408,72 @@ M.tabs = function(opts) core.fzf_exec(contents, opts) end + +M.treesitter = function(opts) + opts = config.normalize_opts(opts, "treesitter") + if not opts then return end + + local __has_ts, _ = pcall(require, "nvim-treesitter") + if not __has_ts then + utils.info("Treesitter requires 'nvim-treesitter'.") + return + end + + -- Default to current buffer + opts.bufnr = tonumber(opts.bufnr) or vim.api.nvim_get_current_buf() + opts._bufname = path.basename(vim.api.nvim_buf_get_name(opts.bufnr)) + if not opts._bufname or #opts._bufname == 0 then + opts._bufname = utils.nvim_buf_get_name(opts.bufnr) + end + + local ts_parsers = require("nvim-treesitter.parsers") + if not ts_parsers.has_parser(ts_parsers.get_buf_lang(opts.bufnr)) then + utils.info(string.format("No treesitter parser found for '%s' (bufnr=%d).", + opts._bufname, opts.bufnr)) + return + end + + local kind2hl = function(kind) + local map = { var = "variable.builtin" } + return "@" .. (map[kind] or kind) + end + + local contents = function(cb) + coroutine.wrap(function() + local co = coroutine.running() + local ts_locals = require("nvim-treesitter.locals") + for _, definition in ipairs(ts_locals.get_definitions(opts.bufnr)) do + local nodes = ts_locals.get_local_nodes(definition) + for _, node in ipairs(nodes) do + if node.node then + vim.schedule(function() + local lnum, col, _, _ = vim.treesitter.get_node_range(node.node) + local node_text = vim.treesitter.get_node_text(node.node, opts.bufnr) + local node_kind = node.kind and utils.ansi_from_hl(kind2hl(node.kind), node.kind) + local entry = string.format("[%s]%s%s:%s:%s:\t[%s] %s", + utils.ansi_codes[opts.hls.buf_nr](tostring(opts.bufnr)), + utils.nbsp, + utils.ansi_codes[opts.hls.buf_name](opts._bufname), + utils.ansi_codes[opts.hls.path_linenr](tostring(lnum + 1)), + utils.ansi_codes[opts.hls.path_colnr](tostring(col + 1)), + node_kind or "", + node_text) + cb(entry, function(err) + coroutine.resume(co) + if err then cb(nil) end + end) + end) + coroutine.yield() + end + end + end + cb(nil) + end)() + end + + opts = core.set_header(opts, opts.headers or { "cwd" }) + + core.fzf_exec(contents, opts) +end + return M