├── .gitignore
├── README.md
├── assets
├── buffers.jpg
├── statusline.jpg
└── tabs.jpg
├── buffalo-nvim.png
├── lua
└── buffalo
│ ├── api.lua
│ ├── dev.lua
│ ├── init.lua
│ ├── ui.lua
│ └── utils.lua
└── plugin
├── buffalo.lua
└── buffalo.vim
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pheon-Dev/buffalo-nvim/86cc767848fe747c28b226af0d35049fd5faf288/.gitignore
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |
5 |
6 | buffalo-nvim
7 |
8 |
9 |
10 |
11 |
12 |
13 | This is a [harpoon](https://github.com/ThePrimeagen/harpoon) like plugin that provides an interface
14 | to navigate through buffers or tabs.
15 |
16 | > Their respective totals can be displayed on the statusline, tabline or winbar.
17 |
18 |
19 |
20 | NOTE:
21 |
22 | Please note that this plugin is still in its early development stages. Breaking changes are to be expected!
23 |
24 |
25 |
26 | BUFFERS
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | TABS
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | STATUSLINE
43 |
44 |
45 |
46 |
47 |
48 |
49 | tabs: 4 | buffers: 7 [lualine]
50 |
51 |
52 | ## Installation
53 |
54 | Using [Lazy](https://github.com/folke/lazy.nvim)
55 |
56 | ```lua
57 | {
58 | 'Pheon-Dev/buffalo-nvim'
59 | }
60 | ```
61 |
62 | Using [packer.nvim](https://github.com/wbthomason/packer.nvim)
63 |
64 | ```lua
65 | use 'Pheon-Dev/buffalo-nvim'
66 | ```
67 |
68 | Using [vim-plug](https://github.com/junegunn/vim-plug)
69 |
70 | ```vim
71 | Plug 'Pheon-Dev/buffalo-nvim'
72 | ```
73 |
74 | ## Setup
75 |
76 | ```lua
77 | -- default config
78 | require('buffalo').setup({})
79 | ```
80 |
81 | ## Usage
82 |
83 | ```lua
84 | -- Keymaps
85 | local opts = { noremap = true }
86 | local map = vim.keymap.set
87 | local buffalo = require("buffalo.ui")
88 |
89 | -- buffers
90 | map({ 't', 'n' }, '', buffalo.toggle_buf_menu, opts)
91 |
92 | map('n', '', buffalo.nav_buf_next, opts)
93 | map('n', '', buffalo.nav_buf_prev, opts)
94 |
95 | -- tabpages
96 | map({ 't', 'n' }, '', buffalo.toggle_tab_menu, opts)
97 |
98 | map('n', '', buffalo.nav_tab_next, opts)
99 | map('n', '', buffalo.nav_tab_prev, opts)
100 |
101 | -- Example in lualine
102 | ...
103 | sections = {
104 | ...
105 | lualine_x = {
106 | {
107 | function()
108 | local buffers = require("buffalo").buffers()
109 | local tabpages = require("buffalo").tabpages()
110 | return " " .. buffers .. " " .. tabpages
111 | end,
112 | color = { fg = "#ffaa00", bg = "#24273a",},
113 | }
114 | },
115 | ...
116 | },
117 | ...
118 | ```
119 |
120 | ---
121 |
122 | ## Config
123 |
124 | ```lua
125 | require("buffalo").setup({
126 | tab_commands = { -- use default neovim commands for tabs e.g `tablast`, `tabnew` etc
127 | next = { -- you can use any unique name e.g `tabnext`, `tab_next`, `next`, `random` etc
128 | key = "",
129 | command = "tabnext"
130 | },
131 | close = {
132 | key = "c",
133 | command = "tabclose"
134 | },
135 | dd = {
136 | key = "dd",
137 | command = "tabclose"
138 | },
139 | new = {
140 | key = "n",
141 | command = "tabnew"
142 | }
143 | },
144 | buffer_commands = { -- use default neovim commands for buffers e.g `bd`, `edit`
145 | edit = {
146 | key = "",
147 | command = "edit"
148 | },
149 | vsplit = {
150 | key = "v",
151 | command = "vsplit"
152 | },
153 | split = {
154 | key = "h",
155 | command = "split"
156 | }
157 | buffer_delete = {
158 | key = "d",
159 | command = "bd"
160 | }
161 | },
162 | general_commands = {
163 | cycle = true, -- cycle through buffers or tabs
164 | exit_menu = "x", -- similar to 'q' and ''
165 | },
166 | go_to = {
167 | enabled = true,
168 | go_to_tab = "%s",
169 | go_to_buffer = "",
170 | },
171 | filter = {
172 | enabled = true,
173 | filter_tabs = "",
174 | filter_buffers = "",
175 | },
176 | ui = {
177 | width = 60,
178 | height = 10,
179 | row = 2,
180 | col = 2,
181 | borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
182 | }
183 | })
184 | ```
185 |
186 | ---
187 |
188 | ## Tips
189 |
190 | - Hit any number on the menu to navigate to that buffer or tab without having to scroll.
191 | - Use normal keymap defaults for neovim e.g `dd` to delete a buffer, on the open menu.
192 |
193 | ---
194 |
195 | ## Highlights
196 |
197 | - `BuffaloBorder`
198 | - `BuffaloWindow`
199 | - `BuffaloBuffersModified`
200 | - `BuffaloBuffersCurrentLine`
201 | - `BuffaloTabsCurrentLine`
202 |
203 | ---
204 |
205 | ## Acknowledgement
206 |
207 | - ThePrimeagen's [Harpoon](https://github.com/ThePrimeagen/harpoon)
208 | - J-Morano's [Buffer Manager](https://github.com/j-morano/buffer_manager.nvim)
209 |
210 | ---
211 |
212 | ## Contributions
213 |
214 | - PRs and Issues are always welcome.
215 |
--------------------------------------------------------------------------------
/assets/buffers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pheon-Dev/buffalo-nvim/86cc767848fe747c28b226af0d35049fd5faf288/assets/buffers.jpg
--------------------------------------------------------------------------------
/assets/statusline.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pheon-Dev/buffalo-nvim/86cc767848fe747c28b226af0d35049fd5faf288/assets/statusline.jpg
--------------------------------------------------------------------------------
/assets/tabs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pheon-Dev/buffalo-nvim/86cc767848fe747c28b226af0d35049fd5faf288/assets/tabs.jpg
--------------------------------------------------------------------------------
/buffalo-nvim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pheon-Dev/buffalo-nvim/86cc767848fe747c28b226af0d35049fd5faf288/buffalo-nvim.png
--------------------------------------------------------------------------------
/lua/buffalo/api.lua:
--------------------------------------------------------------------------------
1 | local api = {}
2 |
3 | function api.get_tabs()
4 | return vim.api.nvim_list_tabpages()
5 | end
6 |
7 | function api.get_tab_wins(tabid)
8 | local wins = vim.api.nvim_tabpage_list_wins(tabid)
9 | return vim.tbl_filter(api.is_not_float_win, wins)
10 | end
11 |
12 | function api.get_current_tab()
13 | return vim.api.nvim_get_current_tabpage()
14 | end
15 |
16 | function api.get_tab_current_win(tabid)
17 | return vim.api.nvim_tabpage_get_win(tabid)
18 | end
19 |
20 | function api.get_tab_number(tabid)
21 | local valid = vim.api.nvim_tabpage_is_valid(tabid)
22 | if not valid then return -1 end
23 | return vim.api.nvim_tabpage_get_number(tabid)
24 | end
25 |
26 | function api.get_wins()
27 | local wins = vim.api.nvim_list_wins()
28 | return vim.tbl_filter(api.is_not_float_win, wins)
29 | end
30 |
31 | function api.get_win_tab(winid)
32 | return vim.api.nvim_win_get_tabpage(winid)
33 | end
34 |
35 | function api.is_float_win(winid)
36 | return vim.api.nvim_win_get_config(winid).relative ~= ''
37 | end
38 |
39 | function api.is_not_float_win(winid)
40 | return vim.api.nvim_win_get_config(winid).relative == ''
41 | end
42 |
43 | function api.get_win_buf(winid)
44 | return vim.api.nvim_win_get_buf(winid)
45 | end
46 |
47 | function api.get_buf_type(bufid)
48 | return vim.api.nvim_buf_get_option(bufid, 'buftype')
49 | end
50 |
51 | function api.get_buf_is_changed(bufid)
52 | return vim.fn.getbufinfo(bufid)[1].changed == 1
53 | end
54 |
55 | return api
56 |
--------------------------------------------------------------------------------
/lua/buffalo/dev.lua:
--------------------------------------------------------------------------------
1 | -- Don't include this file, we should manually include it via
2 | -- require("buffalo.dev").reload();
3 | --
4 | -- A quick mapping can be setup using something like:
5 | -- :nmap rr :lua require("buffalo.dev").reload()
6 | local M = {}
7 |
8 | function M.reload()
9 | require("plenary.reload").reload_module("buffalo")
10 | end
11 |
12 | local log_levels = { "trace", "debug", "info", "warn", "error", "fatal" }
13 | local function set_log_level()
14 | local log_level = vim.env.BUFFALO_LOG or vim.g.buffalo_log_level
15 |
16 | for _, level in pairs(log_levels) do
17 | if level == log_level then
18 | return log_level
19 | end
20 | end
21 |
22 | return "warn" -- default, if user hasn't set to one from log_levels
23 | end
24 |
25 | local log_level = set_log_level()
26 | M.log = require("plenary.log").new({
27 | plugin = "buffalo",
28 | level = log_level,
29 | })
30 |
31 | local log_key = os.time()
32 |
33 | local function override(key)
34 | local fn = M.log[key]
35 | M.log[key] = function(...)
36 | fn(log_key, ...)
37 | end
38 | end
39 |
40 | for _, v in pairs(log_levels) do
41 | override(v)
42 | end
43 |
44 | function M.get_log_key()
45 | return log_key
46 | end
47 |
48 | return M
49 |
--------------------------------------------------------------------------------
/lua/buffalo/init.lua:
--------------------------------------------------------------------------------
1 | local Dev = require("buffalo.dev")
2 | local api = require("buffalo.api")
3 | local log = Dev.log
4 | local buffer_is_valid = require("buffalo.utils").buffer_is_valid
5 | local merge_tables = require("buffalo.utils").merge_tables
6 | --
7 | local M = {}
8 |
9 | M.marks = {}
10 | M.tab_marks = {}
11 |
12 | M.Config = M.Config or {}
13 |
14 | function M.buffers()
15 | local bufs = vim.api.nvim_list_bufs()
16 | bufs = vim.tbl_filter(function(buf)
17 | local is_loaded = vim.api.nvim_buf_is_loaded(buf)
18 | local is_listed = vim.fn.buflisted(buf) == 1
19 |
20 | if not (is_loaded and is_listed) then
21 | return false
22 | end
23 |
24 | return true
25 | end, bufs)
26 | local count = 0
27 | for _, buf in pairs(bufs) do
28 | count = count + 1
29 | end
30 | return count
31 | end
32 |
33 | function M.tabpages()
34 | local tabs = api.get_tabs()
35 | local count = 0
36 | for _, tab in pairs(tabs) do
37 | count = count + 1
38 | end
39 | return count
40 | end
41 |
42 | function M.init_buffers()
43 | local buffers = vim.api.nvim_list_bufs()
44 |
45 | for idx = 1, #buffers do
46 | local buf_id = buffers[idx]
47 | local buf_name = vim.api.nvim_buf_get_name(buf_id)
48 | local filename = buf_name
49 | -- if buffer is listed, then add to contents and marks
50 | if buffer_is_valid(buf_id, buf_name) then
51 | table.insert(
52 | M.marks,
53 | {
54 | filename = filename,
55 | buf_id = buf_id,
56 | }
57 | )
58 | end
59 | end
60 | end
61 |
62 | function M.setup(config)
63 | log.trace("setup(): Setting up...")
64 |
65 | if not config then
66 | config = {}
67 | end
68 |
69 | local default_config = {
70 | tab_commands = {
71 | edit = {
72 | key = "",
73 | command = "tabnext"
74 | }
75 | },
76 | buffer_commands = {
77 | edit = {
78 | key = "",
79 | command = "edit"
80 | }
81 | },
82 | general_commands = {
83 | cycle = true,
84 | exit_menu = "q",
85 | },
86 | go_to = {
87 | enabled = true,
88 | go_to_tab = "%s",
89 | go_to_buffer = "",
90 | },
91 | filter = {
92 | enabled = true,
93 | filter_tabs = "",
94 | filter_buffers = "",
95 | },
96 | ui = {
97 | width = 60,
98 | height = 10,
99 | row = 2,
100 | col = 2,
101 | borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
102 | }
103 | }
104 |
105 | local complete_config = merge_tables(default_config, config)
106 |
107 | M.Config = complete_config
108 | log.debug("setup(): Config", M.Config)
109 | end
110 |
111 | function M.get_config()
112 | log.trace("get_config()")
113 | return M.Config or {}
114 | end
115 |
116 | M.setup()
117 |
118 | M.init_buffers()
119 |
120 | return M
121 |
--------------------------------------------------------------------------------
/lua/buffalo/ui.lua:
--------------------------------------------------------------------------------
1 | local Path = require("plenary.path")
2 | local buffalo = require("buffalo")
3 | local popup = require("plenary.popup")
4 | local utils = require("buffalo.utils")
5 | local log = require("buffalo.dev").log
6 | local marks = require("buffalo").marks
7 | local api = require("buffalo.api")
8 |
9 | local M = {}
10 |
11 | Buffalo_win_id = nil
12 | Buffalo_bufh = nil
13 | Buffalo_Tabs_win_id = nil
14 | Buffalo_Tabs_bufh = nil
15 |
16 | local initial_marks = {}
17 | local config = buffalo.get_config()
18 |
19 | local function close_menu(force_save)
20 | force_save = force_save or false
21 |
22 | vim.api.nvim_win_close(Buffalo_win_id, true)
23 |
24 | Buffalo_win_id = nil
25 | Buffalo_bufh = nil
26 | end
27 |
28 | local function close_tabs_menu(force_save)
29 | force_save = force_save or false
30 |
31 | vim.api.nvim_win_close(Buffalo_Tabs_win_id, true)
32 |
33 | Buffalo_Tabs_win_id = nil
34 | Buffalo_Tabs_bufh = nil
35 | end
36 | local opts = { noremap = true }
37 | local map = vim.keymap.set
38 |
39 | local function create_window(title)
40 | log.trace("_create_window()")
41 |
42 | local width = config.ui.width or 60
43 | local height = config.ui.height or 10
44 | local row = config.ui.row or 2
45 | local col = config.ui.col or 2
46 |
47 | local borderchars = config.ui.borderchars or { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }
48 | local bufnr = vim.api.nvim_create_buf(false, false)
49 |
50 | local Buffalo_win_id, win = popup.create(bufnr, {
51 | title = "Buffalo [" .. title .. "]",
52 | highlight = "BuffaloWindow",
53 | titlehighlight = "BuffaloTitle",
54 | line = math.floor(((vim.o.lines - height) / row) - 1),
55 | col = math.floor((vim.o.columns - width) / col),
56 | minwidth = width,
57 | minheight = height,
58 | borderchars = borderchars,
59 | })
60 |
61 | vim.api.nvim_win_set_option(
62 | win.border.win_id,
63 | "winhl",
64 | "Normal:BuffaloBorder"
65 | )
66 |
67 | return {
68 | bufnr = bufnr,
69 | win_id = Buffalo_win_id,
70 | }
71 | end
72 |
73 |
74 | local function string_starts(string, start)
75 | return string.sub(string, 1, string.len(start)) == start
76 | end
77 |
78 | local function can_be_deleted(bufname, bufnr)
79 | return (
80 | vim.api.nvim_buf_is_valid(bufnr)
81 | and (not string_starts(bufname, "term://"))
82 | and (not vim.bo[bufnr].modified)
83 | and bufnr ~= -1
84 | )
85 | end
86 |
87 | local function is_buffer_in_marks(bufnr)
88 | for _, mark in pairs(marks) do
89 | if mark.buf_id == bufnr then
90 | return true
91 | end
92 | end
93 | return false
94 | end
95 |
96 | local function get_mark_by_name(name, specific_marks)
97 | local ref_name = nil
98 | for _, mark in pairs(specific_marks) do
99 | ref_name = mark.filename
100 | if string_starts(mark.filename, "term://") then
101 | ref_name = utils.get_short_term_name(mark.filename)
102 | else
103 | ref_name = utils.normalize_path(mark.filename)
104 | end
105 | if name == ref_name then
106 | return mark
107 | end
108 | end
109 | return nil
110 | end
111 |
112 | local function update_buffers()
113 | for _, mark in pairs(initial_marks) do
114 | if not is_buffer_in_marks(mark.buf_id) then
115 | if can_be_deleted(mark.filename, mark.buf_id) then
116 | vim.api.nvim_buf_clear_namespace(mark.buf_id, -1, 1, -1)
117 | vim.api.nvim_buf_delete(mark.buf_id, {})
118 | end
119 | end
120 | end
121 |
122 | for idx, mark in pairs(marks) do
123 | local bufnr = vim.fn.bufnr(mark.filename)
124 | if bufnr == -1 then
125 | vim.cmd("badd " .. mark.filename)
126 | marks[idx].buf_id = vim.fn.bufnr(mark.filename)
127 | end
128 | end
129 | end
130 |
131 | local function remove_mark(idx)
132 | marks[idx] = nil
133 | if idx < #marks then
134 | for i = idx, #marks do
135 | marks[i] = marks[i + 1]
136 | end
137 | end
138 | end
139 |
140 | local function update_marks()
141 | for idx, mark in pairs(marks) do
142 | if not utils.buffer_is_valid(mark.buf_id, mark.filename) then
143 | remove_mark(idx)
144 | end
145 | end
146 | for _, buf in pairs(vim.api.nvim_list_bufs()) do
147 | local bufname = vim.api.nvim_buf_get_name(buf)
148 | if utils.buffer_is_valid(buf, bufname) and not is_buffer_in_marks(buf) then
149 | table.insert(marks, {
150 | filename = bufname,
151 | buf_id = buf,
152 | })
153 | end
154 | end
155 | end
156 |
157 | function M.toggle_buf_menu()
158 | log.trace("toggle_buf_menu()")
159 | if Buffalo_win_id ~= nil and vim.api.nvim_win_is_valid(Buffalo_win_id) then
160 | if vim.api.nvim_buf_get_changedtick(vim.fn.bufnr()) > 0 then
161 | M.on_menu_save()
162 | end
163 | close_menu(true)
164 | update_buffers()
165 | return
166 | end
167 | local current_buf_id = -1
168 | current_buf_id = vim.fn.bufnr()
169 |
170 | local win_info = create_window("buffers")
171 | local contents = {}
172 | initial_marks = {}
173 |
174 | Buffalo_win_id = win_info.win_id
175 | Buffalo_bufh = win_info.bufnr
176 |
177 | update_marks()
178 |
179 | local current_buf_line = 1
180 | local line = 1
181 | local modified_lines = {}
182 | for idx, mark in pairs(marks) do
183 | if vim.fn.buflisted(mark.buf_id) ~= 1 then
184 | marks[idx] = nil
185 | else
186 | local current_mark = marks[idx]
187 | initial_marks[idx] = {
188 | filename = current_mark.filename,
189 | buf_id = current_mark.buf_id,
190 | }
191 | if vim.bo[current_mark.buf_id].modified then
192 | table.insert(modified_lines, line)
193 | end
194 | if current_mark.buf_id == current_buf_id then
195 | current_buf_line = line
196 | end
197 | local display_filename = current_mark.filename
198 | display_filename = utils.normalize_path(display_filename)
199 | contents[line] = string.format("%s", display_filename)
200 | line = line + 1
201 | end
202 | end
203 |
204 | vim.api.nvim_set_option_value("number", true, { win = Buffalo_win_id })
205 | vim.api.nvim_buf_set_name(Buffalo_bufh, "buffalo-buffers")
206 | vim.api.nvim_buf_set_lines(Buffalo_bufh, 0, #contents, false, contents)
207 | vim.api.nvim_buf_set_option(Buffalo_bufh, "filetype", "buffalo")
208 | vim.api.nvim_buf_set_option(Buffalo_bufh, "buftype", "acwrite")
209 | vim.api.nvim_buf_set_option(Buffalo_bufh, "bufhidden", "delete")
210 | vim.cmd(string.format(":call cursor(%d, %d)", current_buf_line, 1))
211 | vim.api.nvim_buf_set_keymap(
212 | Buffalo_bufh,
213 | "n",
214 | "q",
215 | "lua require('buffalo.ui').toggle_buf_menu()",
216 | { silent = true }
217 | )
218 | vim.api.nvim_buf_set_keymap(
219 | Buffalo_bufh,
220 | "n",
221 | config.general_commands.exit_menu,
222 | "lua require('buffalo.ui').toggle_buf_menu()",
223 | { silent = true }
224 | )
225 | vim.api.nvim_buf_set_keymap(
226 | Buffalo_bufh,
227 | "n",
228 | "",
229 | "lua require('buffalo.ui').toggle_buf_menu()",
230 | { silent = true }
231 | )
232 | for _, value in pairs(config.buffer_commands) do
233 | if type(value.command) == "string" then
234 | vim.api.nvim_buf_set_keymap(
235 | Buffalo_bufh,
236 | "n",
237 | value.key,
238 | "lua require('buffalo.ui').select_menu_item('" .. value.command .. "')",
239 | {}
240 | )
241 | end
242 | if type(value.command) == "function" then
243 | vim.keymap.set(
244 | "n",
245 | value.key,
246 | value.command,
247 | {buffer = Buffalo_bufh }
248 | )
249 | end
250 | end
251 | vim.cmd(
252 | string.format(
253 | "autocmd BufModifiedSet set nomodified",
254 | Buffalo_bufh
255 | )
256 | )
257 | vim.cmd(
258 | "autocmd BufLeave ++nested ++once silent" ..
259 | " lua require('buffalo.ui').toggle_buf_menu()"
260 | )
261 | vim.cmd(
262 | string.format(
263 | "autocmd BufWriteCmd " ..
264 | " lua require('buffalo.ui').on_menu_save()",
265 | Buffalo_bufh
266 | )
267 | )
268 | local str = "1234567890"
269 |
270 | for i = 1, #str do
271 | local c = str:sub(i, i)
272 | vim.api.nvim_buf_set_keymap(
273 | Buffalo_bufh,
274 | "n",
275 | c,
276 | string.format(
277 | "%s lua require('buffalo.ui')" ..
278 | ".select_menu_item()",
279 | i
280 | ),
281 | {}
282 | )
283 | end
284 |
285 |
286 | for _, modified_line in pairs(modified_lines) do
287 | vim.api.nvim_buf_add_highlight(
288 | Buffalo_bufh,
289 | -1,
290 | "BuffaloBuffersModified",
291 | modified_line - 1,
292 | 0,
293 | -1
294 | )
295 | end
296 | vim.api.nvim_buf_add_highlight(
297 | Buffalo_bufh,
298 | -1,
299 | "BuffaloBuffersCurrentLine",
300 | current_buf_line - 1,
301 | 0,
302 | -1
303 | )
304 | end
305 |
306 | function M.toggle_tab_menu()
307 | log.trace("toggle_tab_menu()")
308 | if Buffalo_Tabs_win_id ~= nil and vim.api.nvim_win_is_valid(Buffalo_Tabs_win_id) then
309 | close_tabs_menu(true)
310 | return
311 | end
312 | local tabid = api.get_current_tab()
313 | local current_tab_id = api.get_tab_number(tabid)
314 | local tabs = api.get_tabs()
315 |
316 | local win_info = create_window("tabpages")
317 | local contents = {}
318 |
319 | Buffalo_Tabs_win_id = win_info.win_id
320 | Buffalo_Tabs_bufh = win_info.bufnr
321 |
322 | local current_tab_line = 1
323 |
324 | for idx = 1, #tabs do
325 | local current_tab = api.get_tab_number(idx)
326 | if current_tab == current_tab_id then
327 | current_tab_line = idx
328 | end
329 |
330 | if current_tab == 0 then
331 | return
332 | end
333 | if current_tab > 0 then
334 | local twins = api.get_tab_wins(idx)
335 | local window = #twins > 1 and "[ " .. #twins .. " windows ]" or "[ " .. #twins .. " window ]"
336 | contents[idx] = string.format("Tab %s %s", current_tab, window)
337 | else
338 | contents[idx] = string.format("Tab [ deleted ]")
339 | end
340 | end
341 |
342 | vim.api.nvim_set_option_value("number", true, { win = Buffalo_Tabs_win_id })
343 |
344 | vim.api.nvim_buf_set_name(Buffalo_Tabs_bufh, "buffalo-tabs")
345 | vim.api.nvim_buf_set_lines(Buffalo_Tabs_bufh, 0, #contents, false, contents)
346 | vim.api.nvim_buf_set_option(Buffalo_Tabs_bufh, "filetype", "buffalo")
347 | vim.api.nvim_buf_set_option(Buffalo_Tabs_bufh, "buftype", "acwrite")
348 | vim.api.nvim_buf_set_option(Buffalo_Tabs_bufh, "bufhidden", "delete")
349 | vim.cmd(string.format(":call cursor(%d, %d)", current_tab_line, 1))
350 | vim.api.nvim_buf_set_keymap(
351 | Buffalo_Tabs_bufh,
352 | "n",
353 | config.general_commands.exit_menu,
354 | "lua require('buffalo.ui').toggle_tab_menu()",
355 | { silent = true }
356 | )
357 | vim.api.nvim_buf_set_keymap(
358 | Buffalo_Tabs_bufh,
359 | "n",
360 | "q",
361 | "lua require('buffalo.ui').toggle_tab_menu()",
362 | { silent = true }
363 | )
364 | vim.api.nvim_buf_set_keymap(
365 | Buffalo_Tabs_bufh,
366 | "n",
367 | "",
368 | "lua require('buffalo.ui').toggle_tab_menu()",
369 | { silent = true }
370 | )
371 | for _, value in pairs(config.tab_commands) do
372 | vim.api.nvim_buf_set_keymap(
373 | Buffalo_Tabs_bufh,
374 | "n",
375 | value.key,
376 | "lua require('buffalo.ui').select_tab_menu_item('" .. value.command .. "')",
377 | {}
378 | )
379 | end
380 | vim.cmd(
381 | string.format(
382 | "autocmd BufModifiedSet set nomodified",
383 | Buffalo_Tabs_bufh
384 | )
385 | )
386 | vim.cmd(
387 | "autocmd BufLeave ++nested ++once silent" ..
388 | " lua require('buffalo.ui').toggle_tab_menu()"
389 | )
390 | vim.cmd(
391 | string.format(
392 | "autocmd BufWriteCmd " ..
393 | " lua require('buffalo.ui').on_menu_save()",
394 | Buffalo_Tabs_bufh
395 | )
396 | )
397 | local str = "1234567890"
398 |
399 | for i = 1, #str do
400 | local c = str:sub(i, i)
401 | vim.api.nvim_buf_set_keymap(
402 | Buffalo_Tabs_bufh,
403 | "n",
404 | c,
405 | string.format(
406 | "%s lua require('buffalo.ui')" ..
407 | ".select_tab_menu_item()",
408 | i
409 | ),
410 | {}
411 | )
412 | end
413 | vim.api.nvim_buf_add_highlight(
414 | Buffalo_Tabs_bufh,
415 | -1,
416 | "BuffaloTabsCurrentLine",
417 | current_tab_line - 1,
418 | 0,
419 | -1
420 | )
421 | end
422 |
423 | function M.select_tab_menu_item(command)
424 | local idx = vim.fn.line(".")
425 | close_tabs_menu(true)
426 | M.nav_tab(idx, command)
427 | end
428 |
429 | function M.select_menu_item(command)
430 | local idx = vim.fn.line(".")
431 | if vim.api.nvim_buf_get_changedtick(vim.fn.bufnr()) > 0 then
432 | M.on_menu_save()
433 | end
434 | close_menu(true)
435 | M.nav_buf(idx, command)
436 | update_buffers()
437 | end
438 |
439 | local function get_menu_items()
440 | log.trace("_get_menu_items()")
441 | local lines = vim.api.nvim_buf_get_lines(Buffalo_bufh, 0, -1, true)
442 | local indices = {}
443 |
444 | for _, line in pairs(lines) do
445 | if not utils.is_white_space(line) then
446 | table.insert(indices, line)
447 | end
448 | end
449 |
450 | return indices
451 | end
452 |
453 | local function set_mark_list(new_list)
454 | log.trace("set_mark_list(): New list:", new_list)
455 |
456 | local original_marks = utils.deep_copy(marks)
457 | marks = {}
458 | for _, v in pairs(new_list) do
459 | if type(v) == "string" then
460 | local filename = v
461 | local buf_id = nil
462 | local current_mark = get_mark_by_name(filename, original_marks)
463 | if current_mark then
464 | filename = current_mark.filename
465 | buf_id = current_mark.buf_id
466 | else
467 | buf_id = vim.fn.bufnr(v)
468 | end
469 | table.insert(marks, {
470 | filename = filename,
471 | buf_id = buf_id,
472 | })
473 | end
474 | end
475 | end
476 |
477 | function M.on_menu_save()
478 | log.trace("on_menu_save()")
479 | set_mark_list(get_menu_items())
480 | end
481 |
482 | function M.nav_tab(id, command)
483 | log.trace("nav_buf(): Navigating to", id)
484 |
485 | if command == nil or command == "tabnext" then
486 | local tabid = api.get_tab_number(id)
487 | if tabid ~= -1 then
488 | vim.cmd(tabid .. "tabnext")
489 | end
490 | elseif command == "tabclose" then
491 | -- vim.api.nvim_tabpage_del_var(id, "buffalo")
492 | vim.cmd(id .. "tabclose")
493 | else
494 | vim.cmd(id .. command)
495 | end
496 | end
497 |
498 | function M.nav_buf(id, command)
499 | log.trace("nav_buf(): Navigating to", id)
500 | update_marks()
501 |
502 | local mark = marks[id]
503 | if not mark then
504 | return
505 | end
506 | if command == nil or command == "edit" then
507 | local bufnr = vim.fn.bufnr(mark.filename)
508 | if bufnr ~= -1 then
509 | vim.cmd("buffer " .. bufnr)
510 | else
511 | vim.cmd("edit " .. mark.filename)
512 | end
513 | else
514 | vim.cmd(command .. " " .. mark.filename)
515 | end
516 | end
517 |
518 | local function get_current_buf_line()
519 | local current_buf_id = vim.fn.bufnr()
520 | for idx, mark in pairs(marks) do
521 | if mark.buf_id == current_buf_id then
522 | return idx
523 | end
524 | end
525 | log.error("get_current_buf_line(): Could not find current buffer in marks")
526 | return -1
527 | end
528 |
529 | function M.nav_tab_next()
530 | log.trace("nav_tab_next()")
531 | vim.cmd("tabnext")
532 | end
533 |
534 | function M.nav_tab_prev()
535 | log.trace("nav_tab_prev()")
536 | vim.cmd("tabprev")
537 | end
538 |
539 | function M.nav_buf_next()
540 | log.trace("nav_buf_next()")
541 | update_marks()
542 | local current_buf_line = get_current_buf_line()
543 | if current_buf_line == -1 then
544 | return
545 | end
546 | local next_buf_line = current_buf_line + 1
547 | if next_buf_line > #marks then
548 | if config.general_commands.cycle then
549 | M.nav_buf(1)
550 | end
551 | else
552 | M.nav_buf(next_buf_line)
553 | end
554 | end
555 |
556 | function M.nav_buf_prev()
557 | log.trace("nav_buf_prev()")
558 | update_marks()
559 | local current_buf_line = get_current_buf_line()
560 | if current_buf_line == -1 then
561 | return
562 | end
563 | local prev_buf_line = current_buf_line - 1
564 | if prev_buf_line < 1 then
565 | if config.general_commands.cycle then
566 | M.nav_buf(#marks)
567 | end
568 | else
569 | M.nav_buf(prev_buf_line)
570 | end
571 | end
572 |
573 | function M.location_window(options)
574 | local default_options = {
575 | relative = "editor",
576 | style = "minimal",
577 | width = 30,
578 | height = 15,
579 | row = 2,
580 | col = 2,
581 | }
582 | options = vim.tbl_extend("keep", options, default_options)
583 |
584 | local bufnr = options.bufnr or vim.api.nvim_create_buf(false, true)
585 | local win_id = vim.api.nvim_open_win(bufnr, true, options)
586 |
587 | return {
588 | bufnr = bufnr,
589 | win_id = win_id,
590 | }
591 | end
592 |
593 | function M.save_menu_to_file(filename)
594 | log.trace("save_menu_to_file()")
595 | if filename == nil or filename == "" then
596 | filename = vim.fn.input("Enter filename: ")
597 | if filename == "" then
598 | return
599 | end
600 | end
601 | local file = io.open(filename, "w")
602 | if file == nil then
603 | log.error("save_menu_to_file(): Could not open file for writing")
604 | return
605 | end
606 | for _, mark in pairs(marks) do
607 | file:write(Path:new(mark.filename):absolute() .. "\n")
608 | end
609 | file:close()
610 | end
611 |
612 | function M.load_menu_from_file(filename)
613 | log.trace("load_menu_from_file()")
614 | if filename == nil or filename == "" then
615 | filename = vim.fn.input("Enter filename: ")
616 | if filename == "" then
617 | return
618 | end
619 | end
620 | local file = io.open(filename, "r")
621 | if file == nil then
622 | log.error("load_menu_from_file(): Could not open file for reading")
623 | return
624 | end
625 | local lines = {}
626 | for line in file:lines() do
627 | table.insert(lines, line)
628 | end
629 | file:close()
630 | set_mark_list(lines)
631 | update_buffers()
632 | end
633 |
634 | local go_to = config.go_to
635 | if go_to.enabled then
636 | local keys = "1234567890"
637 |
638 | for i = 1, #keys do
639 | local buffer = keys:sub(i, i)
640 | map(
641 | 'n',
642 | string.format(go_to.go_to_buffer, buffer),
643 | function() M.nav_buf(i) end,
644 | opts
645 | )
646 | end
647 |
648 | for i = 1, #keys do
649 | local tab = keys:sub(i, i)
650 | map(
651 | 'n',
652 | string.format(go_to.go_to_tab, tab),
653 | function() M.nav_tab(i) end,
654 | opts
655 | )
656 | end
657 | end
658 |
659 | local filter = config.filter
660 | if filter.enabled then
661 | map({ 't', 'n' }, filter.filter_tabs, function()
662 | M.toggle_tab_menu()
663 |
664 | vim.defer_fn(function()
665 | vim.fn.feedkeys('/')
666 | end, 50)
667 | end, opts)
668 |
669 | map({ 't', 'n' }, filter.filter_buffers, function()
670 | M.toggle_buf_menu()
671 |
672 | vim.defer_fn(function()
673 | vim.fn.feedkeys('/')
674 | end, 50)
675 | end, opts)
676 | end
677 | return M
678 |
--------------------------------------------------------------------------------
/lua/buffalo/utils.lua:
--------------------------------------------------------------------------------
1 | local Path = require("plenary.path")
2 |
3 | local M = {}
4 |
5 |
6 | function M.project_key()
7 | return vim.loop.cwd()
8 | end
9 |
10 | function M.normalize_path(item)
11 | if string.find(item, ".*:///.*") ~= nil then
12 | return Path:new(item)
13 | end
14 | return Path:new(Path:new(item):absolute()):make_relative(M.project_key())
15 | end
16 |
17 | function M.get_file_name(file)
18 | return file:match("[^/\\]*$")
19 | end
20 |
21 | local function key_in_table(key, table)
22 | for k, _ in pairs(table) do
23 | if k == key then
24 | return true
25 | end
26 | end
27 | return false
28 | end
29 |
30 |
31 | function M.get_short_file_name(file, current_short_fns)
32 | local short_name = nil
33 | -- Get normalized file path
34 | file = M.normalize_path(file)
35 | -- Get all folders in the file path
36 | local folders = {}
37 | -- Convert file to string
38 | local file_str = tostring(file)
39 | for folder in string.gmatch(file_str, "([^/]+)") do
40 | -- insert firts char only
41 | table.insert(folders, folder)
42 | end
43 | -- File to string
44 | file = tostring(file)
45 | -- Count the number of slashes in the relative file path
46 | local slash_count = 0
47 | for _ in string.gmatch(file, "/") do
48 | slash_count = slash_count + 1
49 | end
50 | if slash_count == 0 then
51 | short_name = M.get_file_name(file)
52 | else
53 | -- Return the file name preceded by the number of slashes
54 | short_name = slash_count .. "|" .. M.get_file_name(file)
55 | end
56 | -- Check if the file name is already in the list of short file names
57 | -- If so, return the short file name with one number in front of it
58 | local i = 1
59 | while key_in_table(short_name, current_short_fns) do
60 | local folder = folders[i]
61 | if folder == nil then
62 | folder = i
63 | end
64 | short_name = short_name .. " (" .. folder .. ")"
65 | i = i + 1
66 | end
67 | return short_name
68 | end
69 |
70 | function M.get_short_term_name(term_name)
71 | return term_name:gsub("://.*//", ":")
72 | end
73 |
74 | function M.absolute_path(item)
75 | return Path:new(item):absolute()
76 | end
77 |
78 | function M.is_white_space(str)
79 | return str:gsub("%s", "") == ""
80 | end
81 |
82 | function M.buffer_is_valid(buf_id, buf_name)
83 | return 1 == vim.fn.buflisted(buf_id)
84 | and buf_name ~= ""
85 | end
86 |
87 | function M.tab_is_valid(tab_id, tab_name)
88 | -- return 1 == vim.api.nvim_tabpage_is_valid(tab_id)
89 | -- and tab_name ~= ""
90 | local valid = vim.api.nvim_tabpage_is_valid(tab_id)
91 | if not valid then return -1 end
92 | return tab_name ~= ""
93 | end
94 |
95 | -- tbl_deep_extend does not work the way you would think
96 | local function merge_table_impl(t1, t2)
97 | for k, v in pairs(t2) do
98 | if type(v) == "table" then
99 | if type(t1[k]) == "table" then
100 | merge_table_impl(t1[k], v)
101 | else
102 | t1[k] = v
103 | end
104 | else
105 | t1[k] = v
106 | end
107 | end
108 | end
109 |
110 |
111 | function M.merge_tables(...)
112 | local out = {}
113 | for i = 1, select("#", ...) do
114 | merge_table_impl(out, select(i, ...))
115 | end
116 | return out
117 | end
118 |
119 | function M.deep_copy(obj, seen)
120 | -- Handle non-tables and previously-seen tables.
121 | if type(obj) ~= 'table' then return obj end
122 | if seen and seen[obj] then return seen[obj] end
123 |
124 | -- New table; mark it as seen and copy recursively.
125 | local s = seen or {}
126 | local res = {}
127 | s[obj] = res
128 | for k, v in pairs(obj) do res[M.deep_copy(k, s)] = M.deep_copy(v, s) end
129 | return setmetatable(res, getmetatable(obj))
130 | end
131 |
132 | return M
133 |
--------------------------------------------------------------------------------
/plugin/buffalo.lua:
--------------------------------------------------------------------------------
1 | if vim.g.buffalo_loaded ~= nil then
2 | return
3 | end
4 | vim.g.buffalo_loaded = 1
5 |
--------------------------------------------------------------------------------
/plugin/buffalo.vim:
--------------------------------------------------------------------------------
1 | if exists('g:loaded_buffalo') | finish | endif
2 |
3 | function! s:complete(...)
4 | return "buffers"
5 | endfunction
6 |
7 | command! -nargs=1 -complete=custom,s:complete Buffalo lua require'buffalo'.()
8 |
9 | let g:loaded_buffalo = 1
10 |
11 |
--------------------------------------------------------------------------------