diff options
-rw-r--r-- | LICENSE | 23 | ||||
-rw-r--r-- | README.md | 73 | ||||
-rw-r--r-- | doc/BufferBrowser.txt | 45 | ||||
-rw-r--r-- | lua/buffer-browser.lua | 94 | ||||
-rw-r--r-- | lua/buffer_browser.lua | 259 | ||||
-rw-r--r-- | plugin/buffer_browser.vim (renamed from plugin/buffer-browser.vim) | 9 |
6 files changed, 385 insertions, 118 deletions
@@ -0,0 +1,23 @@ +Copyright 2010 Ton van den Heuvel. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + @@ -1,39 +1,78 @@ -# NVIM Buffer Browser Navigation +# NVIM Buffer Browse `:b#` on steroids. Browse your buffers like you browse history in a browser. ## Motivation While `:bnext` and `:bprev` allows you to browse between buffers, it is not -very intuitive. Oftentimes I would jump to definitions, a different file -through netrw, an org file and then when using bprev I would end up in a -completely different file, because buffers are ordered by when they are opened. -`:b#`, <Ctrl-I> and <Ctrl-O> works only assuming you didn't navigate one step -further in the documentation. +very intuitive. Oftentimes I jump to definitions, a different file through +netrw, an org file. `:bprev` would then end up in a completely different file, +because buffers are ordered by when they are opened, not by history. `:b#`, +<Ctrl-I>, <Ctrl-6> and <Ctrl-O> works only assuming you didn't navigate one step further +in the documentation. I wanted to be able to browse buffers in a way that is similar to how you browse tabs in a browser. ## Usage -Install through any of your favorite plugin managers. For example, with -[lazy](https://www.lazyvim.org). - This plugin implements two functions: `require('buffer-browser').next()` and `require('buffer-browser').prev()`. You can easily map these to whatever you want. Here is an example `init.lua`: ```lua -vim.api.nvim_set_keymap('n', '<leader>b[', require("buffer-browser").next(), {desc = "Next [B]uffer [[]"}) -vim.api.nvim_set_keymap('n', '<leader>b]', require("buffer-browser").prev(), {desc = "Previous [B]uffer []]"}) +vim.api.nvim_set_keymap('n', '<leader>b[', require("buffer_browser").next(), {desc = "Next [B]uffer [[]"}) +vim.api.nvim_set_keymap('n', '<leader>b]', require("buffer_browser").prev(), {desc = "Previous [B]uffer []]"}) ``` -## Splits +### Splits + +If a split has been performed, the new split will not preserve any of the previous history. + +## Insallation + +Install through any of your favorite plugin managers. + +### [lazy](https://www.lazyvim.org). + +Make sure to run the setup function. For example with lazy: + +```lua +{ + 'https://git.sr.ht/~marcc/BufferBrowser', +} +``` + +Then run + +```lua +require('buffer_browser').setup() +``` + + +### Configuration + +You can configure the `filetype_filters` by passing a table to the `setup` +function. This is the default config: + +```lua +require('buffer-browser').setup({ + -- '' + 'netrw' is used to filter out netrw. + filetype_filters = ['', 'netrw', 'gitcommit', 'TelescopePrompt'] +}) +``` + +This can be used to filter out buffers you do not want in the history. + +#### Limitation + +To filter out netrw, we need to also filter out `''`, as the filetype +occassionally gets set to that by netrw (not sure why...). This means files +without filetype are filtered out. -This plugin works with splits. If you have multiple splits open, the plugin -will fork the split history and create a different "buffer history" for that -particular split. +## Credits -## Credits +The plugin is mostly a copy of +[ton/vim-bufsurf](https://github.com/ton/vim-bufsurf), but rewritten in Lua and +adds the ability to filter unwanted filetypes to get rid of netrw buffers. -The plugin is mostly based on [ton/vim-bufsurf](https://github.com/ton/vim-bufsurf), but rewritten in Lua. diff --git a/doc/BufferBrowser.txt b/doc/BufferBrowser.txt new file mode 100644 index 0000000..d6bc2f3 --- /dev/null +++ b/doc/BufferBrowser.txt @@ -0,0 +1,45 @@ +*BufferBrowser.txt* `:b#` on steroids. Browse your buffers like you browse history in a browser. *BufferBrowser* + +This plugin implements two functions: + require('buffer-browser').next() + require('buffer-browser').prev() + +You can easily map these to whatever you want. Here is an example `init.lua`: + vim.api.nvim_set_keymap('n', '<leader>b[', require("buffer_browser").next(), {desc = "Next [B]uffer [[]"}) + vim.api.nvim_set_keymap('n', '<leader>b]', require("buffer_browser").prev(), {desc = "Previous [B]uffer []]"}) + +INSTALLATION + +Install through any of your favorite plugin managers. + +### [lazy](https://www.lazyvim.org). +Make sure to run the setup function. For example with lazy: + + { + 'https://git.sr.ht/~marcc/BufferBrowser', + setup = function() + require('buffer_browser').setup() + end + } + + +CONFIGURATION + +You can configure the filetype_filters by passing a table to the setup function. For example with default config: + + require('buffer-browser').setup({ + -- '' + 'netrw' is used to filter out netrw. + filetype_filters = ['', 'netrw', 'gitcommit', 'TelescopePrompt'] + }) + +This can be used to filter out buffers you do not want in the history. If your +current buffer ends with, for example netrw, it will be filtered out and not +available in the history + +LIMITATIONS + +To filter out netrw, we need to also filter out '', as the filetype +occassionally gets set to that by netrw (not sure why...). This means files +without filetype are filtered out. + + diff --git a/lua/buffer-browser.lua b/lua/buffer-browser.lua deleted file mode 100644 index c6a41a8..0000000 --- a/lua/buffer-browser.lua +++ /dev/null @@ -1,94 +0,0 @@ --- BUFFER BROWSER --- Author: Marc Coquand --- See readme for explanation on what it does. --- This code contains examples that can be used to test the code. --- Run them in `lua %` --- or with a code runner, like sniprun. -local api = vim.api - -local function StateAppend(bufName, state, filters) - -- State has three fields, current = current buffer, previous = previous buffer, and future = future buffers - -- This function clears the future, and appends the current to the previous, and sets the current to the next - - -- If filters matches the bufName, skip the buffer - if filters ~= nil then - for _, filter in ipairs(filters) do - if string.match(bufName, filter) then - return state - end - end - end - - -- Clear the future - state.future = {} - -- Append the current to the previous - table.insert(state.previous, state.current) - -- Set the current to the next - state.current = bufName - - return state -end --- Test StateAppend --- local state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } --- print(StateAppend(6, state, {})['current']) --- -- > 6 --- state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } --- print(StateAppend(6, state, {})['previous'][1]) --- -- > 2 --- state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } --- print(StateAppend(6, state, { 6 })['current']) --- -- > 1 - -local function StateGoBack(state) - -- Go back in the state by setting current to the first element of previous, and appending the current to the future - -- Check if past is empty, if so return state as is - if #state.previous == 0 then - return state - end - - table.insert(state.future, state.current) - state.current = table.remove(state.previous, 1) - return state -end --- Test StateGoBack --- local state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } --- print(StateGoBack(StateGoBack(StateGoBack(state)))['current']) --- > 3 - -local function StateGoForward(state) - -- Go forward in the state by setting current to the first element of future, and appending the current to the previous - - -- Check if future is empty, if so return state as is - if #state.future == 0 then - return state - end - - table.insert(state.previous, state.current) - state.current = table.remove(state.future, 1) - - return state -end --- Test StateGoForward --- local state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } --- print(StateGoForward(StateGoForward(StateGoForward(state)))['current']) --- > 5 - -function BufferEnter() - local bufName = api.nvim_buf_get_name(0) - local state = vim.g.buffer_browser_state - local filters = vim.g.buffer_browser_filters - vim.g.buffer_browser_state = StateAppend(bufName, state, filters) -end - -local BufferBrowserGroup = api.nvim_create_augroup('BufferBrowser') - -api.nvim_create_autocmd('WinEnter', { - '*', - 'lua BufferEnter()', - { group = BufferBrowserGroup } -}) -api.nvim_create_autocmd('BufEnter', { - '*', - 'lua BufferEnter()', - { group = BufferBrowserGroup } -}) diff --git a/lua/buffer_browser.lua b/lua/buffer_browser.lua new file mode 100644 index 0000000..0683f4b --- /dev/null +++ b/lua/buffer_browser.lua @@ -0,0 +1,259 @@ +--[[ +BUFFER BROWSER +Author: Marc Coquand + +The comments below the code can be used to test the functions. Run them in `lua %` or with a code runner, like sniprun. +]] +local api = vim.api + +--[[ STATE MANAGEMENT +We make use of a Zipper to keep track of the state of the buffers +(https://stackoverflow.com/questions/380438/what-is-the-zipper-data-structure-and-should-i-be-using-it) +]] +local function matchesFilter(filters, name) + if filters == nil then + return false + end + + for _, filter in ipairs(filters) do + if name == filter then + return true + end + end + return false +end + + +local function StateAppend(bufNr, stateFiletype, state, filters) + if (state.current == bufNr) then + return state + end + + -- if filters matches the filetype of the buffer we change from, don't add it to history + if (stateFiletype ~= nil and matchesFilter(filters, stateFiletype)) then + state.current = bufNr + return state + else + state.future = {} + table.insert(state.previous, 1, state.current) + state.current = bufNr + + return state + end +end + +local function StateGoBack(state) + -- Go back in the state by setting current to the first element of previous, and appending the current to the future + -- Check if past is empty, if so return state as is + if #state.previous == 0 then + return state + end + + table.insert(state.future, 1, state.current) + state.current = table.remove(state.previous, 1) + return state +end +-- local state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } +-- print(StateGoBack(StateGoBack(StateGoBack(state)))['current']) +-- > 3 + +local function StateGoForward(state) + -- Go forward in the state by setting current to the first element of future, and appending the current to the previous + + -- Check if future is empty, if so return state as is + if #state.future == 0 then + return state + end + + table.insert(state.previous, 1, state.current) + state.current = table.remove(state.future, 1) + + return state +end +-- local state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } +-- print(StateGoForward(StateGoForward(StateGoForward(state)))['current']) + +local function StateDelete(state, bufNr) + -- Delete bufNr from state.previous + for i, v in ipairs(state.previous) do + if v == bufNr then + table.remove(state.previous, i) + end + end + + -- Delete bufNr from state.future + for i, v in ipairs(state.future) do + if v == bufNr then + table.remove(state.future, i) + end + end + + -- If bufNr is current, set current to the first element of previous + if state.current == bufNr and #state.previous > 0 then + state.current = table.remove(state.previous, 1) + return state + end + + -- If bufNr is current and we have a future + if state.current == bufNr and #state.future > 0 then + state.current = table.remove(state.future, 1) + return state + end + + -- If bufNr is current, set current to the first element of future + if state.current == bufNr then + state.current = nil + return state + end + return state +end +-- local state = { current = 1, previous = { 2, 3 }, future = { 4, 5 } } +-- print(StateDelete(state, 1)['current']) +-- -- > 2 + +local function stateIsEmpty(state) + return (state.current == nil and state.previous == nil and state.future == nil) +end + + +-- [[ EFFECTS ]] +local function setState(state) + vim.w.buffer_browser_previous = state.previous + vim.w.buffer_browser_current = state.current + vim.w.buffer_browser_future = state.future +end + +local function getState() + local current = vim.w.buffer_browser_current + local previous = vim.w.buffer_browser_previous + local future = vim.w.buffer_browser_future + return { + current = current, + previous = previous, + future = future, + } +end + +local function initState(bufNr) + vim.w.buffer_browser_current = bufNr + vim.w.buffer_browser_previous = {} + vim.w.buffer_browser_future = {} +end + +local function getBufNr() + local winnr = api.nvim_tabpage_get_win(0) + return api.nvim_win_get_buf(winnr) +end + +local function getFiletype(bufnr) + if (bufnr == nil) then + return '' + end + return vim.bo[bufnr].filetype +end + +--[[ AUTO COMMANDS ]] +function BufferBrowserBufferEnter(args) + local bufNr = args.buf + local filters = vim.g.buffer_browser_filters + + local state = getState() + if stateIsEmpty(state) then + initState(bufNr) + else + if (state.current ~= bufNr) then + local stateFiletype = getFiletype(state.current) + StateAppend(bufNr, stateFiletype, state, filters) + setState(state) + end + end +end + +function BufferBrowserBufferWipeOut(args) + local buf = args.buf + local state = getState() + if not stateIsEmpty(state) then + StateDelete(state, buf) + setState(state) + end +end + +local BufferBrowserGroup = api.nvim_create_augroup('BufferBrowser', { clear = true }) + +api.nvim_create_autocmd({ 'WinEnter', 'BufEnter' }, + { + group = BufferBrowserGroup, + pattern = '*', + callback = function(args) + BufferBrowserBufferEnter(args) + end + } +) + +-- BufWipeout is called before deletion +api.nvim_create_autocmd('BufWipeout', + { + group = BufferBrowserGroup, + pattern = '*', + callback = function(args) + BufferBrowserBufferWipeOut(args) + end + } +) + + +--[[ COMMANDS ]] +local function next() + local bufnr = getBufNr() + local state = getState() + + if stateIsEmpty(state) then + initState(bufnr) + else + StateGoForward(state) + setState(state) + end + api.nvim_command('buffer ' .. state.current) +end + +local function prev() + local bufnr = getBufNr() + local state = getState() + + if stateIsEmpty(state) then + initState(bufnr) + else + StateGoBack(state) + setState(state) + end + api.nvim_command('buffer ' .. state.current) +end + +--[[ DEBUG ]] +local function printBrowserHistory() + local state = getState() + print('Browser State Previous: ' .. vim.inspect(state.previous)) + print('Browser State Current: ' .. vim.inspect(state.current)) + print('Browser State Future: ' .. vim.inspect(state.future)) +end + + +--[[ SETUP ]] +local function setup(opts) + if (opts == nil) then + local default = { + '', 'netrw', 'gitcommit', 'TelescopePrompt' + } + vim.g.buffer_browser_filters = default + else + -- Filter is based on filetype + vim.g.buffer_browser_filters = opts.filetype_filters + end +end + +return { + next = next, + prev = prev, + printBrowserHistory = printBrowserHistory, + setup = setup +} diff --git a/plugin/buffer-browser.vim b/plugin/buffer_browser.vim index dfdb366..a37f436 100644 --- a/plugin/buffer-browser.vim +++ b/plugin/buffer_browser.vim @@ -1,13 +1,8 @@ if exists('g:loaded_buffer_browser') | finish | endif " prevent loading file twice -let s:save_cpo = &cpo " save user coptions -set cpo&vim " reset them to defaults - " command to run our plugin command! BufferBrowserNext lua require'buffer_browser'.next() command! BufferBrowserPrevious lua require'buffer_browser'.prev() +command! BufferBrowserEcho lua require'buffer_browser'.printBrowserHistory() -let &cpo = s:save_cpo " and restore after -unlet s:save_cpo - -let g:loaded_whid = 1 +let g:loaded_buffer_browser = 1 |