├── LICENSE ├── README.md ├── lua └── nvim-smartbufs │ ├── buff.lua │ ├── init.lua │ └── term.lua └── plugin └── nvim-smartbufs.vim /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 nvim-smartbufs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-smartbufs 2 | 3 | WIP neovim plugin that will allow easier buffer management. 4 | 5 | Master neovim buffers by quickly switch into any active file or terminal buffer. 6 | 7 | ## Features 8 | 9 | * Switch into any buffer you opened (by index). 10 | * Launch a terminal buffer that won't get deleted so you can reopen it and resume work. 11 | * Why? When using `:terminal`, if the buffer is not displayed, it will get deleted. 12 | * Go to next and previous buffer in the buffer list. 13 | * Without considering terminal buffers. 14 | * Close (delete) any buffer or the current one. 15 | * It will try to preserve your current layout, so it won't close your splits. 16 | 17 | ## Demo 18 | 19 | * Switch between first and third opened buffers 20 | ![Switch between 1st and 3rd buffer](https://raw.githubusercontent.com/wiki/johann2357/nvim-smartbufs/screenshots/switch-to-n.gif) 21 | * [Check the complete demo](https://github.com/johann2357/nvim-smartbufs/wiki/Demo) 22 | 23 | ## Usage 24 | 25 | Add some mappings according to your needs. 26 | 27 | The following mappings are recommended. 28 | 29 | ```viml 30 | " Jump to the N buffer (by index) according to :ls buffer list 31 | " where N is NOT the buffer number but the INDEX in such list 32 | " NOTE: it does not include terminal buffers 33 | nnoremap 1 :lua require("nvim-smartbufs").goto_buffer(1) 34 | nnoremap 2 :lua require("nvim-smartbufs").goto_buffer(2) 35 | nnoremap 3 :lua require("nvim-smartbufs").goto_buffer(3) 36 | nnoremap 4 :lua require("nvim-smartbufs").goto_buffer(4) 37 | nnoremap 5 :lua require("nvim-smartbufs").goto_buffer(5) 38 | nnoremap 6 :lua require("nvim-smartbufs").goto_buffer(6) 39 | nnoremap 7 :lua require("nvim-smartbufs").goto_buffer(7) 40 | nnoremap 8 :lua require("nvim-smartbufs").goto_buffer(8) 41 | nnoremap 9 :lua require("nvim-smartbufs").goto_buffer(9) 42 | 43 | " Improved :bnext :bprev behavior (without considering terminal buffers) 44 | nnoremap :lua require("nvim-smartbufs").goto_next_buffer() 45 | nnoremap :lua require("nvim-smartbufs").goto_prev_buffer() 46 | 47 | " Open terminal buffer and set it as hidden so it won't get deleted 48 | nnoremap c1 :lua require("nvim-smartbufs").goto_terminal(1) 49 | nnoremap c2 :lua require("nvim-smartbufs").goto_terminal(2) 50 | nnoremap c3 :lua require("nvim-smartbufs").goto_terminal(3) 51 | nnoremap c4 :lua require("nvim-smartbufs").goto_terminal(4) 52 | 53 | " Delete current buffer and goes back to the previous one 54 | nnoremap qq :lua require("nvim-smartbufs").close_current_buffer() 55 | 56 | " Delete the N buffer according to :ls buffer list 57 | nnoremap q1 :lua require("nvim-smartbufs").close_buffer(1) 58 | nnoremap q2 :lua require("nvim-smartbufs").close_buffer(2) 59 | nnoremap q3 :lua require("nvim-smartbufs").close_buffer(3) 60 | nnoremap q4 :lua require("nvim-smartbufs").close_buffer(4) 61 | nnoremap q5 :lua require("nvim-smartbufs").close_buffer(5) 62 | nnoremap q6 :lua require("nvim-smartbufs").close_buffer(6) 63 | nnoremap q7 :lua require("nvim-smartbufs").close_buffer(7) 64 | nnoremap q8 :lua require("nvim-smartbufs").close_buffer(8) 65 | nnoremap q9 :lua require("nvim-smartbufs").close_buffer(9) 66 | ``` 67 | 68 | ## Getting Started 69 | 70 | This was tested with [Neovim Nightly (0.5)](https://github.com/neovim/neovim/releases/tag/nightly) 71 | but it should probably work with the stable version as well. 72 | 73 | This is meant for neovim users that use buffers and not tabs 74 | * You might want to have all buffers listed somewhere (optional) 75 | * I use `nvim-hardline` to list the open buffers at the top. 76 | * https://github.com/ojroques/nvim-hardline 77 | * Also it is possible with `vim-airline`. 78 | 79 | ### Installation 80 | 81 | Using [vim-plug](https://github.com/junegunn/vim-plug) 82 | 83 | ```viml 84 | Plug 'johann2357/nvim-smartbufs' 85 | ``` 86 | 87 | ### Future ideas 88 | 89 | * Possible new mappings: 90 | 91 | ```viml 92 | " Delete buffer it is a file buffer or close terminal buffer 93 | nnoremap qq :lua require("nvim-smartbufs").close_this() 94 | 95 | " Close all buffers but current one 96 | nnoremap qa :lua require("nvim-smartbufs").close_all() 97 | ``` 98 | 99 | * I might add a simple interface to list and switch to any buffer. 100 | * Currently, only remaps are the only way to go. 101 | -------------------------------------------------------------------------------- /lua/nvim-smartbufs/buff.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | 4 | --- Checks whether the buffer is valid. 5 | -- Checks if buffer is valid and listed. 6 | -- @param buf_id buffer id to be checked. 7 | -- @treturn bool 8 | local function is_valid_buffer(buf_id) 9 | return vim.api.nvim_buf_is_valid(buf_id) and vim.fn.getbufvar(buf_id, "&buflisted") == 1 10 | end 11 | 12 | 13 | --- Checks whether the buffer is a regular file buffer. 14 | -- It also checks if buffer is valid and listed. 15 | -- @param buf_id buffer id to be checked. 16 | -- @treturn bool 17 | local function is_file_buffer(buf_id) 18 | return is_valid_buffer(buf_id) and vim.fn.getbufvar(buf_id, "&buftype") ~= "terminal" 19 | end 20 | 21 | 22 | --- Checks whether the buffer is regular file buffer. 23 | -- It also checks if buffer is valid and listed. 24 | -- @return table with active buffers 25 | local function get_active_buffers() 26 | local bufs = vim.api.nvim_list_bufs() 27 | local active_buffers = {} 28 | local count = 0 29 | for idx, buf_id in pairs(bufs) do 30 | if is_file_buffer(buf_id) then 31 | count = count + 1 32 | active_buffers[count] = buf_id 33 | end 34 | end 35 | -- TODO: remove print used to debug 36 | -- print(vim.inspect(active_buffers)) 37 | return active_buffers 38 | end 39 | 40 | 41 | --- Opens the given buffer id. 42 | -- It uses the current window. 43 | -- @param buf_id buffer id to be opened. 44 | -- @treturn nil 45 | local function open_buf_id(buf_id) 46 | -- TODO: find a better way to do this 47 | vim.api.nvim_command(string.format("buffer %d", buf_id)) 48 | end 49 | 50 | 51 | --- Find buffer in buffer table. 52 | -- Finds a given buffer in the active buffers table (no terminal buffers included). 53 | -- @param buf_id buffer id to be found. 54 | -- @param buffer_table table with buffer ids. 55 | -- @return buf_id index found or nil if not found. 56 | local function find_buffer(buf_id, buffer_table) 57 | for idx, table_buf_id in ipairs(buffer_table) do 58 | if buf_id == table_buf_id then 59 | return idx 60 | end 61 | end 62 | end 63 | 64 | 65 | --- Opens the given buffer index in buffers table. 66 | -- It uses the current window. 67 | -- @param buf_idx buffer index to be opened. 68 | -- @treturn nil 69 | M.goto_buffer = function(buf_idx) 70 | local active_bufs = get_active_buffers() 71 | local selected_buf = active_bufs[buf_idx] 72 | if selected_buf then 73 | open_buf_id(selected_buf) 74 | end 75 | end 76 | 77 | 78 | --- Opens the next buffer in relation to the current one. 79 | -- It only considers regular file buffers. Similar to :bnext 80 | -- @treturn nil 81 | M.goto_next_buffer = function() 82 | local active_bufs = get_active_buffers() 83 | local current_buf_id = vim.api.nvim_get_current_buf() 84 | local total_bufs = table.maxn(active_bufs) 85 | local buf_idx = find_buffer(current_buf_id, active_bufs) 86 | if buf_idx == nil then 87 | -- TODO: keep track of last used buffer 88 | buf_idx = 0 89 | end 90 | local next_buf_idx = (buf_idx + 1) % (total_bufs + 1) 91 | if next_buf_idx == 0 then 92 | next_buf_idx = 1 93 | end 94 | M.goto_buffer(next_buf_idx) 95 | end 96 | 97 | 98 | --- Opens the previous buffer in relation to the current one. 99 | -- It only considers regular file buffers. Similar to :bprev 100 | -- @treturn nil 101 | M.goto_prev_buffer = function() 102 | local active_bufs = get_active_buffers() 103 | local current_buf_id = vim.api.nvim_get_current_buf() 104 | local total_bufs = table.maxn(active_bufs) 105 | local buf_idx = find_buffer(current_buf_id, active_bufs) 106 | if buf_idx == nil then 107 | -- TODO: keep track of last used buffer 108 | buf_idx = 0 109 | end 110 | local prev_buf_idx = (buf_idx - 1) % (total_bufs + 1) 111 | if prev_buf_idx == 0 then 112 | prev_buf_idx = total_bufs 113 | end 114 | M.goto_buffer(prev_buf_idx) 115 | end 116 | 117 | 118 | --- Closes current active buffer. 119 | -- Also calls goto_prev_buffer so it doesn't close the current window. 120 | -- @treturn nil 121 | M.close_current_buffer = function() 122 | local buf_id = vim.api.nvim_get_current_buf() 123 | M.goto_prev_buffer() 124 | vim.api.nvim_command(string.format("bdelete %d", buf_id)) 125 | end 126 | 127 | 128 | --- Closes the given buffer index in buffers table. 129 | -- If buf_idx is the current buffer it will call close_current_buffer. 130 | -- @param buf_idx buffer index to be opened. 131 | -- @treturn nil 132 | M.close_buffer = function(buf_idx) 133 | local active_bufs = get_active_buffers() 134 | local selected_buf = active_bufs[buf_idx] 135 | if selected_buf == nil then 136 | return 137 | end 138 | 139 | local current_buf_id = vim.api.nvim_get_current_buf() 140 | if selected_buf == current_buf_id then 141 | M.close_current_buffer() 142 | else 143 | vim.api.nvim_command(string.format("bdelete %d", selected_buf)) 144 | end 145 | end 146 | 147 | 148 | return M 149 | -------------------------------------------------------------------------------- /lua/nvim-smartbufs/init.lua: -------------------------------------------------------------------------------- 1 | local term = require("nvim-smartbufs.term") 2 | local buff = require("nvim-smartbufs.buff") 3 | 4 | local M = {} 5 | 6 | 7 | -- Stable 8 | M.goto_terminal = term.goto_terminal 9 | M.send_command = term.send_command 10 | M.goto_buffer = buff.goto_buffer 11 | M.goto_next_buffer = buff.goto_next_buffer 12 | M.goto_prev_buffer = buff.goto_prev_buffer 13 | M.close_current_buffer = buff.close_current_buffer 14 | M.close_buffer = buff.close_buffer 15 | 16 | -- Deprecated 17 | M.open_n_buffer = buff.goto_buffer 18 | M.open_next_buffer = buff.goto_next_buffer 19 | M.open_prev_buffer = buff.goto_prev_buffer 20 | 21 | 22 | return M 23 | -------------------------------------------------------------------------------- /lua/nvim-smartbufs/term.lua: -------------------------------------------------------------------------------- 1 | -- Adapted from ThePrimeagen 2 | -- https://github.com/ThePrimeagen/harpoon/blob/abb717f174f9fdafe0867193f81558c1fd7d9b7b/lua/harpoon/term.lua 3 | 4 | local M = {} 5 | local terminals = {} 6 | 7 | local function create_terminal() 8 | 9 | vim.api.nvim_command("terminal") 10 | local buf_id = vim.fn.bufnr() 11 | local term_id = vim.b.terminal_job_id 12 | 13 | if term_id == nil then 14 | -- TODO: Throw an erro? 15 | return nil 16 | end 17 | 18 | -- Make sure the term buffer has "hidden" set so it doesn't get thrown 19 | -- away and cause an error 20 | vim.api.nvim_buf_set_option(0, "bufhidden", "hide") 21 | 22 | return buf_id, term_id 23 | end 24 | 25 | 26 | M.goto_terminal = function(idx) 27 | local term_handle = terminals[idx] 28 | if not term_handle or not vim.api.nvim_buf_is_valid(term_handle.buf_id) then 29 | local buf_id, term_id = create_terminal() 30 | if buf_id == nil then 31 | return 32 | end 33 | 34 | term_handle = { 35 | buf_id = buf_id, 36 | term_id = term_id 37 | } 38 | terminals[idx] = term_handle 39 | else 40 | vim.api.nvim_set_current_buf(term_handle.buf_id) 41 | end 42 | end 43 | 44 | 45 | M.send_command = function(idx, cmd) 46 | local term_handle = terminals[idx] 47 | if term_handle == nil then 48 | return 49 | end 50 | if cmd then 51 | vim.fn.chansend(term_handle.term_id, cmd) 52 | end 53 | end 54 | 55 | 56 | return M 57 | -------------------------------------------------------------------------------- /plugin/nvim-smartbufs.vim: -------------------------------------------------------------------------------- 1 | " fun! NvimSmartBufs() 2 | " lua for k in pairs(package.loaded) do if k:match("^nvim%-smartbufs") then package.loaded[k] = nil end end 3 | " endfun 4 | 5 | " augroup NvimSmartBufs 6 | " autocmd! 7 | " nnoremap 1 :lua require("nvim-smartbufs").goto_buffer(1) 8 | " nnoremap 2 :lua require("nvim-smartbufs").goto_buffer(2) 9 | " nnoremap 3 :lua require("nvim-smartbufs").goto_buffer(3) 10 | " nnoremap 4 :lua require("nvim-smartbufs").goto_buffer(4) 11 | " nnoremap 5 :lua require("nvim-smartbufs").goto_buffer(5) 12 | " nnoremap 6 :lua require("nvim-smartbufs").goto_buffer(6) 13 | " nnoremap 7 :lua require("nvim-smartbufs").goto_buffer(7) 14 | " nnoremap 8 :lua require("nvim-smartbufs").goto_buffer(8) 15 | " nnoremap 9 :lua require("nvim-smartbufs").goto_buffer(9) 16 | " nnoremap c1 :lua require("nvim-smartbufs").goto_terminal(1) 17 | " nnoremap c2 :lua require("nvim-smartbufs").goto_terminal(2) 18 | " nnoremap c3 :lua require("nvim-smartbufs").goto_terminal(3) 19 | " nnoremap c4 :lua require("nvim-smartbufs").goto_terminal(4) 20 | " nnoremap :lua require("nvim-smartbufs").goto_next_buffer() 21 | " nnoremap :lua require("nvim-smartbufs").goto_prev_buffer() 22 | " augroup END 23 | --------------------------------------------------------------------------------