From af2ffefe248b2bbdc1e489a6727e83c491cf36dd Mon Sep 17 00:00:00 2001 From: Aymen Hafeez <49293546+aymenhafeez@users.noreply.github.com> Date: Sun, 28 Dec 2025 18:21:49 +0000 Subject: [PATCH 1/2] feat: added telescope.nvim integration. :Telescope pydoc to fuzzy find over documentation files and :Telescope pydoc grep to grep throught the documentation --- README.md | 33 ++-- lua/pydoc-nvim.lua | 5 + lua/pydoc-nvim/telescope.lua | 252 ++++++++++++++++++++++++++++ lua/telescope/_extensions/pydoc.lua | 6 + 4 files changed, 282 insertions(+), 14 deletions(-) create mode 100644 lua/pydoc-nvim/telescope.lua create mode 100644 lua/telescope/_extensions/pydoc.lua diff --git a/README.md b/README.md index 10e8e23..d7720e4 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ This plugin is 90% a copy of https://github.com/girishji/pythondoc.vim, which is In addition to pythondoc.vim, this plugin let's you choose between Python versions: 3.8 to 3.14. ---- -[![asciicast](https://asciinema.org/a/661709.svg)](https://asciinema.org/a/661709) ---- +*** + +## [![asciicast](https://asciinema.org/a/661709.svg)](https://asciinema.org/a/661709) ## Install @@ -17,7 +17,6 @@ Install via your preffered package manager. `RazorBest/pydoc.nvim` - ## Setup Setup the plugin in your `init.lua` @@ -41,6 +40,11 @@ to find the matches for the keyword. `:PyDocVersion ` change the version of the documentation +### Telescope integration + +If you have telescope.nvim installed you can use `:Telescope pydoc` to fuzzy +find over documentation files or `:Telescope pydoc grep` to grep through the +documentation. ## Updating the docs @@ -53,13 +57,14 @@ In order to update all the versions, run from the root of this repo: `./build_python_docs.sh` This script permforms the following: -- Clones the Python repo in tmp/cpython -- Extracts the available versions greater than `MIN_VERSION` -- Checks out on every version and performs the following, for each: - * Installs the virtual environment for the `Doc` and `vimbuilder` - * Modifies `Doc/conf.py` for `vimbuilder` - * Creates a makefile that runs sphinx with the `vimbuilder` extension - * Runs the makefile - * Copies the built files in `python_docs`, in the corresponding version - * Adds the Python version at the beginning of all the help files - * Runs vim helptags on the files + +* Clones the Python repo in tmp/cpython +* Extracts the available versions greater than `MIN_VERSION` +* Checks out on every version and performs the following, for each: + * Installs the virtual environment for the `Doc` and `vimbuilder` + * Modifies `Doc/conf.py` for `vimbuilder` + * Creates a makefile that runs sphinx with the `vimbuilder` extension + * Runs the makefile + * Copies the built files in `python_docs`, in the corresponding version + * Adds the Python version at the beginning of all the help files + * Runs vim helptags on the files diff --git a/lua/pydoc-nvim.lua b/lua/pydoc-nvim.lua index 35ddcf6..0f9137c 100644 --- a/lua/pydoc-nvim.lua +++ b/lua/pydoc-nvim.lua @@ -74,6 +74,11 @@ M.setup = function(opts) version = opts["version"] or M.latest_version M.select_version(version, true) + + local has_telescope, telescope = pcall(require, "telescope") + if has_telescope then + pcall(telescope.load_extension, "pydoc") + end end diff --git a/lua/pydoc-nvim/telescope.lua b/lua/pydoc-nvim/telescope.lua new file mode 100644 index 0000000..992ffa9 --- /dev/null +++ b/lua/pydoc-nvim/telescope.lua @@ -0,0 +1,252 @@ +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local conf = require("telescope.config").values +local actions = require("telescope.actions") +local action_state = require("telescope.actions.state") +local action_set = require("telescope.actions.set") +local make_entry = require("telescope.make_entry") + +local M = {} + +-- get current Python version from pydoc.nvim +local function get_pydoc() + local present, pydoc = pcall(require, "pydoc-nvim") + if not present then + vim.notify("pydoc.nvim not found. Please install RazorBest/pydoc.nvim", vim.log.levels.ERROR) + return nil + end + + if not pydoc.current_version then + vim.notify("pydoc.nvim not initialised. Please call require('pydoc-nvim').setup()", vim.log.levels.ERROR) + return nil + end + + return pydoc +end + +-- find tags file for specific version +local function find_tags_file(doc_path) + local tags_file = doc_path .. "/tags-py" + if vim.fn.filereadable(tags_file) == 1 then + return tags_file + end + return nil +end + +-- ensure tags file exists, generate if needed +local function ensure_tags_exist(doc_path) + local tags_file = find_tags_file(doc_path) + if not tags_file then + -- local versioned_doc_path = doc_path .. "/doc_py" .. version + vim.notify("Generating tags for Python Documentation...", vim.log.levels.INFO) + vim.cmd("helptags " .. vim.fn.fnameescape(doc_path)) + end +end + +-- read and parse Python help tags +local function get_python_help_tags(doc_path) + local tags_file = find_tags_file(doc_path) + if not tags_file then + return nil, "Failed to find or generate tags file for Python" + end + + local tags = {} + local file = io.open(tags_file, "r") + if not file then + return nil, "Could not open tags file: " .. tags_file + end + + -- tab character + local delimiter = string.char(9) + for line in file:lines() do + -- skip comment lines (tags file metadata) + if not line:match("^!_TAG_") then + -- parse: tag_namefilenamesearch_pattern + local fields = vim.split(line, delimiter, { trimempty = true }) + if #fields >= 3 then + local tag_name = fields[1] + local filename = fields[2] + local cmd = fields[3] + + -- filter for .pyx files + if filename:match("%.pyx$") then + local full_filename = doc_path .. "/" .. filename + table.insert(tags, { + name = tag_name, + filename = full_filename, + cmd = cmd, + }) + end + end + end + end + file:close() + return tags, nil +end + +local function setup_pydoc() + local pydoc = get_pydoc() + if not pydoc then + return nil + end + + local version = pydoc.current_version + local doc_path = pydoc.python_docs[version] + + if not doc_path then + vim.notify("Documentation path not found for Python version " .. version, vim.log.levels.ERROR) + return nil + end + + ensure_tags_exist(doc_path) + + local tags, err = get_python_help_tags(doc_path) + if not tags then + vim.notify(err or "Failed to load Python help tags", vim.log.levels.ERROR) + return nil + end + + if #tags == 0 then + vim.notify("No Python help tags found for Python version " .. version, vim.log.levels.WARN) + return nil + end + + return { + version = version, + tags = tags, + doc_path = doc_path, + } +end + +M.pydoc = function(opts) + opts = opts or {} + + local setup = setup_pydoc() + if not setup then + return + end + + pickers + .new(opts, { + prompt_title = "Python Documentation (v" .. setup.version .. ")", + finder = finders.new_table({ + results = setup.tags, + entry_maker = function(entry) + return make_entry.set_default_entry_mt({ + value = entry.name, + display = entry.name, + ordinal = entry.name, + filename = entry.filename, + cmd = entry.cmd, + }, opts) + end, + }), + + sorter = conf.generic_sorter(opts), + previewer = conf.file_previewer(opts), + + attach_mappings = function(prompt_bufnr) + ---@diagnostic disable-next-line: undefined-field + action_set.select:replace(function(_) + local selection = action_state.get_selected_entry() + if not selection then + vim.notify("No selection", vim.log.levels.WARN) + return + end + actions.close(prompt_bufnr) + vim.cmd("help " .. selection.value) + end) + return true + end, + }) + :find() +end + +M.pydoc_grep = function(opts) + opts = opts or {} + + local setup = setup_pydoc() + if not setup then + return + end + + local file_paths = {} + local seen = {} + for _, tag in ipairs(setup.tags) do + if not seen[tag.filename] then + table.insert(file_paths, tag.filename) + seen[tag.filename] = true + end + end + + pickers + .new(opts, { + prompt_title = "Grep Python Documentation (v" .. setup.version .. ")", + finder = finders.new_async_job({ + command_generator = function(prompt) + if not prompt or prompt == "" then + return nil + end + + local args = { + "rg", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + "-e", + prompt, + } + + -- add .pyx file paths + for _, path in ipairs(file_paths) do + table.insert(args, path) + end + + return args + end, + + entry_maker = function(entry) + local default_entry = make_entry.gen_from_vimgrep(opts)(entry) + if default_entry then + local filename = default_entry.filename:match("([^/]+)$") + default_entry.display = function(e) + return string.format("%s:%s:%s: %s", filename, e.lnum, e.col, e.text) + end + end + return default_entry + end, + }), + + sorter = conf.generic_sorter(opts), + previewer = conf.grep_previewer(opts), + + attach_mappings = function(prompt_bufnr) + ---@diagnostic disable-next-line: undefined-field + action_set.select:replace(function(_) + local selection = action_state.get_selected_entry() + if not selection then + vim.notify("No selection", vim.log.levels.WARN) + return + end + actions.close(prompt_bufnr) + + -- get filename from full path + local filename = selection.filename + local lnum = selection.lnum or 1 + + -- get just the .pyx filename + local help_file = filename:match("([^/]+)$") + + vim.cmd("help " .. help_file) + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + end) + return true + end, + }) + :find() +end + +return M diff --git a/lua/telescope/_extensions/pydoc.lua b/lua/telescope/_extensions/pydoc.lua new file mode 100644 index 0000000..0130b5e --- /dev/null +++ b/lua/telescope/_extensions/pydoc.lua @@ -0,0 +1,6 @@ +return require("telescope").register_extension({ + exports = { + pydoc = require("pydoc-nvim.telescope").pydoc, + grep = require("pydoc-nvim.telescope").pydoc_grep, + }, +}) From fda18469030172f597058b3e306a835657b6d7bb Mon Sep 17 00:00:00 2001 From: Aymen Hafeez <49293546+aymenhafeez@users.noreply.github.com> Date: Sun, 28 Dec 2025 18:35:22 +0000 Subject: [PATCH 2/2] README correction --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d7720e4..77a86de 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ This plugin is 90% a copy of https://github.com/girishji/pythondoc.vim, which is In addition to pythondoc.vim, this plugin let's you choose between Python versions: 3.8 to 3.14. -*** - -## [![asciicast](https://asciinema.org/a/661709.svg)](https://asciinema.org/a/661709) +--- +[![asciicast](https://asciinema.org/a/661709.svg)](https://asciinema.org/a/661709) +--- ## Install @@ -17,6 +17,7 @@ Install via your preffered package manager. `RazorBest/pydoc.nvim` + ## Setup Setup the plugin in your `init.lua` @@ -57,14 +58,13 @@ In order to update all the versions, run from the root of this repo: `./build_python_docs.sh` This script permforms the following: - -* Clones the Python repo in tmp/cpython -* Extracts the available versions greater than `MIN_VERSION` -* Checks out on every version and performs the following, for each: - * Installs the virtual environment for the `Doc` and `vimbuilder` - * Modifies `Doc/conf.py` for `vimbuilder` - * Creates a makefile that runs sphinx with the `vimbuilder` extension - * Runs the makefile - * Copies the built files in `python_docs`, in the corresponding version - * Adds the Python version at the beginning of all the help files - * Runs vim helptags on the files +- Clones the Python repo in tmp/cpython +- Extracts the available versions greater than `MIN_VERSION` +- Checks out on every version and performs the following, for each: + * Installs the virtual environment for the `Doc` and `vimbuilder` + * Modifies `Doc/conf.py` for `vimbuilder` + * Creates a makefile that runs sphinx with the `vimbuilder` extension + * Runs the makefile + * Copies the built files in `python_docs`, in the corresponding version + * Adds the Python version at the beginning of all the help files + * Runs vim helptags on the files