Skip to content

Commit f90a249

Browse files
committed
feat: initial commit
0 parents  commit f90a249

File tree

14 files changed

+341
-0
lines changed

14 files changed

+341
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Oskar Grunning
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.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# bufignore

lua/bufignore/config.lua

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
local defaults = require('bufignore.defaults')
2+
3+
--- @class Config
4+
--- @field private _user_config UserConfig
5+
local M = {}
6+
7+
M._user_config = nil
8+
9+
--- Gets the user's configuration.
10+
--- @return UserConfig config The current configuration.
11+
M.get_user_config = function()
12+
return M._user_config
13+
end
14+
15+
--- Sets up the configuration.
16+
--- @param overrides? UserConfig Configration which will override the defaults.
17+
--- @return UserConfig config The configuration.
18+
M.setup = function(overrides)
19+
M._user_config = vim.tbl_deep_extend('force', defaults, overrides or {})
20+
21+
return M._user_config
22+
end
23+
24+
return M

lua/bufignore/defaults.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--- @alias ConfigCallback fun(event: { bufnr: number, file_path: string }): boolean
2+
3+
--- @class UserConfig
4+
--- @field auto_start boolean
5+
--- @field callback ConfigCallback | false | nil TODO
6+
local defaults = {
7+
auto_start = true,
8+
callback = nil,
9+
}
10+
11+
return defaults

lua/bufignore/dispatcher.lua

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
local queue = require('bufignore.queue')
2+
3+
local augroup =
4+
vim.api.nvim_create_augroup('bufignore-augroup', { clear = true })
5+
6+
--- @class Dispatcher
7+
--- @field private _is_bound boolean
8+
local M = {}
9+
10+
M._is_bound = false
11+
12+
--- Binds event listeners for the plugin.
13+
M.bind_events = function()
14+
if not M._is_bound then
15+
-- Listen for `BufHidden` events and enqueue the file for
16+
-- processing.
17+
vim.api.nvim_create_autocmd('BufHidden', {
18+
group = augroup,
19+
callback = function(event)
20+
queue.enqueue_file(vim.fn.fnamemodify(event.file, ':p'))
21+
end,
22+
})
23+
24+
-- Listen for `DirChanged` events and enqueue all buffers for
25+
-- processing.
26+
vim.api.nvim_create_autocmd('DirChanged', {
27+
group = augroup,
28+
callback = function()
29+
local bufnrs = vim.api.nvim_list_bufs()
30+
31+
queue.clear_queue()
32+
for _, bufnr in ipairs(bufnrs) do
33+
queue.enqueue_file(vim.api.nvim_buf_get_name(bufnr))
34+
end
35+
end,
36+
})
37+
38+
M._is_bound = true
39+
end
40+
end
41+
42+
--- Unbinds event listeners for the plugin.
43+
M.unbind_events = function()
44+
if M._is_bound then
45+
vim.api.nvim_clear_autocmds({ group = augroup })
46+
47+
M._is_bound = false
48+
end
49+
end
50+
51+
return M

lua/bufignore/file-processor.lua

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
local config = require('bufignore.config')
2+
3+
--- @class FileProcessor
4+
local M = {}
5+
6+
--- Checks if a buffer meets the requirements for unlisting.
7+
--- @param bufnr number The buffer number.
8+
--- @param file_path string The path of the file associated with the buffer.
9+
--- @return boolean `true` if the buffer meets the unlisting requirements, otherwise `false`.
10+
M._is_valid_for_unlisting = function(bufnr, file_path)
11+
local user_config = config.get_user_config()
12+
13+
-- Buffer is not visible
14+
return vim.fn.bufwinid(bufnr) == -1
15+
-- Buffer is valid
16+
and vim.api.nvim_buf_is_valid(bufnr)
17+
-- Buffer is loaded
18+
and vim.api.nvim_buf_is_loaded(bufnr)
19+
-- Buffer is listed
20+
and vim.api.nvim_buf_get_option(bufnr, 'buflisted')
21+
-- User-defined callback is either falsy or returns true.
22+
and (
23+
not user_config.callback
24+
or user_config.callback({ bufnr = bufnr, file_path = file_path })
25+
)
26+
end
27+
28+
--- Unlists a buffer for an ignored file.
29+
--- @param file_path string The path of the ignored file.
30+
--- @private
31+
M._unlist_ignored_file = function(file_path)
32+
-- Schedules main event-loop invocation to preventing textlock issues.
33+
vim.schedule(function()
34+
---@diagnostic disable-next-line: param-type-mismatch
35+
local bufnr = vim.fn.bufnr(file_path)
36+
37+
if M._is_valid_for_unlisting(bufnr, file_path) then
38+
-- Unlist git ignored file buffer.
39+
vim.api.nvim_buf_set_option(bufnr, 'buflisted', false)
40+
end
41+
end)
42+
end
43+
44+
--- Unlists buffers for ignored files.
45+
---@param file_paths string[] The paths of the ignored files.
46+
M.unlist_ignored_files = function(file_paths)
47+
for _, file_path in ipairs(file_paths) do
48+
M._unlist_ignored_file(file_path)
49+
end
50+
end
51+
52+
return M

