
telescope.nvim 插件开发指南从零编写你的第一个 Picker 与 Extension【免费下载链接】telescope.nvimFind, Filter, Preview, Pick. All lua, all the time.项目地址: https://gitcode.com/GitHub_Trending/te/telescope.nvim本指南以仓库根目录的 developers.md 为核心面向希望为 telescope.nvim 编写自定义 Picker选择器或扩展Extension的开发者系统讲解 Picker、Finder、Action、Entry Maker、Previewer 五大组件的编写方法与底层机制并配套展示仓库内源码实现作为依据。读完本文你将能够独立完成一个可运行的自定义 Picker、替换默认动作、通过 Entry Maker 定制展示与排序并将其打包成可通过:Telescope命令调用的扩展。阅读前提本指南假设你已具备一定的 Lua 编程基础telescope 面向 Neovim使用 Lua 5.1 语法。如果你刚开始接触 Lua建议先学习 Lua 5.1 手册 以及 Neovim 下的 Lua 用法。对 telescope 的整体架构Picker / Finder / Sorter / Previewer 之间的数据流还不太熟悉的话可以先通过:h telescope.nvim查看架构流程图。编写你的第一个 Picker1. 准备一个 Lua 草稿文件建议打开一个空的 Lua 草稿文件在其中逐步开发 Picker并通过:luafile %反复执行验证。到文章末尾我们会把这个文件打包成正式的扩展。2. 必需的 Requires开始编写 Picker 前先在文件顶部引入三个核心模块local pickers require telescope.pickers local finders require telescope.finders local conf require(telescope.config).values模块作用pickers主模块用于创建新的 Picker 实例finders提供各种 Finder 接口用来向 Picker 填充条目itemsconfigconfig.values表保存了用户的配置直接将其引用到conf可让 Picker 尊重用户的 sorter、theme 等自定义设置conf是全文反复用到的关键把它传给 sorter、previewer 等组件就能让自定义 Picker 自动继承用户在setup()里配置的行为例如用户若配置了 fzf-native sorterPicker 会自动采用它。3. 第一个 Picker一个最简单的颜色选择器-- our picker function: colors local colors function(opts) opts opts or {} pickers.new(opts, { prompt_title colors, finder finders.new_table { results { red, green, blue } }, sorter conf.generic_sorter(opts), }):find() end -- to execute the function colors()执行:luafile %后会打开一个包含red、green、blue三个条目的 telescope Picker。但此时按回车选择一个颜色会打开一个新文件——这正是我们下一步要解决的问题。逐段拆解这段代码colors函数接收一个opts表。这是良好的实践用户可以通过传入自己的opts表来改变 Picker 的行为。例如在opts中传入主题配置即可更换 Picker 的显示主题这正是把opts作为第一个参数传给pickers.new的原因。prompt_title是可选字段未设置时默认显示Prompt。finder是必填字段必须赋值为某个 finders 函数的返回值。这里的new_table允许定义一组静态结果results是元素数组——它不一定是字符串数组也可以是表数组下一节会用到。sorter虽不是必填但强烈建议设置默认值是empty()意味着没有附加任何 sorter结果无法过滤。实践上建议设为conf.generic_sorter(opts)或conf.file_sorter(opts)。从conf取值会自动尊重用户配置例如用户启用了 fzf-native就会自动挂载对应 sorter同时把opts也传进去是因为 sorter 可能用到它比如 fzf sorter 可以通过opts切换大小写敏感/不敏感。定义完 Picker 后必须调用find()才能真正启动它。4. 通过 opts 切换主题得益于opts的透传我们可以用dropdown主题来打开 Picker把上一节的调用行替换为colors(require(telescope.themes).get_dropdown{})仓库中 lua/telescope/themes.lua 定义了get_dropdown、get_ivy等主题工厂函数它们的返回值就是一份可供pickers.new消费的 opts 表。替换默认 Action现在解决选中颜色却打开了新文件的问题。这需要替换默认的 select 动作。之所以选择替换而非把新函数映射到CR是因为替换会尊重用户的配置如果用户已把select_default重映射到其他按键替换后的逻辑依然会按用户习惯触发。为此需要在文件顶部追加两个 requireslocal actions require telescope.actions local action_state require telescope.actions.stateactions保存了所有可被用户映射的动作我们需要它来访问默认动作并替换它参见:help telescope.actions。action_state提供若干工具函数用于获取当前 Picker、当前选中项、当前输入行参见:help telescope.actions.state。然后在我们传给pickers.new的表例如sorter之后中新增attach_mappings键attach_mappings function(prompt_bufnr, map) actions.select_default:replace(function() actions.close(prompt_bufnr) local selection action_state.get_selected_entry() -- print(vim.inspect(selection)) vim.api.nvim_put({ selection[1] }, , false, true) end) return true end,关键点说明attach_mappings的值是一个函数它必须返回true或false。返回false表示只挂载该函数内定义的动作这会移除默认的move_selection_{next,previous}等移动选择的动作因此绝大多数情况下应当返回true。若函数没有任何返回值会抛出错误。该函数有两个参数prompt_bufnr是 prompt 缓冲区的编号可据此拿到 Picker 对象map是用于把动作或函数映射到任意按键序列的函数。select_default默认映射到CR。替换它需要调用actions.select_default:replace并传入新函数。新函数中先调用actions.close关闭 Picker再用action_state取回selection。注意即使 Picker 已经关闭依然可以用action_state获取选中项和当前输入action_state.get_current_line()。用print(vim.inspect(selection))查看会发现 selection 与我们输入的字符串不同——因为 telescope 内部会把它打包成带多个键的表。这个行为可由 Entry Maker 自定义下一节。最后对 selection 做点实事本例用vim.api.nvim_put把文本放入当前缓冲区。从源码看actions.select_default:replace的底层实现在 lua/telescope/actions/mt.luaaction 实际是一个带元表metatable的对象replace会调用replace_map { [true] v }把新函数写入_replacements表执行时run_replace_or_original会先检查所有 replacement 条件没有匹配才回退到原始函数original_func(...)这就保证了替换而非覆盖的语义。同一个文件中还提供了replace_if(condition, replacement)仅当condition返回 true 时替换与replace_map(tbl)以函数为键的条件映射表等更细粒度的手段我们将在技术解析部分详述。Entry Maker定制展示与匹配Entry Maker 是一个把 Finder 返回的原始条目转换为内部 entry 表的函数entry 表有几个必需的键。它的价值在于展示的字符串与参与匹配/排序的字符串可以完全不同处理文件时还能同时设置绝对路径保证文件总能被找到与用于展示和排序的相对路径这个相对路径甚至不需要在当前工作目录下有效。现在为颜色示例定义 entry_maker并把 results 改成更有内容的表数组finder finders.new_table { results { { red, #ff0000 }, { green, #00ff00 }, { blue, #0000ff }, }, entry_maker function(entry) return { value entry, display entry[1], ordinal entry[1], } end },新的 results 是表数组每个表包含颜色名与十六进制色值。entry_maker依次接收每个表并产出 entryvalue推荐保存对原始条目的引用这样在 action 里总能拿到完整的原始表。display必填可以是字符串也可以是function(tbl)tbl是 entry_maker 返回的表因此可以访问到value、ordinal等字段。如果条目很多建议用函数形式的display尤其当你要修改展示文本时这样它只会在条目实际被显示时执行避免不必要的开销。ordinal必填用于排序/过滤。正因为 display 与 ordinal 分离我们才能让 display 携带图标、特殊标记等复杂内容而 ordinal 只保留简单的排序键。除以上键外还有几个在本例中用不到但处理文件时很重要的键path设置文件的绝对路径保证文件始终能被找到lnum指定文件中的行号让conf.grep_previewer能定位到该行并让默认动作跳转到该行。仓库中 lua/telescope/make_entry.lua 是内置 Entry Maker 的权威示例其文件头部注释还列出了 entry 表的完整键位包括可选的valid设为 false 时 Picker 不展示该条目、filename默认CR动作会将其解释为打开该文件、bufnr、col等。想让 display 呈现类似表格的多列效果可以借助 lua/telescope/pickers/entry_display.lua 中的 displayer一个更简单的 displayer 示例是 make_entry.lua 中的gen_from_git_commits函数它用entry_display.create构造了8 列哈希 剩余宽度提交信息的展示布局并返回一个闭包作为 entry_maker。Previewer何时需要本示例基础颜色选择器不需要 Previewer这是更进阶的主题在:help telescope.previewers中有完善说明。如果你需要一个不带列的文件预览器默认应选用conf.file_previewer或conf.grep_previewer——和 sorter 同理从conf取值会自动尊重用户配置。Oneshot Job异步外部进程结果oneshot_jobFinder 用于启动一个异步外部进程进程逐行输出结果每行都会调用entry_maker生成条目。典型用法是把find命令的结果喂给 Pickerfinder finders.new_oneshot_job({ find }, opts ),源码实现见 lua/telescope/finders.lua 的finders.new_oneshot_job它把命令列表的第一个元素当作command、其余作为args返回一个async_oneshot_finder由 lua/telescope/finders/async_oneshot_finder.lua 提供并支持entry_maker、cwd、maximum_results、split_char等 opts 选项。更多示例lua/telescope/builtin 目录包含全部内置 Picker是寻找更多编写范式的最佳去处内置 Picker 也正是用本文介绍的概念写成的。社区已有很多基于这些概念编写的扩展可供参考例如 telescope-fzf-native 这类提供替代 sorter 的扩展。读完本指南仍有疑问可以到项目 Discussions 提问。打包为 Extension要把 Picker 打包成可通过:Telescope命令调用的扩展需要按如下结构组织插件保证 telescope 能够发现它. └── lua ├── plugin_name # Your actual plugin code │ ├── init.lua │ └── some_file.lua └── telescope └── _extensions # The underscore is significant └─ plugin_name.lua # Init and register your extension注意_extensions目录名的下划线是有意义的。lua/telescope/_extensions/plugin_name.lua文件需要返回如下结构参见:help telescope.register_extensionreturn require(telescope).register_extension { setup function(ext_config, config) -- access extension config and user config end, exports { stuff require(plugin_name).stuff }, }setup函数可以访问扩展配置与用户 telescope 默认配置用于设置扩展专属的全局配置也允许覆盖内部函数——例如为扩展提供替代 sorter像 telescope-fzf-native 那样。exports表声明导出的 Picker之后可以通过Telescope plugin_name stuff访问。如果只导出一个功能建议把键名与插件名保持一致这样直接用Telescope plugin_name即可调用。仓库中的扩展注册机制实现在 lua/telescope/_extensions/init.luaextensions.register原样返回模块load_extension用pcall(require, telescope._extensions. .. name)加载模块并给出友好错误extensions.manager的元表会在首次访问某个扩展时调用其setup(extensions._config[k] or {}, require(telescope.config).values)并把ext.exports暴露给用户——这就是require(telescope).extensions.foo背后发生的事情。文档注释还指出exports中键名不以_开头且值为函数的条目会在启用include_extensions选项的内置 Picker 中一并出现。技术解析Picker 的可配置字段以下摘录自 lua/telescope/pickers.lua是创建自定义 Picker 时的字段总览-- lua/telescope/pickers.lua Picker:new{ prompt_title , finder FUNCTION, -- see lua/telescope/finders.lua sorter FUNCTION, -- see lua/telescope/sorters.lua previewer FUNCTION, -- see lua/telescope/previewers/previewer.lua selection_strategy reset, -- follow, reset, row border {}, borderchars {─, │, ─, │, ┌, ┐, ┘, └}, default_selection_index 1, -- Change the index of the initial selection row }其中selection_strategy在源码pickers.lua中实际支持row、follow、reset、closest、none等多种策略用于定义 prompt 内容变化时选中行如何迁移borderchars与window配置联动未显式设置时回落到config.values中的默认值。default_selection_index控制 Picker 初始选中第几行从 1 开始。Finder 的字段摘录自 lua/telescope/finders.lua-- lua/telescope/finders.lua Finder:new{ entry_maker function(line) end, fn_command function() { command , args { ls-files } } end, static false, maximum_results false }从实现看Finder 家族包括JobFinder通过外部 Job 获取结果并边到达边处理、DynamicFinder调用opts.fn(prompt)同步返回结果列表、以及new_table背后的async_static_finder、new_oneshot_job背后的async_oneshot_finder与new_job的async_job_finder分别见 lua/telescope/finders/async_static_finder.lua、async_oneshot_finder.lua、async_job_finder.lua。maximum_results对实时更新的大型查询尤其有用可限制处理的结果数量。覆盖 Actions / Action Set以下文件是理解 action 机制的关键lua/telescope/actions/init.lua最面向用户的文件包含我们提供的全部内置动作。lua/telescope/actions/set.lua第二面向用户的文件提供被多个内置动作共同消费的 action set从而允许只覆盖其中一项而不必复制多份相同配置/函数。lua/telescope/actions/state.lua提供在 action 内部与 telescope 状态交互的 API如get_selected_entry()、get_current_line()、get_current_picker(prompt_bufnr)见 state.lua对编写自定义 action 很有用。lua/telescope/actions/mt.lua定义了 action 的行为机制一般情况下无需深入但了解它能更好地理解下面的替换 API。:replace(function)—— 直接覆盖local actions require(telescope.actions) actions.select_default:replace(git_checkout_function):replace_if(conditional, function)—— 条件覆盖local action_set require(telescope.actions.set) action_set.select:replace_if( function() return action_state.get_selected_entry().path:sub(-1) os_sep end, function(_, type) -- type is { default, horizontal, vertical, tab } local path actions.get_selected_entry().path action_state.get_current_picker(prompt_bufnr):refresh(gen_new_finder(new_cwd), { reset_prompt true}) end ):replace_map(configuration)—— 多条件映射local action_set require(telescope.actions.set) -- Use functions as keys to map to which function to execute when called. action_set.select:replace_map { [function(e) return e 0 end] function(e) return (e / 10) end, [function(e) return e 0 end] function(e) return (e 10) end, }结合 mt.lua 的实现可以更清晰地理解这套 API每个 action 都是一张带元表的表内部维护_static_pre、_pre、_replacements、_static_post、_post等表调用 action 时先执行静态前置钩子再经run_replace_or_original按序尝试各个 replacement 条件条件为true或条件函数返回真即命中全部不命中才执行原始函数随后执行后置钩子。replace等价于replace_map { [true] v }即无条件替换replace_if则是单条件版本。此外enhance(opts)可以给 action 附加pre/post钩子__add元方法还支持把多个 action 组合成一个。Previewers参见:help telescope.previewers仓库实现位于 lua/telescope/previewers/含buffer_previewer.lua、term_previewer.lua、previewer.lua等其中previewer.lua是自定义 Previewer 的基类所在。小结至此你已完成从零到一的完整链路用pickers.newfinders.new_table创建静态 Picker → 用attach_mappingsactions.select_default:replace替换默认动作 → 用entry_maker定制展示/匹配/排序 → 用finders.new_oneshot_job接入异步外部进程 → 最后把整套逻辑打包成_extensions扩展并通过register_extension暴露给:Telescope命令。借助conf与opts的透传你的自定义 Picker 还能自动继承用户的 sorter、主题与按键配置真正做到All lua, all the time。【免费下载链接】telescope.nvimFind, Filter, Preview, Pick. All lua, all the time.项目地址: https://gitcode.com/GitHub_Trending/te/telescope.nvim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考