From 18658156879cee4cf5d7b6f28e85dfc21237fa46 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 16 Sep 2020 18:20:14 -0400 Subject: [PATCH] feat: Easy Termopen for Previewer (#88) First pass, but seems to work. Fixed a few other related items, so pushing it to master now. * [WIP]: Wed 16 Sep 2020 05:17:10 PM EDT * [WIP]: Wed 16 Sep 2020 06:10:33 PM EDT * [WIP]: Wed 16 Sep 2020 06:19:43 PM EDT --- lua/telescope/actions.lua | 4 + lua/telescope/builtin.lua | 4 +- lua/telescope/from_entry.lua | 26 ++++ lua/telescope/make_entry.lua | 10 +- lua/telescope/opts.lua | 17 --- lua/telescope/previewers.lua | 230 ++++++++++++++++++++--------------- lua/telescope/utils.lua | 13 ++ 7 files changed, 180 insertions(+), 124 deletions(-) create mode 100644 lua/telescope/from_entry.lua delete mode 100644 lua/telescope/opts.lua diff --git a/lua/telescope/actions.lua b/lua/telescope/actions.lua index 224a3e4..74d6c56 100644 --- a/lua/telescope/actions.lua +++ b/lua/telescope/actions.lua @@ -131,6 +131,10 @@ function actions.close(prompt_bufnr) local prompt_win = state.get_status(prompt_bufnr).prompt_win local original_win_id = picker.original_win_id + if picker.previewer then + picker.previewer:teardown() + end + actions.close_pum(prompt_bufnr) vim.cmd [[stopinsert]] diff --git a/lua/telescope/builtin.lua b/lua/telescope/builtin.lua index f1f8739..5b9a678 100644 --- a/lua/telescope/builtin.lua +++ b/lua/telescope/builtin.lua @@ -115,7 +115,7 @@ builtin.live_grep = function(opts) return nil end - return flatten { conf.vimgrep_arguments, " --", prompt } + return flatten { conf.vimgrep_arguments, prompt } end, opts.entry_maker or make_entry.gen_from_vimgrep(opts), opts.max_results or 1000 @@ -347,7 +347,7 @@ builtin.grep_string = function(opts) pickers.new(opts, { prompt = 'Find Word', finder = finders.new_oneshot_job( - flatten { conf.vimgrep_arguments, " --", search}, + flatten { conf.vimgrep_arguments, search}, opts ), previewer = previewers.vimgrep.new(opts), diff --git a/lua/telescope/from_entry.lua b/lua/telescope/from_entry.lua new file mode 100644 index 0000000..f6b2a62 --- /dev/null +++ b/lua/telescope/from_entry.lua @@ -0,0 +1,26 @@ +--[[ ============================================================================= + +Get metadata from entries. + +This file is still WIP, so expect some changes if you're trying to consume these APIs. + +This will provide standard mechanism for accessing information from an entry. + +--============================================================================= ]] + +local from_entry = {} + +function from_entry.path(entry, validate) + local path = entry.path + if path == nil then path = entry.filename end + if path == nil then path = entry.value end + if path == nil then print("Invalid entry", vim.inspect(entry)); return end + + if validate and not vim.fn.filereadable(path) then + return + end + + return path +end + +return from_entry diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua index 90562bd..99735a2 100644 --- a/lua/telescope/make_entry.lua +++ b/lua/telescope/make_entry.lua @@ -11,21 +11,13 @@ make_entry.types = { local transform_devicons if has_devicons then - _DeviconStore = _DeviconStore or {} - transform_devicons = function(filename, display, opts) - if opts.disable_devicons then + if opts.disable_devicons or not filename then return display end - if _DeviconStore[filename] then - return _DeviconStore[filename] - end - local icon_display = (devicons.get_icon(filename, string.match(filename, '%a+$')) or ' ') .. ' ' .. display - _DeviconStore[filename] = icon_display - return icon_display end else diff --git a/lua/telescope/opts.lua b/lua/telescope/opts.lua deleted file mode 100644 index 0c3de91..0000000 --- a/lua/telescope/opts.lua +++ /dev/null @@ -1,17 +0,0 @@ - -local opts_manager = {} - --- Could use cool metatable to do this automatically --- Idk, I have some other thoughts. -opts_manager.shorten_path = function(opts) - if opts.shorten_path ~= nil then - return opts.shorten_path - elseif config.values.shorten_path ~= nil then - return config.values.shorten_path - else - return true - end -end - - -return opts_manager diff --git a/lua/telescope/previewers.lua b/lua/telescope/previewers.lua index df5db16..abb59fb 100644 --- a/lua/telescope/previewers.lua +++ b/lua/telescope/previewers.lua @@ -1,8 +1,13 @@ local context_manager = require('plenary.context_manager') +local from_entry = require('telescope.from_entry') local log = require('telescope.log') local utils = require('telescope.utils') +local get_default = utils.get_default +local buf_delete = utils.buf_delete +local job_is_running = utils.job_is_running + local defaulter = utils.make_default_callable local previewers = {} @@ -15,6 +20,18 @@ local bat_options = " --style=plain --color=always " local previewer_ns = vim.api.nvim_create_namespace('telescope.previewers') +local with_preview_window = function(status, bufnr, callable) + if bufnr and vim.api.nvim_buf_call then + vim.api.nvim_buf_call(bufnr, callable) + else + return context_manager.with(function() + vim.cmd(string.format("noautocmd call win_gotoid(%s)", status.preview_win)) + coroutine.yield() + vim.cmd(string.format("noautocmd call win_gotoid(%s)", status.prompt_win)) + end, callable) + end +end + -- --terminal-width=%s -- TODO: We shoudl make sure that all our terminals close all the way. @@ -41,13 +58,12 @@ function Previewer:preview(entry, status) if not self.state then if self._setup_func then - self.state = self._setup_func() + self.state = self:_setup_func() else self.state = {} end end - self:teardown() return self:preview_fn(entry, status) end @@ -77,12 +93,104 @@ previewers.new = function(...) return Previewer:new(...) end -local with_preview_window = function(status, callable) - return context_manager.with(function() - vim.cmd(string.format("noautocmd call win_gotoid(%s)", status.preview_win)) - coroutine.yield() - vim.cmd(string.format("noautocmd call win_gotoid(%s)", status.prompt_win)) - end, callable) +previewers.new_termopen_previewer = function(opts) + opts = opts or {} + + assert(opts.get_command, "get_command is a required function") + + local opt_setup = opts.setup + local opt_teardown = opts.teardown + + local old_bufs = {} + + local function get_term_id(self) return self.state.termopen_id end + local function get_bufnr(self) return self.state.termopen_bufnr end + + local function set_term_id(self, value) + if job_is_running(get_term_id(self)) then vim.fn.jobstop(get_term_id(self)) end + self.state.termopen_id = value + end + local function set_bufnr(self, value) + if get_bufnr(self) then table.insert(old_bufs, get_bufnr(self)) end + self.state.termopen_bufnr = value + end + + local function setup(self) + local state = {} + if opt_setup then + vim.tbl_deep_extend("force", state, opt_setup(self)) + end + + return state + end + + local function teardown(self) + if opt_teardown then + opt_teardown(self) + end + + local term_id = get_term_id(self) + if term_id and utils.job_is_running(term_id) then + vim.fn.jobclose(term_id) + end + + set_term_id(self, nil) + set_bufnr(self, nil) + + for _, bufnr in ipairs(old_bufs) do + buf_delete(bufnr) + end + end + + local function preview_fn(self, entry, status) + if get_bufnr(self) == nil then + set_bufnr(self, vim.api.nvim_win_get_buf(status.preview_win)) + end + + set_bufnr(self, vim.api.nvim_create_buf(false, true)) + + local bufnr = get_bufnr(self) + vim.api.nvim_win_set_buf(status.preview_win, bufnr) + + local term_opts = { + cwd = opts.cwd or vim.fn.getcwd(), + } + + with_preview_window(status, bufnr, function() + set_term_id( + self, + vim.fn.termopen(opts.get_command(entry, status), term_opts) + ) + end) + + vim.api.nvim_buf_set_name(bufnr, tostring(bufnr)) + end + + return Previewer:new { + setup = setup, + teardown = teardown, + preview_fn = preview_fn, + + send_input = function(self, input) + local termcode = vim.api.nvim_replace_termcodes(input, true, false, true) + + local term_id = get_term_id(self) + if term_id then + vim.fn.chansend(term_id, termcode) + end + end, + + scroll_fn = function(self, direction) + if not self.state then + return + end + + local input = direction > 0 and "d" or "u" + local count = math.abs(direction) + + self:send_input(count..input) + end, + } end previewers.termopen = defaulter(function(opts) @@ -94,7 +202,7 @@ previewers.termopen = defaulter(function(opts) vim.api.nvim_win_set_buf(status.preview_win, bufnr) - with_preview_window(status, function() + with_preview_window(status, bufnr, function() vim.fn.termopen(string.format(command_string, entry.value)) end) end @@ -191,108 +299,38 @@ previewers.vim_buffer_or_bat = defaulter(function(_) end, {}) previewers.cat = defaulter(function(opts) - return previewers.new { - setup = function() - local command_string = "cat -- '%s'" - if 1 == vim.fn.executable("bat") then - command_string = "bat " .. bat_options .. " -- '%s'" - end - - return { - command_string = command_string, - termopen_id = nil, - } - end, - - send_input = function(self, input) - termcode = vim.api.nvim_replace_termcodes(input, true, false, true) - if self.state.termopen_id then - pcall(vim.fn.chansend, self.state.termopen_id, termcode) - end - end, - - scroll_fn = function(self, direction) - if not self.state then - return - end - if direction > 0 then input = "d" else input = "u" end - local count = math.abs(direction) - self:send_input(count..input) - end, - - teardown = function(self) - if not self.state then + return previewers.new_termopen_previewer { + get_command = function(entry) + local path = from_entry.path(entry, true) + if path == nil then return end - if self.state.termopen_id then - pcall(vim.fn.chanclose, self.state.termopen_id) - end - end, - - preview_fn = function(self, entry, status) - local bufnr = vim.api.nvim_create_buf(false, true) - - vim.api.nvim_win_set_buf(status.preview_win, bufnr) - - local path = entry.path - if path == nil then path = entry.filename end - if path == nil then path = entry.value end - if path == nil then print("Invalid entry", vim.inspect(entry)); return end - - local term_opts = vim.empty_dict() - term_opts.cwd = opts.cwd - - with_preview_window(status, function() - self.state.termopen_id = vim.fn.termopen(string.format(self.state.command_string, path), term_opts) - end) - - vim.api.nvim_buf_set_name(bufnr, tostring(bufnr)) + return string.format('bat %s -- "%s"', bat_options, path) end } end, {}) previewers.vimgrep = defaulter(function(_) - return previewers.new { - setup = function() - local command_string = "cat -- '%s'" - if vim.fn.executable("bat") then - command_string = "bat --highlight-line '%s' -r '%s':'%s'" .. bat_options .. " -- '%s'" - end + local command_string = "cat -- '%s'" + if vim.fn.executable("bat") then + command_string = "bat --highlight-line '%s' -r '%s':'%s'" .. bat_options .. " -- '%s'" + end - return { - command_string = command_string - } - end, - - preview_fn = function(self, entry, status) - local bufnr = vim.api.nvim_create_buf(false, true) + return previewers.new_termopen_previewer { + get_command = function(entry, status) local win_id = status.preview_win local height = vim.api.nvim_win_get_height(win_id) - local line = entry.value - if type(line) == "table" then - line = entry.ordinal - end - - local _, _, filename, lnum, col, text = string.find(line, [[([^:]+):(%d+):(%d+):(.*)]]) - - filename = filename or entry.filename - lnum = lnum or entry.lnum or 0 + local filename = entry.filename + local lnum = entry.lnum or 0 local context = math.floor(height / 2) local start = math.max(0, lnum - context) local finish = lnum + context - vim.api.nvim_win_set_buf(status.preview_win, bufnr) - - local termopen_command = string.format(self.state.command_string, lnum, start, finish, filename) - - with_preview_window(status, function() - vim.fn.termopen(termopen_command) - end) - - end + return string.format(command_string, lnum, start, finish, filename) + end, } end, {}) @@ -331,7 +369,7 @@ previewers.qflist = defaulter(function(_) local termopen_command = string.format(self.state.command_string, filename, lnum, start, finish) - with_preview_window(status, function() + with_preview_window(status, bufnr, function() vim.fn.termopen(termopen_command) end) end @@ -341,7 +379,7 @@ end, {}) previewers.help = defaulter(function(_) return previewers.new { preview_fn = function(_, entry, status) - with_preview_window(status, function() + with_preview_window(status, nil, function() local old_tags = vim.o.tags vim.o.tags = vim.fn.expand("$VIMRUNTIME") .. '/doc/tags' diff --git a/lua/telescope/utils.lua b/lua/telescope/utils.lua index fb7d48b..db77a7f 100644 --- a/lua/telescope/utils.lua +++ b/lua/telescope/utils.lua @@ -142,4 +142,17 @@ function utils.get_separator() return package.config:sub(1, 1) end +function utils.job_is_running(job_id) + if job_id == nil then return false end + return vim.fn.jobwait({job_id}, 0)[1] == -1 +end + +function utils.buf_delete(bufnr) + if bufnr == nil then return end + + if vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) then + vim.cmd(string.format("bdelete! %s", bufnr)) + end +end + return utils