lua/bufignore/git.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
local Job = require('plenary.job')
2+
3+
--- @class Git
4+
local M = {}
5+
6+
--- Checks if files are ignored by Git.
7+
--- @param file_paths string[] The list of file paths to check for ignore status.
8+
--- @param callback function The callback function that receives the ignored file paths.
9+
M.check_ignore = function(file_paths, callback)
10+
local args = { 'check-ignore' }
11+
12+
for _, file_path in ipairs(file_paths) do
13+
table.insert(args, file_path)
14+
end
15+
16+
-- Execute the Git command.
17+
Job:new({
18+
command = 'git',
19+
args = args,
20+
on_exit = function(_, exit_code)
21+
if exit_code > 0 then
22+
callback({})
23+
end
24+
end,
25+
on_stdout = function(_, data)
26+
local ignored_file_paths = {}
27+
for line in data:gmatch('([^\n]+)') do
28+
table.insert(ignored_file_paths, line)
29+
end
30+
31+
callback(ignored_file_paths)
32+
end,
33+
}):start()
34+
end
35+
36+
return M

lua/bufignore/init.lua

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
local config = require('bufignore.config')
2+
local dispatcher = require('bufignore.dispatcher')
3+
4+
--- @class Init
5+
local M = {}
6+
7+
--- Sets up the plugin.
8+
--- @param overrides? UserConfig Configration which will override the defaults.
9+
M.setup = function(overrides)
10+
local user_config = config.setup(overrides)
11+
12+
if user_config.auto_start then
13+
M.start()
14+
end
15+
end
16+
17+
--- Starts the plugin.
18+
M.start = function()
19+
dispatcher.bind_events()
20+
end
21+
22+
--- Stops the plugin.
23+
M.stop = function()
24+
dispatcher.unbind_events()
25+
end
26+
27+
return M

lua/bufignore/queue.lua

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
local git = require('bufignore.git')
2+
local file_processor = require('bufignore.file-processor')
3+
4+
--- @class Queue
5+
--- @field private _queue string[]
6+
--- @field private _is_running boolean
7+
--- @field private _throttle_ms number
8+
local M = {}
9+
10+
M._is_running = false
11+
M._queue = {}
12+
M._throttle_ms = 400
13+
14+
--- Validates a file.
15+
--- @param file_path string The file path to validate.
16+
--- @return boolean valid_file_path `true` if the file path is valid, otherwise `false`.
17+
--- @private
18+
M._is_valid_file = function(file_path)
19+
local is_non_empty_file_path = file_path ~= nil and file_path:len() > 0
20+
local is_file_path_in_cwd = vim.startswith(file_path, vim.fn.getcwd())
21+
22+
return is_non_empty_file_path and is_file_path_in_cwd
23+
end
24+
25+
--- Clears the event queue.
26+
M.clear_queue = function()
27+
M._queue = {}
28+
M._is_running = false
29+
end
30+
31+
--- Adds an event to the queue, and starts processing if not already running.
32+
--- @param file_path string
33+
M.enqueue_file = function(file_path)
34+
if M._is_valid_file(file_path) then
35+
local absolute_file_path = vim.fn.fnamemodify(file_path, ':p')
36+
37+
table.insert(M._queue, absolute_file_path)
38+
39+
M._process_queue()
40+
end
41+
end
42+
43+
--- Processes pending file paths in the queue after the throttle time, if not already running.
44+
--- @private
45+
M._process_queue = function()
46+
if M._is_running then
47+
return
48+
end
49+
50+
M._is_running = true
51+
52+
vim.defer_fn(function()
53+
local pending_files = vim.deepcopy(M._queue)
54+
55+
git.check_ignore(pending_files, file_processor.unlist_ignored_files)
56+
57+
M.clear_queue()
58+
end, M._throttle_ms)
59+
end
60+
61+
return M

package-lock.json

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "bufignore.nvim",
3+
"version": "1.0.0",
4+
"author": "Oskar Grunning",
5+
"description": "Unlist hidden buffers that are git ignored.",
6+
"homepage": "https://github.com/sQVe/bufignore.nvim",
7+
"repository": "github:sQVe/bufignore.nvim#readme",
8+
"license": "MIT",
9+
"devDependencies": {
10+
"prettier": "latest"
11+
}
12+
}

stylua.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
column_width = 80
2+
indent_type = "Spaces"
3+
indent_width = 2
4+
quote_style = "AutoPreferSingle"

0 commit comments

Comments
 (0)