From b952c2f852de1ec5ce7306ccd9262c97e27902d2 Mon Sep 17 00:00:00 2001 From: johnybx Date: Mon, 8 Nov 2021 18:20:17 +0100 Subject: [PATCH] feat: add hide / unhide preview (#1305) * feat: add hide / unhide preview or results and prompt * fix edit actions while only preview window is active * add option to start telescope without previewer -> "disable_previewer_at_startup" * remove hide / unhide prompt and resutls, improve hide / unhide preview * fix tests - check if popup window should be created with borders - popup.create does not support setting borderhighlight and border=false * allow toggle preview even if preview is set to false * reuse recalculate_layout in hide / unhide preview * add docs to toggle preview * check if preview window is valid window * remove unnecessary changes * cleanup, remove unnecessary changes * close all previewers, refactor toggle preview code [docgen] Update doc/telescope.txt skip-checks: true --- doc/telescope.txt | 12 +++ lua/telescope/actions/init.lua | 18 ++-- lua/telescope/config.lua | 4 + lua/telescope/pickers.lua | 154 +++++++++++++++++++-------------- lua/telescope/utils.lua | 19 ++++ 5 files changed, 134 insertions(+), 73 deletions(-) diff --git a/doc/telescope.txt b/doc/telescope.txt index 54691a6..3c89161 100644 --- a/doc/telescope.txt +++ b/doc/telescope.txt @@ -395,6 +395,9 @@ telescope.setup({opts}) *telescope.setup()* Default: true - msg_bg_fillchar: Character to fill background of unpreviewable buffers with Default: "╱" + - hide_on_startup: Hide previewer when picker starts. Previewer can be toggled + with actions.toggle_preview. + Default: false *telescope.defaults.vimgrep_arguments* @@ -1909,6 +1912,15 @@ actions.toggle_all({prompt_bufnr}) *actions.toggle_all()* {prompt_bufnr} (number) The prompt bufnr +actions.toggle_preview({prompt_bufnr}) *actions.toggle_preview()* + Toggle preview window. + - Note: preview window can be toggled even if preview is set to false. + + + Parameters: ~ + {prompt_bufnr} (number) The prompt bufnr + + actions.git_create_branch({prompt_bufnr}) *actions.git_create_branch()* Create and checkout a new git branch if it doesn't already exist diff --git a/lua/telescope/actions/init.lua b/lua/telescope/actions/init.lua index e87bac6..bafa914 100644 --- a/lua/telescope/actions/init.lua +++ b/lua/telescope/actions/init.lua @@ -144,6 +144,13 @@ function actions.toggle_all(prompt_bufnr) end) end +--- Toggle preview window. +--- - Note: preview window can be toggled even if preview is set to false. +---@param prompt_bufnr number: The prompt bufnr +function actions.toggle_preview(prompt_bufnr) + action_state.get_current_picker(prompt_bufnr):toggle_preview() +end + function actions.preview_scrolling_up(prompt_bufnr) action_set.scroll_previewer(prompt_bufnr, -1) end @@ -240,23 +247,14 @@ end actions._close = function(prompt_bufnr, keepinsert) action_state.get_current_history():reset() local picker = action_state.get_current_picker(prompt_bufnr) - local prompt_win = state.get_status(prompt_bufnr).prompt_win local original_win_id = picker.original_win_id - if picker.previewer then - for _, v in ipairs(picker.all_previewers) do - v:teardown() - end - end - actions.close_pum(prompt_bufnr) if not keepinsert then vim.cmd [[stopinsert]] end - vim.api.nvim_win_close(prompt_win, true) - - pcall(vim.cmd, string.format([[silent bdelete! %s]], prompt_bufnr)) + require("telescope.pickers").on_close_prompt(prompt_bufnr) pcall(a.nvim_set_current_win, original_win_id) end diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index 8e5b682..127307b 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -431,6 +431,7 @@ append( timeout = 250, treesitter = true, msg_bg_fillchar = "╱", + hide_on_startup = false, }, [[ This field handles the global configuration for previewers. @@ -511,6 +512,9 @@ append( Default: true - msg_bg_fillchar: Character to fill background of unpreviewable buffers with Default: "╱" + - hide_on_startup: Hide previewer when picker starts. Previewer can be toggled + with actions.toggle_preview. + Default: false ]] ) diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index b844020..76e6136 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -135,6 +135,14 @@ function Picker:new(opts) obj.previewer = false end + local __hide_previewer = opts.__hide_previewer + if __hide_previewer then + obj.hidden_previewer = obj.previewer + obj.previewer = nil + else + obj.hidden_previewer = nil + end + -- TODO: It's annoying that this is create and everything else is "new" obj.scroller = p_scroller.create(obj.scroll_strategy, obj.sorting_strategy) @@ -297,6 +305,7 @@ function Picker:find() -- 1. Prompt window -- 2. Options window -- 3. Preview window + local line_count = vim.o.lines - vim.o.cmdheight if vim.o.laststatus ~= 0 then line_count = line_count - 1 @@ -315,12 +324,11 @@ function Picker:find() popup_opts.prompt.titlehighlight = "TelescopePromptTitle" if popup_opts.preview then popup_opts.preview.minheight = popup_opts.preview.height - popup_opts.preview.highlight = "TelescopeNormal" + popup_opts.preview.highlight = "TelescopePreviewNormal" popup_opts.preview.borderhighlight = "TelescopePreviewBorder" popup_opts.preview.titlehighlight = "TelescopePreviewTitle" end - -- local results_win, results_opts = popup.create("", popup_opts.results) local results_win, results_opts, results_border_win = self:_create_window("", popup_opts.results, true) local results_bufnr = a.nvim_win_get_buf(results_win) @@ -457,12 +465,14 @@ function Picker:find() -- TODO: Use WinLeave as well? local on_buf_leave = string.format( - [[ autocmd BufLeave ++nested ++once :silent lua require('telescope.pickers').on_close_prompt(%s)]], + [[ autocmd BufLeave ++nested ++once :silent lua require('telescope.pickers').on_close_prompt(%s)]], + prompt_bufnr, prompt_bufnr ) local on_vim_resize = string.format( - [[ autocmd VimResized ++nested :lua require('telescope.pickers').on_resize_window(%s)]], + [[ autocmd VimResized ++nested :lua require('telescope.pickers').on_resize_window(%s)]], + prompt_bufnr, prompt_bufnr ) @@ -527,11 +537,16 @@ function Picker:recalculate_layout() if preview_win ~= nil then popup.move(preview_win, popup_opts.preview) else - popup_opts.preview.highlight = "TelescopeNormal" + popup_opts.preview.highlight = "TelescopePreviewNormal" popup_opts.preview.borderhighlight = "TelescopePreviewBorder" popup_opts.preview.titlehighlight = "TelescopePreviewTitle" - preview_win, preview_opts, preview_border_win = self:_create_window("", popup_opts.preview) + local preview_bufnr = vim.api.nvim_buf_is_valid(status.preview_bufnr) and status.preview_bufnr or "" + preview_win, preview_opts, preview_border_win = self:_create_window(preview_bufnr, popup_opts.preview) + if preview_bufnr == "" then + preview_bufnr = a.nvim_win_get_buf(preview_win) + end status.preview_win = preview_win + status.preview_bufnr = preview_bufnr status.preview_border_win = preview_border_win state.set_status(prompt_win, status) self.preview_win = preview_win @@ -539,10 +554,8 @@ function Picker:recalculate_layout() self.preview_border = preview_opts and preview_opts.border end elseif preview_win ~= nil then - vim.api.nvim_win_close(preview_win, false) - if status.preview_border_win then - vim.api.nvim_win_close(status.preview_border_win, false) - end + utils.win_delete("preview_win", preview_win, true) + utils.win_delete("preview_win", status.preview_border_win, true) status.preview_win = nil status.preview_border_win = nil state.set_status(prompt_win, status) @@ -559,9 +572,39 @@ function Picker:recalculate_layout() -- self.max_results = popup_opts.results.height end -function Picker:hide_preview() - -- 1. Hide the window (and border) - -- 2. Resize prompt & results windows accordingly +local update_scroll = function(win, oldinfo, oldcursor, strategy, max_results) + if strategy == "ascending" then + vim.api.nvim_win_set_cursor(win, { max_results, 0 }) + vim.api.nvim_win_set_cursor(win, { oldinfo.topline, 0 }) + vim.api.nvim_win_set_cursor(win, oldcursor) + elseif strategy == "descending" then + vim.api.nvim_win_set_cursor(win, { 1, 0 }) + vim.api.nvim_win_set_cursor(win, { oldinfo.botline, 0 }) + vim.api.nvim_win_set_cursor(win, oldcursor) + else + error(debug.traceback("Unknown sorting strategy: " .. (strategy or ""))) + end +end + +function Picker:toggle_preview() + local status = state.get_status(self.prompt_bufnr) + + if self.previewer and status.preview_win then + self.hidden_previewer = self.previewer + self.previewer = nil + elseif self.hidden_previewer and not status.preview_win then + self.previewer = self.hidden_previewer + self.hidden_previewer = nil + else + return + end + + local oldinfo = vim.fn.getwininfo(status.results_win)[1] + local oldcursor = vim.api.nvim_win_get_cursor(status.results_win) + + self:recalculate_layout() + self:refresh_previewer() + update_scroll(status.results_win, oldinfo, oldcursor, self.sorting_strategy, self.max_results) end function Picker:toggle_padding() @@ -638,6 +681,8 @@ function Picker:set_prompt(str) end function Picker.close_windows(status) + -- make sure we don't have BufLeave autocmd. + vim.cmd(string.format([[ autocmd! PickerInsert BufLeave ]], status.prompt_bufnr)) local prompt_win = status.prompt_win local results_win = status.results_win local preview_win = status.preview_win @@ -646,42 +691,22 @@ function Picker.close_windows(status) local results_border_win = status.results_border_win local preview_border_win = status.preview_border_win - local function del_win(name, win_id, force, bdelete) - if win_id == nil or not vim.api.nvim_win_is_valid(win_id) then - return - end + utils.win_delete("prompt_win", prompt_win, true, true) + utils.win_delete("results_win", results_win, true, true) + utils.win_delete("preview_win", preview_win, true, true) - local bufnr = vim.api.nvim_win_get_buf(win_id) - if bdelete and vim.api.nvim_buf_is_valid(bufnr) and not vim.api.nvim_buf_get_option(bufnr, "buflisted") then - vim.cmd(string.format("silent! bdelete! %s", bufnr)) - end + utils.win_delete("prompt_border_win", prompt_border_win, true, true) + utils.win_delete("results_border_win", results_border_win, true, true) + utils.win_delete("preview_border_win", preview_border_win, true, true) - if not vim.api.nvim_win_is_valid(win_id) then - return - end - - if not pcall(vim.api.nvim_win_close, win_id, force) then - log.trace("Unable to close window: ", name, "/", win_id) - end - end - - del_win("prompt_win", prompt_win, true) - del_win("results_win", results_win, true, true) - del_win("preview_win", preview_win, true, true) - - del_win("prompt_border_win", prompt_border_win, true, true) - del_win("results_border_win", results_border_win, true, true) - del_win("preview_border_win", preview_border_win, true, true) + -- Buffers should be deleted but it may be also the case that buffer was swapped in window + -- so make sure that buffers created in Picker are deleted. + utils.buf_delete(status.prompt_bufnr) + utils.buf_delete(status.results_bufnr) + utils.buf_delete(status.preview_bufnr) -- vim.cmd(string.format("bdelete! %s", status.prompt_bufnr)) - -- Major hack?? Why do I have to od this. - -- Probably because we're currently IN the buffer. - -- Should wait to do this until after we're done. - vim.defer_fn(function() - del_win("prompt_win", prompt_win, true) - end, 10) - state.clear_status(status.prompt_bufnr) end @@ -907,7 +932,11 @@ end function Picker:refresh_previewer() local status = state.get_status(self.prompt_bufnr) - if status.preview_win and self.previewer then + if not self._selection_entry then + -- if selection_entry is nil there is nothing to be previewed + return + end + if self.previewer and status.preview_win and a.nvim_win_is_valid(status.preview_win) then self:_increment "previewed" self.previewer:preview(self._selection_entry, status) @@ -938,8 +967,12 @@ function Picker:cycle_previewers(next) self.current_previewer_index = size end - self.previewer = self.all_previewers[self.current_previewer_index] - self:refresh_previewer() + if self.previewer then + self.previewer = self.all_previewers[self.current_previewer_index] + self:refresh_previewer() + elseif self.hidden_previewer then + self.hidden_previewer = self.all_previewers[self.current_previewer_index] + end end function Picker:entry_adder(index, entry, _, insert) @@ -1207,6 +1240,13 @@ pickers.new = function(opts, defaults) end end + if result["previewer"] == false then + result["previewer"] = defaults["previewer"] + result["__hide_previewer"] = true + elseif type(opts["preview"]) == "table" and opts["preview"]["hide_on_startup"] then + result["__hide_previewer"] = true + end + return Picker:new(result) end @@ -1253,8 +1293,10 @@ function pickers.on_close_prompt(prompt_bufnr) picker.sorter:_destroy() end - if picker.previewer then - picker.previewer:teardown() + if picker.all_previewers then + for _, v in ipairs(picker.all_previewers) do + v:teardown() + end end if picker.finder then @@ -1264,20 +1306,6 @@ function pickers.on_close_prompt(prompt_bufnr) picker.close_windows(status) end -local update_scroll = function(win, oldinfo, oldcursor, strategy, max_results) - if strategy == "ascending" then - vim.api.nvim_win_set_cursor(win, { max_results, 0 }) - vim.api.nvim_win_set_cursor(win, { oldinfo.topline, 0 }) - vim.api.nvim_win_set_cursor(win, oldcursor) - elseif strategy == "descending" then - vim.api.nvim_win_set_cursor(win, { 1, 0 }) - vim.api.nvim_win_set_cursor(win, { oldinfo.botline, 0 }) - vim.api.nvim_win_set_cursor(win, oldcursor) - else - error(debug.traceback("Unknown sorting strategy: " .. (strategy or ""))) - end -end - function pickers.on_resize_window(prompt_bufnr) local status = state.get_status(prompt_bufnr) local picker = status.picker diff --git a/lua/telescope/utils.lua b/lua/telescope/utils.lua index e8aefd4..c9a5bbe 100644 --- a/lua/telescope/utils.lua +++ b/lua/telescope/utils.lua @@ -438,6 +438,25 @@ function utils.buf_delete(bufnr) end end +function utils.win_delete(name, win_id, force, bdelete) + if win_id == nil or not vim.api.nvim_win_is_valid(win_id) then + return + end + + local bufnr = vim.api.nvim_win_get_buf(win_id) + if bdelete then + utils.buf_delete(bufnr) + end + + if not vim.api.nvim_win_is_valid(win_id) then + return + end + + if not pcall(vim.api.nvim_win_close, win_id, force) then + log.trace("Unable to close window: ", name, "/", win_id) + end +end + function utils.max_split(s, pattern, maxsplit) pattern = pattern or " " maxsplit = maxsplit or -1