-- lua/plugins/pdf-viewer.lua -- PDF viewer using pdftoppm + image.nvim (kitty/magick backend) -- Requires: poppler (brew install poppler), kitty terminal return { '3rd/image.nvim', opts = { backend = 'kitty', processor = 'magick_cli', integrations = { markdown = { enabled = true }, }, }, config = function(_, opts) local image = require 'image' image.setup(opts) local pdf_state = {} -- Open PDF: convert all pages to PNG via pdftoppm, then display page 1 vim.api.nvim_create_autocmd('BufReadPost', { pattern = '*.pdf', callback = function() local pdf_path = vim.fn.expand '%:p' local temp_dir = vim.fn.tempname() vim.fn.mkdir(temp_dir, 'p') local bufnr = vim.api.nvim_get_current_buf() vim.notify('Converting PDF…', vim.log.levels.INFO) vim.fn.jobstart({ 'pdftoppm', '-png', pdf_path, temp_dir .. '/page' }, { on_exit = function(_, code) vim.schedule(function() if code ~= 0 then vim.notify('Failed to convert PDF', vim.log.levels.ERROR) return end -- Count pages local handle = vim.loop.fs_scandir(temp_dir) local page_count = 0 if handle then while true do local name = vim.loop.fs_scandir_next(handle) if not name then break end if name:match '^page%-%d+%.png$' then page_count = page_count + 1 end end end pdf_state[bufnr] = { temp_dir = temp_dir, current_page = 1, total_pages = page_count } local function show_page(page_num) local state = pdf_state[bufnr] if not state then return end image.clear() vim.bo[bufnr].modifiable = true vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '' }) vim.bo[bufnr].modifiable = false local page_file = string.format('%s/page-%02d.png', state.temp_dir, page_num) local img = image.from_file(page_file, { buffer = bufnr }) if img then img:render() vim.notify(string.format('Page %d / %d', page_num, state.total_pages), vim.log.levels.INFO) else vim.notify('Failed to load page ' .. page_num, vim.log.levels.ERROR) end end local function change_page(delta) local state = pdf_state[bufnr] if not state then return end local new_page = state.current_page + delta if new_page < 1 or new_page > state.total_pages then return end state.current_page = new_page show_page(new_page) end -- Buffer-local navigation keys local map = function(lhs, fn, desc) vim.keymap.set('n', lhs, fn, { buffer = bufnr, desc = desc }) end map('j', function() change_page(1) end, 'PDF: next page') map('k', function() change_page(-1) end, 'PDF: previous page') map('', function() change_page(1) end, 'PDF: next page') map('', function() change_page(-1) end, 'PDF: previous page') map('q', 'bd!', 'PDF: close') show_page(1) end) end, }) end, }) -- Cleanup temp files on buffer close vim.api.nvim_create_autocmd('BufDelete', { pattern = '*.pdf', callback = function(args) if pdf_state[args.buf] then image.clear() vim.fn.delete(pdf_state[args.buf].temp_dir, 'rf') pdf_state[args.buf] = nil end end, }) end, }