aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Coquand <marc@baemingo.com>2023-05-19 14:25:31 -0500
committerMarc Coquand <marc@baemingo.com>2023-05-19 14:25:31 -0500
commite406288adc8a70197730569c4da3e8c61d24e47d (patch)
tree4a3763cf19af65d9d90a2e54cbf0ece2aa37ce2e
parent63ff0bedaa55a6b2957e958ee727bd524b280143 (diff)
downloadBufferBrowser-e406288adc8a70197730569c4da3e8c61d24e47d.tar.gz
BufferBrowser-e406288adc8a70197730569c4da3e8c61d24e47d.tar.bz2
BufferBrowser-e406288adc8a70197730569c4da3e8c61d24e47d.zip
Initial version
-rw-r--r--LICENSE23
-rw-r--r--README.md73
-rw-r--r--doc/BufferBrowser.txt45
-rw-r--r--lua/buffer-browser.lua94
-rw-r--r--lua/buffer_browser.lua259
-rw-r--r--plugin/buffer_browser.vim (renamed from plugin/buffer-browser.vim)9
6 files changed, 385 insertions, 118 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7807837
--- /dev/null
+++ b/LICENSE
@@ -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.
+
diff --git a/README.md b/README.md
index 70dcc7f..5ce7b10 100644
--- a/README.md
+++ b/README.md
@@ -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