From 575dc10ddcf940d321a5cbfcf5915ed2f134ab56 Mon Sep 17 00:00:00 2001 From: Galen Rowell Date: Wed, 18 Aug 2021 20:13:35 +1000 Subject: [PATCH] Greatly improve terminal features! Persistent terminals (#275) * remove toggleTerm plugin * Adding term binds, term hider & Telescope terms to bring them back * Adding many term features! --- lua/chadrc.lua | 17 ++-- lua/mappings.lua | 32 +++---- lua/pluginList.lua | 10 -- lua/plugins/bufferline.lua | 20 +++- lua/plugins/telescope.lua | 3 + lua/plugins/toggleterm.lua | 47 --------- lua/telescope/_extensions/terms.lua | 144 ++++++++++++++++++++++++++++ lua/utils.lua | 113 ++++++++++++++++++++++ 8 files changed, 303 insertions(+), 83 deletions(-) delete mode 100644 lua/plugins/toggleterm.lua create mode 100644 lua/telescope/_extensions/terms.lua diff --git a/lua/chadrc.lua b/lua/chadrc.lua index 0e32766..1f8d7d0 100644 --- a/lua/chadrc.lua +++ b/lua/chadrc.lua @@ -13,7 +13,6 @@ M.ui = { hidden_statusline = { -- these are filetypes, not pattern matched "NvimTree", - "toggleterm", }, } @@ -107,6 +106,7 @@ M.mappings = { bufferline = { new_buffer = "", newtab = "b", + close = "", -- close a buffer with custom func in utils.lua cycleNext = "", -- next buffer cyclePrev = "", -- previous buffer }, @@ -116,11 +116,13 @@ M.mappings = { diffget_3 = "gl", git_blame = "gb", }, - toggleterm = { - toggle_window = "w", - toggle_vert = "v", - toggle_hori = "h", - hide_term = "JK", + terms = { -- below are NvChad mappings, not plugin mappings + esc_termmode = "jk", + esc_hide_termmode = "JK", + pick_term = "W", -- note: this is a telescope extension + new_wind = "w", + new_vert = "v", + new_hori = "h", }, -- navigation in insert mode insert_nav = { @@ -131,10 +133,7 @@ M.mappings = { prev_line = "", next_line = "", }, - -- non plugin misc = { - esc_Termmode = "jk", -- get out of terminal mode - close_buffer = "", -- close current focused buffer copywhole_file = "", toggle_linenr = "n", -- show or hide line number theme_toggle = "x", diff --git a/lua/mappings.lua b/lua/mappings.lua index ecd02d8..17e8d9e 100644 --- a/lua/mappings.lua +++ b/lua/mappings.lua @@ -41,28 +41,27 @@ map("n", miscMap.copywhole_file, ":%y+", opt) -- toggle numbers map("n", miscMap.toggle_linenr, ":set nu!", opt) --- open a new buffer as a Terminal --- get out of terminal with jk -map("t", miscMap.esc_Termmode, "", opt) --- close current focused buffer, terminal or normal --- todo: don't close if non-terminal buffer is saved -map("n", miscMap.close_buffer, ":bd!", opt) +-- terminals +local function terms() + local m = user_map.terms -M.toggleterm = function() - local m = user_map.toggleterm + -- get out of terminal mode + map("t", m.esc_termmode, "", opt) + -- hide a term from within terminal mode + map("t", m.esc_hide_termmode, " :lua require('utils').close_buffer() ", opt) + -- pick a hidden term + map("n", m.pick_term, ":Telescope terms ", opt) -- Open terminals - map("n", m.toggle_window, ":lua termW:toggle() ", opt) - map("n", m.toggle_vert, ":lua termV:toggle() ", opt) - map("n", m.toggle_hori, ":lua termH:toggle() ", opt) - - -- toggle(HIDE) a term from within terminal edit mode - map("t", m.hide_term, " :ToggleTerm ", opt) - map("t", m.hide_term, " :ToggleTerm ", opt) - map("t", m.hide_term, " :ToggleTerm ", opt) + -- TODO this opens on top of an existing vert/hori term, fixme + map("n", m.new_wind, ":execute 'terminal' | let b:term_type = 'wind' | startinsert ", opt) + map("n", m.new_vert, ":execute 'vnew +terminal' | let b:term_type = 'vert' | startinsert ", opt) + map("n", m.new_hori, ":execute 15 .. 'new +terminal' | let b:term_type = 'hori' | startinsert ", opt) end +terms() + M.truezen = function() local m = user_map.truezen @@ -135,6 +134,7 @@ M.bufferline = function() map("n", m.new_buffer, ":enew", opt) -- new buffer map("n", m.newtab, ":tabnew", opt) -- new tab + map("n", m.close, ":lua require('utils').close_buffer() ", opt) -- close buffer -- move between tabs diff --git a/lua/pluginList.lua b/lua/pluginList.lua index c5ff17a..79ad14b 100644 --- a/lua/pluginList.lua +++ b/lua/pluginList.lua @@ -212,16 +212,6 @@ return packer.startup(function() } -- misc plugins - use { - "akinsho/nvim-toggleterm.lua", - event = "BufWinEnter", - config = function() - require "plugins.toggleterm" - end, - setup = function() - require("mappings").toggleterm() - end, - } use { "windwp/nvim-autopairs", after = "nvim-compe", diff --git a/lua/plugins/bufferline.lua b/lua/plugins/bufferline.lua index 06236d5..d55b70c 100644 --- a/lua/plugins/bufferline.lua +++ b/lua/plugins/bufferline.lua @@ -5,7 +5,7 @@ local present, bufferline = pcall(require, "bufferline") if not present then return end - + bufferline.setup { options = { offsets = { { filetype = "NvimTree", text = "", padding = 1 } }, @@ -24,6 +24,24 @@ bufferline.setup { separator_style = "thin", mappings = true, always_show_bufferline = true, + custom_filter = function(buf_number) + -- Func to filter out our managed/persistent split terms + local present_type, type = pcall(function() + return vim.api.nvim_buf_get_var(buf_number, "term_type") + end) + + if present_type then + if type == "vert" then + return false + elseif type == "hori" then + return false + else + return true + end + else + return true + end + end, }, highlights = { fill = { diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua index 46f91ae..40b7383 100644 --- a/lua/plugins/telescope.lua +++ b/lua/plugins/telescope.lua @@ -65,8 +65,11 @@ telescope.setup { }, } +-- NvChad pickers -- load the theme_switcher extension require("telescope").load_extension "themes" +-- load the term_picker extension +require("telescope").load_extension "terms" if not pcall(function() telescope.load_extension "fzf" diff --git a/lua/plugins/toggleterm.lua b/lua/plugins/toggleterm.lua deleted file mode 100644 index 7baaa64..0000000 --- a/lua/plugins/toggleterm.lua +++ /dev/null @@ -1,47 +0,0 @@ -local present, toggleterm = pcall(require, "toggleterm") -if not present then - return -end - -toggleterm.setup { - -- size can be a number or function which is passed the current terminal - size = function(term) - if term.direction == "horizontal" then - return 15 - elseif term.direction == "vertical" then - return vim.o.columns * 0.4 - end - end, - -- open_mapping = [[]], -- mapping set in mappings.lua - hide_numbers = true, -- hide the number column in toggleterm buffers - shade_terminals = false, - start_in_insert = true, - -- insert_mappings = true, -- see 'open_mapping', not set on purpose - -- whether or not the open mapping applies in insert mode - persist_size = true, - direction = "vertical", - close_on_exit = true, -- close the terminal window when the process exits - -- This field is only relevant if direction is set to 'float' - float_opts = { - border = "single", - winblend = 0, - highlights = { - border = "Normal", - background = "Normal", - }, - }, -} - -local Terminal = require("toggleterm.terminal").Terminal - -_G.termW = Terminal:new { - direction = "window", -} - -_G.termV = Terminal:new { - direction = "vertical", -} - -_G.termH = Terminal:new { - direction = "horizontal", -} diff --git a/lua/telescope/_extensions/terms.lua b/lua/telescope/_extensions/terms.lua new file mode 100644 index 0000000..7913666 --- /dev/null +++ b/lua/telescope/_extensions/terms.lua @@ -0,0 +1,144 @@ +-- This file can be loaded as a telescope extension +local M = {} + +-- Custom theme picker +-- Most of the code is copied from telescope buffer builtin +-- Src: https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/telescope/builtin/internal.lua +M.term_picker = function(opts) + local pickers, finders, previewers, make_entry, actions, action_state, utils, conf + if pcall(require, "telescope") then + pickers = require "telescope.pickers" + finders = require "telescope.finders" + previewers = require "telescope.previewers" + + make_entry = require "telescope.make_entry" + actions = require "telescope.actions" + action_state = require "telescope.actions.state" + utils = require "telescope.utils" + conf = require("telescope.config").values + else + error "Cannot find telescope!" + end + + local filter = vim.tbl_filter + + local local_utils = require "utils" + + -- buffer number and name + local bufnr = vim.api.nvim_get_current_buf() + local bufname = vim.api.nvim_buf_get_name(bufnr) + + local bufnrs = filter(function(b) + local present_type, type = pcall(function() + return vim.api.nvim_buf_get_var(b, "term_type") + end) + + if not present_type then + -- let's only terms that we created + return false + end + + + -- if 1 ~= vim.fn.buflisted(b) then + -- return false + -- end + -- only hide unloaded buffers if opts.show_all_buffers is false, keep them listed if true or nil + if opts.show_all_buffers == false and not vim.api.nvim_buf_is_loaded(b) then + return false + end + if opts.ignore_current_buffer and b == vim.api.nvim_get_current_buf() then + return false + end + return true + end, vim.api.nvim_list_bufs()) + if not next(bufnrs) then + return + end + if opts.sort_mru then + table.sort(bufnrs, function(a, b) + return vim.fn.getbufinfo(a)[1].lastused > vim.fn.getbufinfo(b)[1].lastused + end) + end + + local buffers = {} + local default_selection_idx = 1 + for _, bufnr in ipairs(bufnrs) do + local flag = bufnr == vim.fn.bufnr "" and "%" or (bufnr == vim.fn.bufnr "#" and "#" or " ") + + if opts.sort_lastused and not opts.ignore_current_buffer and flag == "#" then + default_selection_idx = 2 + end + + local element = { + bufnr = bufnr, + flag = flag, + info = vim.fn.getbufinfo(bufnr)[1], + } + + if opts.sort_lastused and (flag == "#" or flag == "%") then + local idx = ((buffers[1] ~= nil and buffers[1].flag == "%") and 2 or 1) + table.insert(buffers, idx, element) + else + table.insert(buffers, element) + end + end + + if not opts.bufnr_width then + local max_bufnr = math.max(unpack(bufnrs)) + opts.bufnr_width = #tostring(max_bufnr) + end + + pickers.new(opts, { + prompt_title = "Terminal buffers", + finder = finders.new_table { + results = buffers, + entry_maker = opts.entry_maker or make_entry.gen_from_buffer(opts), + }, + previewer = conf.grep_previewer(opts), + sorter = conf.generic_sorter(opts), + default_selection_index = default_selection_idx, + attach_mappings = function(prompt_bufnr) + actions.select_default:replace(function() + local entry = action_state.get_selected_entry() + actions.close(prompt_bufnr) + + local buf = entry.bufnr + + local chad_term, type = pcall(function() + return vim.api.nvim_buf_get_var(buf, "term_type") + end) + + -- TODO buffer checks/error detection (make sure we do get a buf) + + if chad_term then + if type == "wind" then + -- swtich to term buff & show in bufferline + vim.cmd(string.format('b %d | setlocal bl', buf)) + -- vim.cmd('startinsert') TODO fix this + elseif type == "vert" then + vim.cmd(string.format('vsp #%d', buf)) + -- vim.cmd('startinsert') TODO fix this + elseif type == "hori" then + -- TODO change 15 to a chad config var number + vim.cmd(string.format('15 sp #%d ', buf)) + -- vim.cmd('startinsert') TODO fix this + end + end + end) + + return true + end, + }):find() +end + +-- register term picker as terms to telescope +local present, telescope = pcall(require, "telescope") +if present then + return telescope.register_extension { + exports = { + terms = M.term_picker, + }, + } +else + error "Cannot find telescope!" +end diff --git a/lua/utils.lua b/lua/utils.lua index 2709988..367dd3e 100644 --- a/lua/utils.lua +++ b/lua/utils.lua @@ -34,6 +34,119 @@ M.clear_cmdline = function() end, 0) end +M.close_buffer = function(bufexpr, force) + -- This is a modification of a NeoVim plugin from + -- Author: ojroques - Olivier Roques + -- Src: https://github.com/ojroques/nvim-bufdel + -- (Author has okayed copy-paste) + + -- Options + local opts = { + next = 'cycle', -- how to retrieve the next buffer + quit = false, -- exit when last buffer is deleted + --TODO make this a chadrc flag/option + } + + -- ---------------- + -- Helper functions + -- ---------------- + + -- Switch to buffer 'buf' on each window from list 'windows' + local function switch_buffer(windows, buf) + local cur_win = vim.fn.winnr() + for _, winid in ipairs(windows) do + vim.cmd(string.format('%d wincmd w', vim.fn.win_id2win(winid))) + vim.cmd(string.format('buffer %d', buf)) + end + vim.cmd(string.format('%d wincmd w', cur_win)) -- return to original window + end + + -- Select the first buffer with a number greater than given buffer + local function get_next_buf(buf) + local next = vim.fn.bufnr('#') + if opts.next == 'alternate' and vim.fn.buflisted(next) == 1 then + return next + end + for i = 0, vim.fn.bufnr('$') - 1 do + next = (buf + i) % vim.fn.bufnr('$') + 1 -- will loop back to 1 + if vim.fn.buflisted(next) == 1 then + return next + end + end + end + + -- ---------------- + -- End helper functions + -- ---------------- + + local buf = vim.fn.bufnr() + if vim.fn.buflisted(buf) == 0 then -- exit if buffer number is invalid + return + end + + if #vim.fn.getbufinfo({buflisted = 1}) < 2 then + if opts.quit then + -- exit when there is only one buffer left + if force then + vim.cmd('qall!') + else + vim.cmd('confirm qall') + end + return + end + + local chad_term, type = pcall(function() + return vim.api.nvim_buf_get_var(buf, "term_type") + end) + + if chad_term then + -- Must be a window type + vim.cmd(string.format('setlocal nobl', buf)) + vim.cmd('enew') + return + end + -- don't exit and create a new empty buffer + vim.cmd('enew') + vim.cmd('bp') + end + + local next_buf = get_next_buf(buf) + local windows = vim.fn.getbufinfo(buf)[1].windows + + -- force deletion of terminal buffers to avoid the prompt + if force or vim.fn.getbufvar(buf, '&buftype') == 'terminal' then + local chad_term, type = pcall(function() + return vim.api.nvim_buf_get_var(buf, "term_type") + end) + + -- TODO this scope is error prone, make resilient + if chad_term then + if type == "wind" then + -- hide from bufferline + vim.cmd(string.format('%d bufdo setlocal nobl', buf)) + -- swtich to another buff + -- TODO switch to next bufffer, this works too + vim.cmd('BufferLineCycleNext') + else + local cur_win = vim.fn.winnr() + -- we can close this window + vim.cmd(string.format('%d wincmd c', cur_win)) + return + end + else + switch_buffer(windows, next_buf) + vim.cmd(string.format('bd! %d', buf)) + end + else + switch_buffer(windows, next_buf) + vim.cmd(string.format('silent! confirm bd %d', buf)) + end + -- revert buffer switches if user has canceled deletion + if vim.fn.buflisted(buf) == 1 then + switch_buffer(windows, buf) + end +end + -- 1st arg - r or w -- 2nd arg - file path -- 3rd arg - content if 1st arg is w