From 69552006bc5514f5a98498b74c1e141d7f215415 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Mon, 27 Jul 2026 18:09:14 +0200 Subject: [PATCH] feat(settings)!: Add `settings.keep_nvim_dir` (:h carbon-setting-keep-nvim-dir) This supports Neovim's new `dir` plugin which shows a directory listing on `:edit [dirname]`. Fixes a double initialization issue on startup where Carbon would be initialized once at the end of the `setup` function and once again via the `FileType` (`BufWinEnter` prior to this commit) autocommand when `settings.open_on_dir` was enabled. The impact of the above wasn't noticable due to Carbon caching entries and lines to render during the first initialization however it is a little bit less smelly now. --- Makefile | 2 +- doc/carbon.txt | 18 +++++++++-- lua/carbon/init.lua | 29 ++++++++++++++---- lua/carbon/settings.lua | 3 ++ lua/carbon/view.lua | 2 +- test/config/helpers.lua | 25 ++++++++++++++++ test/specs/carbon_spec.lua | 22 ++++++-------- test/specs/entry_spec.lua | 17 ++++++++--- test/specs/settings_spec.lua | 58 +++++++++++++++++++++++++++++------- test/specs/util_spec.lua | 11 +++++-- test/specs/watcher_spec.lua | 6 ++-- 11 files changed, 151 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index c006fda..6d91252 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ format-check: .PHONY: dev dev: - nvim -Nu dev/init.lua + nvim -Nu dev/init.lua . .PHONY: doc doc: diff --git a/doc/carbon.txt b/doc/carbon.txt index e92926a..0151ab4 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -2387,12 +2387,26 @@ SETTINGS *carbon-setting Sets both `vim.g.loaded_netrw` and `vim.g.loaded_netrwPlugin` to `1` and deletes |augroup| `FileExplorer` and `Network`. - When `true` NetRW will not be harmed by Carbon. This also means that - `Explore` and `Lexplore` are no longer aliased to `Carbon` and `Lcarbon` anymore. + When `true` NetRW will not be harmed by Carbon. This also + means that the `Explore`, `Lexplore`, `Rexplore` and `ToggleSidebarExplore` + commands are no longer aliased to `Carbon`, `Lcarbon` and `Rcarbon` and + `ToggleSidebarCarbon` (respectively) anymore. This does not prevent Carbon from showing on vim startup instead of NetRW. To control this behavior, see |carbon-setting-auto-open|. + `------------------------------------------------------------------------------` + keep_nvim_dir *carbon-setting-keep-nvim-dir* + + Default: `false` + + Sets `vim.g.loaded_nvim_dir_plugin` to `1` and deletes |augroup| `nvim.dir`. + + When `true` the builtin |dir| plugin will not be harmed by Carbon. + + This does not prevent Carbon from showing on vim startup instead of the + builtin |dir| plugin. To control this behavior, see |carbon-setting-auto-open|. + `------------------------------------------------------------------------------` file_icons *carbon-setting-file-icons* diff --git a/lua/carbon/init.lua b/lua/carbon/init.lua index 59519d0..e5f363a 100644 --- a/lua/carbon/init.lua +++ b/lua/carbon/init.lua @@ -54,14 +54,21 @@ function carbon.setup(user_settings) util.autocmd('SessionLoadPost', carbon.session_load_post, { pattern = '*' }) util.autocmd('WinResized', carbon.win_resized, { pattern = '*' }) - if settings.open_on_dir then - util.autocmd('BufWinEnter', carbon.explore_buf_dir, { pattern = '*' }) - end - if settings.sync_on_cd then util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) end + if settings.open_on_dir then + local fts = {} + + fts[#fts + 1] = not settings.keep_netrw and 'netrw' or nil + fts[#fts + 1] = not settings.keep_nvim_dir and 'directory' or nil + + local pattern = table.concat(fts, ',') + + util.autocmd('FileType', carbon.explore_buf_dir, { pattern = pattern }) + end + if not settings.keep_netrw then vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 @@ -75,6 +82,12 @@ function carbon.setup(user_settings) create_command('ToggleSidebarExplore', carbon.toggle_sidebar) end + if not settings.keep_nvim_dir then + vim.g.loaded_nvim_dir_plugin = 1 + + pcall(vim.api.nvim_del_augroup_by_name, 'nvim.dir') + end + for action in pairs(settings.defaults.actions) do vim.keymap.set('', util.plug(action), carbon[action]) end @@ -86,8 +99,9 @@ function carbon.setup(user_settings) end if - vim.fn.has('vim_starting') + vim.fn.has('vim_starting') == 1 and settings.auto_open + and not settings.open_on_dir and util.is_directory(open) then view.activate({ path = open }) @@ -354,7 +368,10 @@ function carbon.explore_float(opts) end function carbon.explore_buf_dir(params) - if vim.bo.filetype == 'carbon.explorer' then + local is_carbon_buf = vim.bo.filetype == 'carbon.explorer' + local vim_starting = vim.fn.has('vim_starting') == 1 + + if is_carbon_buf or vim_starting and not settings.auto_open then return end diff --git a/lua/carbon/settings.lua b/lua/carbon/settings.lua index 828d831..4651411 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings.lua @@ -5,6 +5,7 @@ --- @field compress boolean --- @field auto_open boolean --- @field keep_netrw boolean +--- @field keep_nvim_dir boolean --- @field file_icons boolean --- @field sync_on_cd boolean --- @field sync_delay integer @@ -25,6 +26,7 @@ --- @field compress? boolean --- @field auto_open? boolean --- @field keep_netrw? boolean +--- @field keep_nvim_dir? boolean --- @field file_icons? boolean --- @field sync_on_cd? boolean --- @field sync_delay? integer @@ -49,6 +51,7 @@ local defaults = { compress = true, auto_open = true, keep_netrw = false, + keep_nvim_dir = false, file_icons = pcall(require, 'nvim-web-devicons') and true or false, sync_on_cd = not vim.opt.autochdir:get(), sync_delay = 20, diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 262d47b..8c79866 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -952,8 +952,8 @@ function view:create() util.autocmd('CursorMovedI', create_insert_move(cursor), { buffer = 0 }) vim.keymap.set('i', '', create_confirm(cursor), { buffer = 0 }) vim.keymap.set('i', '', create_cancel(cursor), { buffer = 0 }) - vim.cmd.startinsert({ bang = true }) vim.api.nvim_set_option_value('modifiable', true, { buf = 0 }) + vim.cmd.startinsert({ bang = true }) vim.api.nvim_buf_set_lines( 0, cursor.edit_lnum, diff --git a/test/config/helpers.lua b/test/config/helpers.lua index 1b65b76..5542362 100644 --- a/test/config/helpers.lua +++ b/test/config/helpers.lua @@ -1,9 +1,34 @@ +local carbon = require('carbon') local util = require('carbon.util') local view = require('carbon.view') local entry = require('carbon.entry') local constants = require('carbon.constants') local helpers = {} +--- @param path string? (Default: |uv.cwd|) Absolute path to load entries from +--- @return carbon.entry.Entry[] +function helpers.load_entries(path) + return entry.new(path or vim.uv.cwd() --[[@as string]]):children() +end + +--- @param name string Autocommand name +--- @param opts vim.api.keyset.get_autocmds? (Default: `{}`) Options to pass to |nvim_get_autocmds| +--- @return boolean `true` if autocommand {name} exists +function helpers.augroup_exists(name, opts) + local autocmds = vim.api.nvim_get_autocmds(opts or {}) + local result = util.tbl_find(autocmds, function(autocmd) + return autocmd.group_name == name + end) + + return result and true or false +end + +function helpers.reset_editor_state() + carbon.explore() + util.cursor(1, 1) + vim.cmd.only({ mods = { silent = true } }) +end + --- @param header string function helpers.github_anchor(header) header = string.gsub(header, '^#+ ?', '') diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index 9cd8366..f22421c 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -7,11 +7,7 @@ local settings = require('carbon.settings') local helpers = require('test.config.helpers') describe('carbon', function() - before_each(function() - carbon.explore() - util.cursor(1, 1) - vim.cmd.only({ mods = { silent = true } }) - end) + before_each(helpers.reset_editor_state) describe('autocommands', function() describe('DirChanged', function() @@ -28,6 +24,14 @@ describe('carbon', function() end) end) + describe('FileType', function() + it('has global event', function() + local autocmd = helpers.autocmd('FileType') + + assert.is_false(autocmd.buflocal) + end) + end) + describe('BufWinEnter', function() it('has buffer local event', function() local autocmd = helpers.autocmd( @@ -37,12 +41,6 @@ describe('carbon', function() assert.is_true(autocmd.buflocal) end) - - it('has a global event', function() - local autocmd = helpers.autocmd('BufWinEnter') - - assert.is_false(autocmd.buflocal) - end) end) describe('BufHidden', function() @@ -83,8 +81,6 @@ describe('carbon', function() end) describe('tabe', function() - -- FIXME: Don't know what is going on, fine locally, GH actions complains - -- "file already exists" pending('opens directories in new tab', function() assert.is.equal(#vim.api.nvim_list_tabpages(), 1) diff --git a/test/specs/entry_spec.lua b/test/specs/entry_spec.lua index 16500d5..655ed79 100644 --- a/test/specs/entry_spec.lua +++ b/test/specs/entry_spec.lua @@ -61,10 +61,12 @@ describe('carbon.entry', function() describe('find', function() it('returns loaded children', function() - assert.is.same( - entry, - getmetatable(entry.find(helpers.resolve('lua')) or {}) - ) + helpers.load_entries(vim.uv.cwd()) + + local lua = entry.find(helpers.resolve('lua')) --[[@as carbon.entry.Entry]] + + assert.is_not_nil(lua) + assert.is.same(entry, getmetatable(lua)) end) it('returns nil for not loaded children', function() @@ -82,8 +84,15 @@ describe('carbon.entry', function() end) it('calls synchronize recursively on directory', function() + helpers.load_entries(vim.fs.abspath('lua')) + helpers.load_entries(vim.fs.abspath('lua/carbon')) + local lua = entry.find(helpers.resolve('lua')) --[[@as carbon.entry.Entry]] local lua_carbon = entry.find(helpers.resolve('lua/carbon')) --[[@as carbon.entry.Entry]] + + assert.is_not_nil(lua) + assert.is_not_nil(lua_carbon) + local lua_synchronize = spy.on(lua, 'synchronize') local lua_carbon_synchronize = spy.on(lua_carbon, 'synchronize') diff --git a/test/specs/settings_spec.lua b/test/specs/settings_spec.lua index 8a0cf2f..c8f9293 100644 --- a/test/specs/settings_spec.lua +++ b/test/specs/settings_spec.lua @@ -1,4 +1,6 @@ +local helpers = require('test.config.helpers') local util = require('carbon.util') +local constants = require('carbon.constants') local settings = require('carbon.settings') describe('carbon.settings', function() @@ -34,19 +36,31 @@ describe('carbon.settings', function() end) it('deletes augroup FileExplorer', function() - assert.is_nil( - util.tbl_find(vim.api.nvim_get_autocmds({}), function(autocmd) - return autocmd.group_name == 'FileExplorer' - end) - ) + assert.is_false(helpers.augroup_exists('FileExplorer')) end) it('deletes augroup Network', function() - assert.is_nil( - util.tbl_find(vim.api.nvim_get_autocmds({}), function(autocmd) - return autocmd.group_name == 'Network' - end) - ) + assert.is_false(helpers.augroup_exists('Network')) + end) + end) + + describe('keep_nvim_dir', function() + it('is a boolean', function() + assert.is_boolean(settings.keep_nvim_dir) + end) + + it('sets vim.g.loaded_nvim_dir_plugin', function() + assert.is.same(1, vim.g.loaded_nvim_dir_plugin) + end) + + it('deletes augroup nvim.dir', function() + assert.is_false(helpers.augroup_exists('nvim.dir')) + end) + end) + + describe('file_icons', function() + it('is a boolean', function() + assert.is_boolean(settings.file_icons) end) end) @@ -60,6 +74,30 @@ describe('carbon.settings', function() end) end) + describe('open_on_dir', function() + it('is a boolean', function() + assert.is_boolean(settings.open_on_dir) + end) + + for _, ft_pat in ipairs({ 'netrw', 'directory' }) do + it(string.format('creates %s FileType autocmd', ft_pat), function() + assert.is_not_nil(vim.api.nvim_get_autocmds({ + event = 'FileType', + group = constants.augroup, + pattern = ft_pat, + })[1]) + end) + end + + it(':edit [dirname] shows Carbon buffer', function() + assert.is_not.same(vim.bo.filetype, 'carbon.explorer') + + vim.cmd.edit('lua') + + assert.is.same(vim.bo.filetype, 'carbon.explorer') + end) + end) + describe('sync_delay', function() it('is a number', function() assert.is_number(settings.sync_delay) diff --git a/test/specs/util_spec.lua b/test/specs/util_spec.lua index 5d33f56..79d0da5 100644 --- a/test/specs/util_spec.lua +++ b/test/specs/util_spec.lua @@ -34,8 +34,13 @@ describe('carbon.util', function() describe('cursor', function() it('{lnum} and {col} are both 1-based', function() + vim.cmd.enew() + vim.api.nvim_buf_set_lines(0, 0, -1, true, { 'line 1', 'line 2' }) + util.cursor(2, 2) assert.is.same({ 2, 1 }, vim.api.nvim_win_get_cursor(0)) + + vim.cmd.bdelete({ bang = true }) end) end) @@ -61,11 +66,11 @@ describe('carbon.util', function() describe('relative_path', function() it('Makes an absolute path relative to provided {base}', function() - local entry = helpers.entry('doc/assets') --[[@as carbon.entry.Entry]] + local abs_path = vim.fs.abspath('doc/assets') local cwd = vim.uv.cwd() --[[@as string]] - local relative_path = util.relative_path(entry.path, cwd) + local relative_path = util.relative_path(abs_path, cwd) - assert.is_true(vim.startswith(entry.path, cwd)) + assert.is_true(vim.startswith(abs_path, cwd)) assert.is_false(vim.startswith(relative_path, cwd)) assert.is_false(vim.startswith(relative_path, '/')) end) diff --git a/test/specs/watcher_spec.lua b/test/specs/watcher_spec.lua index f498a8d..f98022b 100644 --- a/test/specs/watcher_spec.lua +++ b/test/specs/watcher_spec.lua @@ -56,6 +56,7 @@ describe('carbon.watcher', function() describe('carbon:synchronize', function() it('triggers on new file', function() local callback = spy.new(function() end) + local tmp_name = vim.fs.basename(os.tmpname()) watcher.register(vim.uv.cwd()) watcher.on( @@ -63,12 +64,13 @@ describe('carbon.watcher', function() callback --[[@as carbon.watcher.CallbackFunction]] ) - helpers.ensure_path('check.txt') + vim.wait(50) + helpers.ensure_path(tmp_name) helpers.poll_spy_calls(callback, 1) assert .spy(callback).was - .called_with('carbon:synchronize', vim.uv.cwd(), 'check.txt', nil) + .called_with('carbon:synchronize', vim.uv.cwd(), tmp_name, nil) end) it('triggers on file change', function()