From caf697e557c259dff9a6de0300fb6b6f8da33362 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 21 Aug 2022 18:11:05 +0200 Subject: [PATCH 01/52] replace `carbon.buffer` with new `carbon.view` module --- dev/init.lua | 3 + lua/carbon.lua | 327 +++++++--------- lua/carbon/entry.lua | 4 + lua/carbon/settings.lua | 3 +- lua/carbon/util.lua | 18 + lua/carbon/view.lua | 772 +++++++++++++++++++++++++++++++++++++ plugin/carbon.vim | 1 - test/config/init.lua | 2 +- test/specs/buffer_spec.lua | 453 ---------------------- test/specs/carbon_spec.lua | 46 ++- 10 files changed, 962 insertions(+), 667 deletions(-) create mode 100644 lua/carbon/view.lua delete mode 100644 plugin/carbon.vim delete mode 100644 test/specs/buffer_spec.lua diff --git a/dev/init.lua b/dev/init.lua index 0869a54..0e7054a 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -1,3 +1,6 @@ +vim.opt.termguicolors = true vim.opt.runtimepath:append({ vim.env.HOME .. '/Dev/sidofc/lua/carbon.nvim' }) vim.opt.runtimepath:remove({ vim.env.HOME .. '/.config/nvim' }) vim.opt.packpath:remove({ vim.env.HOME .. '/.local/share/nvim/site' }) + +require('carbon').setup() diff --git a/lua/carbon.lua b/lua/carbon.lua index 3b5e0bf..0a3fb75 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -1,269 +1,216 @@ local util = require('carbon.util') -local buffer = require('carbon.buffer') local watcher = require('carbon.watcher') local settings = require('carbon.settings') +local view = require('carbon.view') local carbon = {} -local data = { initialized = false } function carbon.setup(user_settings) - if data.initialized then - return - end - - if type(user_settings) == 'function' then - user_settings(settings) - else - local next = vim.tbl_deep_extend('force', settings, user_settings) - - for setting, value in pairs(next) do - settings[setting] = value + if not vim.g.carbon_initialized then + if type(user_settings) == 'function' then + user_settings(settings) + elseif type(user_settings) == 'table' then + local next = vim.tbl_deep_extend('force', settings, user_settings) + + for setting, value in pairs(next) do + settings[setting] = value + end end - end - return settings -end - -function carbon.initialize() - if data.initialized then - return - else - data.initialized = true - end + local argv = vim.fn.argv() + local open = argv[1] and vim.fn.fnamemodify(argv[1], ':p') or vim.loop.cwd() + local command_opts = { bang = true, nargs = '?', complete = 'dir' } - watcher.on('carbon:synchronize', buffer.defer_resync) + watcher.on('carbon:synchronize', function(_, path) + view.resync(path) + end) - util.command('Carbon', carbon.explore, { bang = true }) - util.command('Lcarbon', carbon.explore_left, { bang = true }) - util.command('Fcarbon', carbon.explore_float, { bang = true }) + util.command('Carbon', carbon.explore, command_opts) + util.command('Lcarbon', carbon.explore_left, command_opts) + util.command('Fcarbon', carbon.explore_float, command_opts) - for action in pairs(settings.defaults.actions) do - vim.keymap.set('', util.plug(action), carbon[action]) - end - - if settings.sync_on_cd then - util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) - end - - if type(settings.highlights) == 'table' then - for group, properties in pairs(settings.highlights) do - util.highlight(group, properties) + if settings.sync_on_cd then + util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) end - end - if not settings.keep_netrw then - vim.g.loaded_netrw = 1 - vim.g.loaded_netrwPlugin = 1 + if not settings.keep_netrw then + vim.g.loaded_netrw = 1 + vim.g.loaded_netrwPlugin = 1 - pcall(vim.api.nvim_del_augroup_by_name, 'FileExplorer') - pcall(vim.api.nvim_del_augroup_by_name, 'Network') + pcall(vim.api.nvim_del_augroup_by_name, 'FileExplorer') + pcall(vim.api.nvim_del_augroup_by_name, 'Network') - util.command('Explore', carbon.explore, { bang = true }) - util.command('Lexplore', carbon.explore_left, { bang = true }) - end - - local argv = vim.fn.argv() - local open = argv[1] and vim.fn.fnamemodify(argv[1], ':p') or vim.loop.cwd() + util.command('Explore', carbon.explore, command_opts) + util.command('Lexplore', carbon.explore_left, command_opts) + end - if - vim.fn.has('vim_starting') - and settings.auto_open - and util.is_directory(open) - then - local current_buffer = vim.api.nvim_win_get_buf(0) + for action in pairs(settings.defaults.actions) do + vim.keymap.set('', util.plug(action), carbon[action]) + end - buffer.launch(open) + if type(settings.highlights) == 'table' then + for group, properties in pairs(settings.highlights) do + util.highlight(group, properties) + end + end - if vim.api.nvim_buf_is_valid(current_buffer) then - vim.api.nvim_buf_delete(current_buffer, { force = true }) + if + vim.fn.has('vim_starting') + and settings.auto_open + and util.is_directory(open) + then + view.activate({ path = open, delete_current_buf = true }) end - end - return carbon + vim.g.carbon_initialized = true + end end function carbon.toggle_recursive() - local line = buffer.cursor().line - - if line.entry.is_directory then - line.entry:set_open(not line.entry:is_open(), true) - - buffer.render() - end + view.execute(function(context) + if context.cursor.line.entry.is_directory then + context.cursor.line.entry:toggle_open(true) + context.view:update() + context.view:render() + end + end) end function carbon.edit() - local line = buffer.cursor().line - - if line.entry.is_directory then - line.entry:set_open(not line.entry:is_open()) - - buffer.render() - elseif vim.w.carbon_lexplore_window then - vim.cmd({ cmd = 'wincmd', args = { 'l' } }) - - if vim.w.carbon_lexplore_window == vim.api.nvim_get_current_win() then - vim.cmd({ - cmd = 'split', - args = { line.entry.path }, - mods = { vertical = true, split = 'belowright' }, - }) - - vim.api.nvim_win_set_width( - util.bufwinid(buffer.handle()), - settings.sidebar_width - ) + view.execute(function(context) + if context.cursor.line.entry.is_directory then + context.cursor.line.entry:toggle_open() + context.view:update() + context.view:render() else - vim.cmd({ cmd = 'edit', args = { line.entry.path } }) - end - else - if vim.w.carbon_fexplore_window then - vim.api.nvim_win_close(0, 1) + view.handle_sidebar_or_float() + vim.cmd.edit(context.cursor.line.entry.path) end - - vim.cmd({ cmd = 'edit', args = { line.entry.path } }) - end + end) end function carbon.split() - local line = buffer.cursor().line + view.execute(function(context) + if not context.cursor.line.entry.is_directory then + if vim.w.carbon_fexplore_window then + vim.api.nvim_win_close(0, 1) + end - if not line.entry.is_directory then - if vim.w.carbon_fexplore_window then - vim.api.nvim_win_close(0, 1) + vim.cmd.split(context.cursor.line.entry.path) end - - vim.cmd({ cmd = 'split', args = { line.entry.path } }) - end + end) end function carbon.vsplit() - local line = buffer.cursor().line + view.execute(function(context) + if not context.cursor.line.entry.is_directory then + if vim.w.carbon_fexplore_window then + vim.api.nvim_win_close(0, 1) + end - if not line.entry.is_directory then - if vim.w.carbon_fexplore_window then - vim.api.nvim_win_close(0, 1) + vim.cmd.vsplit(context.cursor.line.entry.path) end - - vim.cmd({ cmd = 'vsplit', args = { line.entry.path } }) - end + end) end -function carbon.explore(options) - if options and options.bang or settings.always_reveal then - buffer.expand_to_path(vim.fn.expand('%')) - end - - buffer.show() +function carbon.up() + view.execute(function(context) + if context.view:up() then + context.view:update() + context.view:render() + util.cursor(1, 1) + end + end) end -function carbon.explore_left(options) - if options and options.bang or settings.always_reveal then - buffer.expand_to_path(vim.fn.expand('%')) - end - - local existing_win - - for _, win in ipairs(vim.api.nvim_list_wins()) do - if pcall(vim.api.nvim_win_get_var, win, 'carbon_lexplore_window') then - if vim.api.nvim_win_is_valid(win) then - existing_win = win - end - - break +function carbon.reset() + view.execute(function(context) + if context.view:reset() then + context.view:update() + context.view:render() + util.cursor(1, 1) end - end + end) +end - if existing_win then - vim.api.nvim_set_current_win(existing_win) - buffer.show() - else - vim.cmd({ cmd = 'split', mods = { vertical = true, split = 'leftabove' } }) - buffer.show() +function carbon.down() + view.execute(function(context) + if context.view:down() then + context.view:update() + context.view:render() + util.cursor(1, 1) + end + end) +end - vim.api.nvim_win_set_width( - util.bufwinid(buffer.handle()), - settings.sidebar_width - ) +function carbon.cd(path) + view.execute(function(context) + local destination = path and path.file or path or vim.v.event.cwd - vim.w.carbon_lexplore_window = vim.api.nvim_get_current_win() - end + if context.view:cd(destination) then + context.view:update() + context.view:render() + util.cursor(1, 1) + end + end) end -function carbon.explore_float(options) - if options and options.bang or settings.always_reveal then - buffer.expand_to_path(vim.fn.expand('%')) - end - - local window_settings = settings.float_settings +function carbon.explore(options_param) + local options = options_param or {} + local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' - if type(window_settings) == 'function' then - window_settings = window_settings() + if path == '' then + path = vim.loop.cwd() end - window_settings = vim.deepcopy(window_settings) - - local carbon_fexplore_window = vim.api.nvim_get_current_win() - local window = vim.api.nvim_open_win(buffer.handle(), 1, window_settings) - - buffer.render() - - vim.api.nvim_win_set_option( - window, - 'winhl', - 'FloatBorder:Normal,Normal:Normal' - ) - - vim.w.carbon_fexplore_window = carbon_fexplore_window + view.activate({ path = path, reveal = options.bang }) end -function carbon.up() - if buffer.up() then - util.cursor(1, 1) - buffer.render() - end -end +function carbon.explore_left(options_param) + local options = options_param or {} + local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' -function carbon.reset() - if buffer.reset() then - util.cursor(1, 1) - buffer.render() + if path == '' then + path = vim.loop.cwd() end -end -function carbon.down() - if buffer.down() then - util.cursor(1, 1) - buffer.render() - end + view.activate({ path = path, reveal = options.bang, sidebar = true }) end -function carbon.cd(path) - local destination = path and path.file or path or vim.v.event.cwd +function carbon.explore_float(options_param) + local options = options_param or {} + local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' - if buffer.cd(destination) then - util.cursor(1, 1) - buffer.render() + if path == '' then + path = vim.loop.cwd() end + + view.activate({ path = path, reveal = options.bang, float = true }) end function carbon.quit() if #vim.api.nvim_list_wins() > 1 then vim.api.nvim_win_close(0, 1) elseif #vim.api.nvim_list_bufs() > 1 then - vim.cmd({ cmd = 'bprevious' }) + vim.cmd.bprevious() end end function carbon.create() - buffer.create() + view.execute(function(context) + context.view:create() + end) end function carbon.delete() - buffer.delete() + view.execute(function(context) + context.view:delete() + end) end function carbon.move() - buffer.move() + view.execute(function(context) + context.view:move() + end) end return carbon diff --git a/lua/carbon/entry.lua b/lua/carbon/entry.lua index 9132fb5..65fa261 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry.lua @@ -137,6 +137,10 @@ function entry:is_open() return data.open[self.path] and true or false end +function entry:toggle_open(recursive) + self:set_open(not self:is_open(), recursive) +end + function entry:children() if self.is_directory and not self:has_children() then self:set_children(self:get_children()) diff --git a/lua/carbon/settings.lua b/lua/carbon/settings.lua index 217978d..84cb255 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings.lua @@ -39,7 +39,7 @@ local defaults = { return { relative = 'editor', style = 'minimal', - border = 'single', + border = 'rounded', width = width, height = height, col = math.floor(columns / 2 - width / 2), @@ -68,6 +68,7 @@ local defaults = { CarbonIndicator = { fg = 'Gray', ctermfg = 'DarkGray', bold = true }, CarbonDanger = { fg = '#ff3333', ctermfg = 'Red', bold = true }, CarbonPending = { fg = '#ffee00', ctermfg = 'Yellow', bold = true }, + CarbonFloat = { bg = '#111111', ctermbg = 'black' }, CarbonFlash = { link = 'Visual' }, }, } diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index c1ce0ea..2469d9c 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -2,6 +2,12 @@ local constants = require('carbon.constants') local settings = require('carbon.settings') local util = {} +function util.resolve(path) + local normalized = vim.fs.normalize(path) + + return string.gsub(vim.fn.fnamemodify(normalized, ':p'), '/+$', '') +end + function util.is_excluded(path) if settings.exclude then for _, pattern in ipairs(settings.exclude) do @@ -245,4 +251,16 @@ function util.set_winhl(win, highlights) vim.api.nvim_win_set_option(win, 'winhl', table.concat(winhls, ',')) end +function util.clear_extmarks(buf, ...) + local extmarks = vim.api.nvim_buf_get_extmarks(buf, constants.hl, ...) + + for _, extmark in ipairs(extmarks) do + vim.api.nvim_buf_del_extmark(buf, constants.hl, extmark[1]) + end +end + +function util.add_highlight(buf, ...) + vim.api.nvim_buf_add_highlight(buf, constants.hl, ...) +end + return util diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua new file mode 100644 index 0000000..6a2cd51 --- /dev/null +++ b/lua/carbon/view.lua @@ -0,0 +1,772 @@ +local util = require('carbon.util') +local entry = require('carbon.entry') +local watcher = require('carbon.watcher') +local settings = require('carbon.settings') +local constants = require('carbon.constants') +local view = {} +local views = {} + +view.__index = view +view.sidebar = { origin = -1, target = -1 } +view.float = { origin = -1, target = -1 } + +local function create_leave(ctx) + vim.cmd({ cmd = 'stopinsert' }) + ctx.target:set_compressible(ctx.prev_compressible) + util.cursor(ctx.target_line.lnum, 1) + vim.keymap.del('i', '', { buffer = 0 }) + vim.keymap.del('i', '', { buffer = 0 }) + util.clear_autocmd('CursorMovedI', { buffer = 0 }) + ctx.view:update() + ctx.view:render() +end + +local function create_confirm(ctx) + return function() + local text = vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) + local name = vim.fn.fnamemodify(text, ':t') + local parent_directory = ctx.target.path + .. '/' + .. vim.trim(vim.fn.fnamemodify(text, ':h')) + + vim.fn.mkdir(parent_directory, 'p') + + if name ~= '' then + vim.fn.writefile({}, parent_directory .. '/' .. name) + end + + create_leave(ctx) + view.resync(vim.fn.fnamemodify(parent_directory, ':h')) + end +end + +local function create_cancel(ctx) + return function() + ctx.target:set_open(ctx.prev_open) + create_leave(ctx) + end +end + +local function create_insert_move(ctx) + return function() + local text = ctx.edit_prefix + .. vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) + local last_slash_col = vim.fn.strridx(text, '/') + 1 + + vim.api.nvim_buf_set_lines(0, ctx.edit_lnum, ctx.edit_lnum + 1, 1, { text }) + util.clear_extmarks(0, { ctx.edit_lnum, 0 }, { ctx.edit_lnum, -1 }, {}) + util.add_highlight(0, 'CarbonDir', ctx.edit_lnum, 0, last_slash_col) + util.add_highlight(0, 'CarbonFile', ctx.edit_lnum, last_slash_col, -1) + util.cursor(ctx.edit_lnum + 1, math.max(ctx.edit_col, vim.fn.col('.'))) + end +end + +function view.get(path) + local resolved = util.resolve(path) + local found_view = util.tbl_find(views, function(target_view) + return target_view.root.path == resolved + end) + + if found_view then + return found_view + end + + local index = #views + 1 + local instance = setmetatable( + { index = index, initial = resolved, root = entry.new(resolved) }, + view + ) + + views[index] = instance + instance.index = index + + return instance +end + +function view.activate(options_param) + local options = options_param or {} + local original_window = vim.api.nvim_get_current_win() + local original_buffer = vim.api.nvim_get_current_buf() + local original_buffer_valid = vim.api.nvim_buf_is_valid(original_buffer) + local current_view = (options.path and view.get(options.path)) + or view.current() + + if options.reveal or settings.always_reveal then + current_view:expand_to_path(vim.fn.expand('%')) + end + + if options.sidebar then + if vim.api.nvim_win_is_valid(view.sidebar.origin) then + vim.api.nvim_set_current_win(view.sidebar.origin) + else + vim.cmd.split({ mods = { vertical = true, split = 'leftabove' } }) + + view.sidebar = { + origin = vim.api.nvim_get_current_win(), + target = original_window, + } + end + + vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + vim.api.nvim_win_set_buf(view.sidebar.origin, current_view:buffer()) + elseif options.float then + local float_settings = settings.float_settings + or settings.defaults.float_settings + + float_settings = type(float_settings) == 'function' and float_settings() + or vim.deepcopy(float_settings) + + view.float = { + origin = vim.api.nvim_open_win(current_view:buffer(), 1, float_settings), + target = original_window, + } + + vim.api.nvim_win_set_option( + view.float.origin, + 'winhl', + 'FloatBorder:CarbonFloat,Normal:CarbonFloat' + ) + else + vim.api.nvim_win_set_buf(0, current_view:buffer()) + end + + if original_buffer_valid and options.delete_current_buf then + vim.api.nvim_buf_delete(original_buffer, { force = true }) + end +end + +function view.handle_sidebar_or_float() + local current_window = vim.api.nvim_get_current_win() + + if current_window == view.sidebar.origin then + if vim.api.nvim_win_is_valid(view.sidebar.target) then + vim.api.nvim_set_current_win(view.sidebar.target) + else + vim.cmd.split({ mods = { vertical = true, split = 'belowright' } }) + + view.sidebar.target = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + end + elseif current_window == view.float.origin then + vim.api.nvim_win_close(0, true) + end +end + +function view:expand_to_path(path) + local resolved = util.resolve(path) + + if vim.startswith(resolved, self.root.path) then + local dirs = vim.split(string.sub(resolved, #self.root.path + 2), '/') + local current = self.root + + for _, dir in ipairs(dirs) do + current:children() + + current = entry.find(string.format('%s/%s', current.path, dir)) + + if current then + current:set_open(true) + else + break + end + end + + if current and current.path == resolved then + self.flash = current + + return true + end + + return false + end +end + +function view.exists(index) + return views[index] and true or false +end + +function view.current() + local bufnr = vim.api.nvim_get_current_buf() + local ref = select(2, pcall(vim.api.nvim_buf_get_var, bufnr, 'carbon')) + + return ref and views[ref.index] or false +end + +function view.execute(callback) + local current_view = view.current() + + if current_view then + return callback({ cursor = current_view:cursor(), view = current_view }) + end +end + +function view.list() + return views +end + +function view.resync(path) -- luacheck:ignore unused argument path + -- print(string.format("view.resync('%s')", path)) +end + +function view:update() + self.cached_lines = nil +end + +function view:buffers() + return vim.tbl_filter(function(bufnr) + local ref = select(2, pcall(vim.api.nvim_buf_get_var, bufnr, 'carbon')) + + return ref and ref.index == self.index + end, vim.api.nvim_list_bufs()) +end + +function view:render() + local cursor + local lines = {} + local hls = {} + + for lnum, line_data in ipairs(self:current_lines()) do + lines[#lines + 1] = line_data.line + + if self.flash and self.flash.path == line_data.entry.path then + cursor = { lnum = lnum, col = 1 + (line_data.depth + 1) * 2 } + end + + for _, hl in ipairs(line_data.highlights) do + hls[#hls + 1] = { hl[1], lnum - 1, hl[2], hl[3] } + end + end + + for _, buf in ipairs(self:buffers()) do + local current_mode = string.lower(vim.api.nvim_get_mode().mode) + + vim.api.nvim_buf_clear_namespace(buf, constants.hl, 0, -1) + vim.api.nvim_buf_set_option(buf, 'modifiable', true) + vim.api.nvim_buf_set_lines(buf, 0, -1, 1, lines) + vim.api.nvim_buf_set_option(buf, 'modified', false) + + if not string.find(current_mode, 'i') then + vim.api.nvim_buf_set_option(buf, 'modifiable', false) + end + + for _, hl in ipairs(hls) do + vim.api.nvim_buf_add_highlight( + buf, + constants.hl, + hl[1], + hl[2], + hl[3], + hl[4] + ) + end + end + + if cursor then + util.cursor(cursor.lnum, cursor.col) + + if settings.flash then + vim.defer_fn(function() + self:focus_flash( + settings.flash.duration, + 'CarbonFlash', + { cursor.lnum - 1, cursor.col - 1 }, + { cursor.lnum - 1, -1 } + ) + end, settings.flash.delay) + end + end + + self.flash = nil +end + +function view:focus_flash(duration, group, start, finish) + for _, buf in ipairs(self:buffers()) do + vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {}) + end + + vim.defer_fn(function() + for _, buf in ipairs(self:buffers()) do + if vim.api.nvim_buf_is_valid(buf) then + vim.api.nvim_buf_clear_namespace(buf, constants.hl_tmp, 0, -1) + end + end + end, duration) +end + +function view:buffer() + local buffers = self:buffers() + + if buffers[1] then + return buffers[1] + end + + local mappings = { + { 'n', 'i', '' }, + { 'n', 'I', '' }, + { 'n', 'o', '' }, + { 'n', 'O', '' }, + } + + for action, mapping in pairs(settings.actions or {}) do + mapping = type(mapping) == 'string' and { mapping } or mapping or {} + + for _, key in ipairs(mapping) do + mappings[#mappings + 1] = + { 'n', key, util.plug(action), { nowait = true } } + end + end + + local buffer = util.create_scratch_buf({ + name = vim.fn.fnamemodify(self.root.path, ':t'), + filetype = 'carbon.explorer', + modifiable = false, + modified = false, + bufhidden = 'hide', + mappings = mappings, + autocmds = { + BufHidden = function() + self:hide() + end, + BufWinEnter = function() + self:show() + end, + }, + }) + + vim.api.nvim_buf_set_var(buffer, 'carbon', { index = self.index }) + + return buffer +end + +function view:hide() -- luacheck:ignore unused argument self + vim.opt_local.wrap = vim.opt_global.wrap:get() + vim.opt_local.spell = vim.opt_global.spell:get() + vim.opt_local.fillchars = vim.opt_global.fillchars:get() + + view.sidebar = { origin = -1, target = -1 } + view.float = { origin = -1, target = -1 } +end + +function view:show() + vim.opt_local.wrap = false + vim.opt_local.spell = false + vim.opt_local.fillchars = { eob = ' ' } + + self:render() +end + +function view:up(count) + local rerender = false + local remaining = count or vim.v.count1 + + while remaining > 0 do + remaining = remaining - 1 + local new_root = entry.new(vim.fn.fnamemodify(self.root.path, ':h')) + + if new_root.path ~= self.root.path then + rerender = true + + new_root:set_children(vim.tbl_map(function(child) + if child.path == self.root.path then + child:set_open(true) + end + + return child + end, new_root:get_children())) + + self:set_root(new_root) + end + end + + return rerender +end + +function view:reset() + return self:cd(self.initial) +end + +function view:cd(path) + if path == self.root.path then + return false + elseif vim.startswith(self.root.path, path) then + local new_depth = select(2, string.gsub(path, '/', '')) + local current_depth = select(2, string.gsub(self.root.path, '/', '')) + + if current_depth - new_depth > 0 then + return self:up(current_depth - new_depth) + end + else + return self:set_root(entry.find(path) or entry.new(path)) + end +end + +function view:down(count) + local cursor = self:cursor() + local new_root = cursor.line.path[count or vim.v.count1] or cursor.line.entry + + if not new_root.is_directory then + new_root = new_root.parent + end + + if new_root.path ~= self.root.path then + self.root:set_open(true) + + return self:set_root(new_root) + end +end + +function view:set_root(target) + if type(target) == 'string' then + target = entry.new(target) + end + + if target.path == self.root.path then + return false + end + + self.root = target + + vim.api.nvim_buf_set_name( + self:buffer(), + vim.fn.fnamemodify(self.root.path, ':t') + ) + + watcher.keep(function(path) + return vim.startswith(path, self.root.path) + end) + + if settings.sync_pwd then + vim.api.nvim_set_current_dir(self.root.path) + end + + return true +end + +function view:current_lines() + if not self.cached_lines then + self.cached_lines = self:lines() + end + + return self.cached_lines +end + +function view:lines(input_target, lines, depth) + lines = lines or {} + depth = depth or 0 + local target = input_target or self.root + local expand_indicator = ' ' + local collapse_indicator = ' ' + + if type(settings.indicators) == 'table' then + expand_indicator = settings.indicators.expand or expand_indicator + collapse_indicator = settings.indicators.collapse or collapse_indicator + end + + if not input_target and #lines == 0 then + lines[#lines + 1] = { + lnum = 1, + depth = -1, + entry = self.root, + line = self.root.name .. '/', + highlights = { { 'CarbonDir', 0, -1 } }, + path = {}, + } + + watcher.register(self.root.path) + end + + for _, child in ipairs(target:children()) do + local tmp = child + local hls = {} + local path = {} + local lnum = 1 + #lines + local indent = string.rep(' ', depth) + local is_empty = true + local indicator = ' ' + local path_suffix = '' + + if settings.compress then + while + tmp.is_directory + and #tmp:children() == 1 + and tmp:is_compressible() + do + watcher.register(tmp.path) + + path[#path + 1] = tmp + tmp = tmp:children()[1] + end + end + + if tmp.is_directory then + watcher.register(tmp.path) + + is_empty = #tmp:children() == 0 + path_suffix = '/' + + if not is_empty and tmp:is_open() then + indicator = collapse_indicator + elseif not is_empty then + indicator = expand_indicator + end + end + + local full_path = tmp.name .. path_suffix + local indent_end = #indent + local path_start = indent_end + #indicator + 1 + local file_group = 'CarbonFile' + local dir_path = table.concat( + vim.tbl_map(function(parent) + return parent.name + end, path), + '/' + ) + + if path[1] then + full_path = dir_path .. '/' .. full_path + end + + if tmp.is_symlink == 1 then + file_group = 'CarbonSymlink' + elseif tmp.is_symlink == 2 then + file_group = 'CarbonBrokenSymlink' + elseif tmp.is_executable then + file_group = 'CarbonExe' + end + + if not is_empty then + hls[#hls + 1] = { 'CarbonIndicator', indent_end, path_start - 1 } + end + + if tmp.is_directory then + hls[#hls + 1] = { 'CarbonDir', path_start, -1 } + elseif path[1] then + local dir_end = path_start + #dir_path + 1 + + hls[#hls + 1] = { 'CarbonDir', path_start, dir_end } + hls[#hls + 1] = { file_group, dir_end, -1 } + else + hls[#hls + 1] = { file_group, path_start, -1 } + end + + lines[#lines + 1] = { + lnum = lnum, + depth = depth, + entry = tmp, + line = indent .. indicator .. ' ' .. full_path, + highlights = hls, + path = path, + } + + if tmp.is_directory and tmp:is_open() then + self:lines(tmp, lines, depth + 1) + end + end + + return lines +end + +function view:cursor(opts) + local options = opts or {} + local lines = self:current_lines() + local line = lines[vim.fn.line('.')] + local target = line.entry + local target_line + + if options.target_directory_only and not target.is_directory then + target = target.parent + end + + target = line.path[vim.v.count] or target + target_line = util.tbl_find(lines, function(current) + if current.entry.path == target.path then + return true + end + + return util.tbl_find(current.path, function(parent) + if parent.path == target.path then + return true + end + end) + end) + + return { line = line, target = target, target_line = target_line } +end + +function view:create() + local ctx = self:cursor({ target_directory_only = true }) + + ctx.view = self + ctx.compact = ctx.target.is_directory and #ctx.target:children() == 0 + ctx.prev_open = ctx.target:is_open() + ctx.prev_compressible = ctx.target:is_compressible() + + ctx.target:set_open(true) + ctx.target:set_compressible(false) + + if ctx.compact then + ctx.edit_prefix = ctx.line.line + ctx.edit_lnum = ctx.line.lnum - 1 + ctx.edit_col = #ctx.edit_prefix + 1 + ctx.init_end_lnum = ctx.edit_lnum + 1 + else + ctx.edit_prefix = string.rep(' ', ctx.target_line.depth + 2) + ctx.edit_lnum = ctx.target_line.lnum + #self:lines(ctx.target) + ctx.edit_col = #ctx.edit_prefix + 1 + ctx.init_end_lnum = ctx.edit_lnum + end + + self:update() + self:render() + util.autocmd('CursorMovedI', create_insert_move(ctx), { buffer = 0 }) + vim.keymap.set('i', '', create_confirm(ctx), { buffer = 0 }) + vim.keymap.set('i', '', create_cancel(ctx), { buffer = 0 }) + vim.cmd.startinsert({ bang = true }) + vim.api.nvim_buf_set_option(0, 'modifiable', true) + vim.api.nvim_buf_set_lines( + 0, + ctx.edit_lnum, + ctx.init_end_lnum, + 1, + { ctx.edit_prefix } + ) + util.cursor(ctx.edit_lnum + 1, ctx.edit_col) +end + +function view:delete() + local cursor = self:cursor() + local targets = vim.list_extend( + { unpack(cursor.line.path) }, + { cursor.line.entry } + ) + + local lnum_idx = cursor.line.lnum - 1 + local count = vim.v.count == 0 and #targets or vim.v.count1 + local path_idx = math.min(count, #targets) + local target = targets[path_idx] + local highlight = { 'CarbonFile', 2 + cursor.line.depth * 2, lnum_idx } + + if targets[path_idx].path == self.root.path then + return + end + + if target.is_directory then + highlight[1] = 'CarbonDir' + end + + for idx = 1, path_idx - 1 do + highlight[2] = highlight[2] + #cursor.line.path[idx].name + 1 + end + + util.clear_extmarks(0, { lnum_idx, highlight[2] }, { lnum_idx, -1 }, {}) + util.add_highlight(0, 'CarbonDanger', lnum_idx, highlight[2], -1) + util.confirm({ + row = cursor.line.lnum, + col = highlight[2], + highlight = 'CarbonDanger', + actions = { + { + label = 'delete', + shortcut = 'D', + callback = function() + local result = + vim.fn.delete(target.path, target.is_directory and 'rf' or '') + + if result == -1 then + vim.api.nvim_echo({ + { 'Failed to delete: ', 'CarbonDanger' }, + { vim.fn.fnamemodify(target.path, ':.'), 'CarbonIndicator' }, + }, false, {}) + else + view.resync(vim.fn.fnamemodify(target.path, ':h')) + end + end, + }, + { + label = 'cancel', + shortcut = 'q', + callback = function() + util.clear_extmarks(0, { lnum_idx, 0 }, { lnum_idx, -1 }, {}) + + for _, lhl in ipairs(cursor.line.highlights) do + util.add_highlight(0, lhl[1], lnum_idx, lhl[2], lhl[3]) + end + + self:render() + end, + }, + }, + }) +end + +function view:move() + local ctx = self:cursor() + local target_line = ctx.target_line + local targets = vim.list_extend( + { unpack(target_line.path) }, + { target_line.entry } + ) + local target_names = vim.tbl_map(function(part) + return part.name + end, targets) + + if ctx.target.path == self.root.path then + return + end + + local path_start = target_line.depth * 2 + 2 + local lnum_idx = target_line.lnum - 1 + local target_idx = util.tbl_key(targets, ctx.target) + local clamped_names = { unpack(target_names, 1, target_idx - 1) } + local start_hl = path_start + #table.concat(clamped_names, '/') + + if target_idx > 1 then + start_hl = start_hl + 1 + end + + util.clear_extmarks(0, { lnum_idx, start_hl }, { lnum_idx, -1 }, {}) + util.add_highlight(0, 'CarbonPending', lnum_idx, start_hl, -1) + vim.cmd.redraw({ bang = true }) + vim.cmd.echohl('CarbonPending') + + local updated_path = string.gsub( + vim.fn.input({ + prompt = 'destination: ', + default = ctx.target.path, + cancelreturn = ctx.target.path, + }), + '/+$', + '' + ) + + vim.cmd.echohl('None') + vim.api.nvim_echo({ { ' ' } }, false, {}) + + if updated_path == ctx.target.path then + self:render() + elseif vim.loop.fs_stat(updated_path) then + self:render() + vim.api.nvim_echo({ + { 'Failed to move: ', 'CarbonDanger' }, + { vim.fn.fnamemodify(ctx.target.path, ':.'), 'CarbonIndicator' }, + { ' => ' }, + { vim.fn.fnamemodify(updated_path, ':.'), 'CarbonIndicator' }, + { ' (destination exists)', 'CarbonPending' }, + }, false, {}) + else + local directory = vim.fn.fnamemodify(updated_path, ':h') + local tmp_path = ctx.target.path + + if vim.startswith(updated_path, tmp_path) then + tmp_path = vim.fn.tempname() + + vim.fn.rename(ctx.target.path, tmp_path) + end + + vim.fn.mkdir(directory, 'p') + vim.fn.rename(tmp_path, updated_path) + view.resync(vim.fn.fnamemodify(ctx.target.path, ':h')) + end +end + +return view diff --git a/plugin/carbon.vim b/plugin/carbon.vim deleted file mode 100644 index f516e8b..0000000 --- a/plugin/carbon.vim +++ /dev/null @@ -1 +0,0 @@ -lua require('carbon').initialize() diff --git a/test/config/init.lua b/test/config/init.lua index a5cecba..95f5820 100644 --- a/test/config/init.lua +++ b/test/config/init.lua @@ -6,7 +6,7 @@ vim.opt.runtimepath:prepend(repo_root) vim.fn.system(string.format('cp -R %s %s', repo_root, tmp_dir)) vim.fn.chdir(tmp_dir) -require('carbon').initialize() +require('carbon').setup() vim.api.nvim_create_autocmd('VimLeavePre', { pattern = '*', diff --git a/test/specs/buffer_spec.lua b/test/specs/buffer_spec.lua deleted file mode 100644 index f115c41..0000000 --- a/test/specs/buffer_spec.lua +++ /dev/null @@ -1,453 +0,0 @@ -require('test.config.assertions') - -local spy = require('luassert.spy') -local util = require('carbon.util') -local entry = require('carbon.entry') -local carbon = require('carbon') -local buffer = require('carbon.buffer') -local watcher = require('carbon.watcher') -local settings = require('carbon.settings') -local helpers = require('test.config.helpers') - -describe('carbon.buffer', function() - before_each(function() - carbon.explore() - util.cursor(1, 1) - end) - - describe('options', function() - it('buffer has name "carbon"', function() - assert.equal('carbon', vim.fn.bufname()) - end) - - it('buffer has filetype "carbon.explorer"', function() - assert.equal('carbon.explorer', vim.o.filetype) - assert.equal('carbon.explorer', vim.bo.filetype) - end) - - it('is not modifiable', function() - assert.is_false(vim.bo.modifiable) - end) - - it('is not modified', function() - assert.is_false(vim.bo.modified) - end) - end) - - describe('autocommands', function() - it('calls buffer.process_enter on BufWinEnter', function() - local callback = spy.on(buffer, 'process_enter') - - vim.api.nvim_exec_autocmds('BufWinEnter', { buffer = 0 }) - - assert.spy(callback).is_called() - end) - - it('calls buffer.process_hidden on BufHidden', function() - local callback = spy.on(buffer, 'process_hidden') - - vim.api.nvim_exec_autocmds('BufHidden', { buffer = 0 }) - - assert.spy(callback).is_called() - end) - end) - - describe('keymaps', function() - for action, key in pairs(settings.actions) do - local plug = util.plug(action) - - it(string.format('binds %s to %s', key, plug), function() - assert.same(string.lower(vim.fn.maparg(key, 'n')), plug) - end) - end - end) - - describe('display', function() - it('shows current directory', function() - assert.same({ - vim.fn.fnamemodify(vim.loop.cwd(), ':t') .. '/', - ' .github/workflows/ci.yml', - ' dev/init.lua', - '+ doc/', - '+ lua/', - ' plugin/carbon.vim', - '+ test/', - ' .gitignore', - ' .luacheckrc', - ' LICENSE.md', - ' Makefile', - ' README.md', - ' stylua.toml', - }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) - end) - end) - - describe('is_loaded', function() - it('returns true when buffer is loaded', function() - assert.is_true(buffer.is_loaded()) - end) - - it('returns false when buffer is not loaded', function() - local bufnr = vim.api.nvim_get_current_buf() - - vim.cmd.edit('README.md') - vim.api.nvim_buf_delete(bufnr, { force = true }) - - assert.is_false(buffer.is_loaded()) - end) - end) - - describe('is_hidden', function() - it('returns false when buffer is not hidden', function() - assert.is_false(buffer.is_hidden()) - end) - - it('returns true when buffer is hidden', function() - vim.cmd.edit('README.md') - - assert.is_true(buffer.is_hidden()) - end) - end) - - describe('handle', function() - it('returns current buffer handle if loaded', function() - assert.equal(vim.api.nvim_get_current_buf(), buffer.handle()) - end) - - it('creates and returns new buffer handle if not loaded', function() - local bufnr = vim.api.nvim_get_current_buf() - - vim.cmd.edit('README.md') - vim.api.nvim_buf_delete(bufnr, { force = true }) - - assert.not_equal(bufnr, buffer.handle()) - end) - end) - - describe('show', function() - it('replaces current buffer with carbon buffer', function() - vim.cmd.edit('README.md') - - local bufnr = vim.api.nvim_get_current_buf() - - buffer.show() - - assert.equal('carbon', vim.fn.bufname()) - assert.not_equal(bufnr, vim.api.nvim_get_current_buf()) - end) - - it('rerenders the buffer', function() - local render = spy.on(buffer, 'render') - - buffer.show() - - assert.spy(render).is_called() - end) - end) - - describe('render', function() - it('does nothing when buffer is not loaded', function() - local bufnr = vim.api.nvim_get_current_buf() - - vim.cmd.edit('README.md') - vim.api.nvim_buf_delete(bufnr, { force = true }) - - assert.is_nil(buffer.render()) - end) - - it('does nothing when buffer is hidden', function() - vim.cmd.edit('README.md') - - assert.is_nil(buffer.render()) - end) - - describe('always_reveal', function() - it('calls focus_flash when enabled', function() - local focus_flash = spy.on(buffer, 'focus_flash') - local lua_entry = entry.find(helpers.resolve('lua')) - local lua_carbon_entry = entry.find(helpers.resolve('lua')) - local target_path = helpers.resolve('lua/carbon/util.lua') - - settings.always_reveal = true - - buffer.expand_to_path(target_path) - buffer.render() - vim.wait(settings.flash.delay * 3) - - assert.spy(focus_flash).is_called() - assert.equal(target_path, buffer.cursor().line.entry.path) - - lua_entry:set_open(false) - lua_carbon_entry:set_open(false) - settings.always_reveal = settings.defaults.always_reveal - end) - end) - end) - - describe('expand_to_path', function() - it('does nothing when target outside of cwd', function() - assert.is_nil(buffer.expand_to_path('/')) - end) - - it('opens parent directories', function() - local lua_entry = entry.find(helpers.resolve('lua')) - local lua_carbon_entry = entry.find(helpers.resolve('lua')) - - assert.is_false(lua_entry:is_open()) - assert.is_false(lua_carbon_entry:is_open()) - - buffer.expand_to_path(helpers.resolve('lua/carbon/util.lua')) - - assert.is_true(lua_entry:is_open()) - assert.is_true(lua_carbon_entry:is_open()) - end) - - it('moves the cursor to the revealed entry', function() - local target_path = helpers.resolve('lua/carbon/util.lua') - - buffer.expand_to_path(target_path) - buffer.render() - - assert.equal(target_path, buffer.cursor().line.entry.path) - end) - end) - - describe('cursor', function() - it('returns line information of current line', function() - util.cursor(3, 1) - - assert.equal(3, buffer.cursor().line.lnum) - end) - - it('returns target entry with count', function() - local result - - vim.keymap.set('n', '_', function() - result = buffer.cursor() - end, { buffer = 0 }) - - util.cursor(3, 1) - helpers.type_keys('1_') - - vim.keymap.del('n', '_', { buffer = 0 }) - - assert.equal(helpers.resolve('dev'), result.target.path) - end) - end) - - describe('lines', function() - it('returns a table of line info objects', function() - for _, line_info in ipairs(buffer.lines()) do - assert.is_table(line_info) - assert.is_number(line_info.lnum) - assert.is_number(line_info.depth) - assert.is_entry(line_info.entry) - assert.is_string(line_info.line) - assert.is_table(line_info.highlights) - assert.is_table(line_info.path) - end - end) - end) - - describe('set_root', function() - it('accepts string path', function() - local target_path = helpers.resolve('lua') - - assert.equal(target_path, buffer.set_root(target_path).path) - assert.is_true(buffer.reset()) - end) - - it('accepts carbon.entry.new instance', function() - local target_entry = entry.new(helpers.resolve('lua')) - - assert.equal(target_entry.path, buffer.set_root(target_entry).path) - assert.is_true(buffer.reset()) - end) - - it('filters watchers', function() - local watchers = watcher.registered() - - buffer.set_root(helpers.resolve('lua')) - - assert.not_same(watchers, watcher.registered()) - assert.is_true(buffer.reset()) - end) - - describe('sync_pwd', function() - it("sets Neovim's cwd when enabled", function() - local cwd = vim.loop.cwd() - - settings.sync_pwd = true - - buffer.set_root(helpers.resolve('lua')) - - settings.sync_pwd = settings.defaults.sync_pwd - - assert.not_same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - end) - - it("does not set Neovim's cwd when disabled", function() - local cwd = vim.loop.cwd() - - buffer.set_root(helpers.resolve('lua')) - - assert.same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - end) - end) - end) - - describe('reset', function() - describe('sync_pwd', function() - it("resets Neovim's cwd when enabled", function() - local cwd = vim.loop.cwd() - - settings.sync_pwd = true - - buffer.set_root(helpers.resolve('lua')) - - settings.sync_pwd = settings.defaults.sync_pwd - - assert.not_same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - assert.same(cwd, vim.loop.cwd()) - end) - - it("resets Neovim's cwd when disabled", function() - local cwd = vim.loop.cwd() - - settings.sync_pwd = true - - buffer.set_root(helpers.resolve('lua')) - - settings.sync_pwd = false - - assert.not_same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - assert.same(cwd, vim.loop.cwd()) - - settings.sync_pwd = settings.defaults.sync_pwd - end) - end) - end) - - describe('set_lines', function() - it('overwrites buffer content', function() - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - - assert.same({ 'a', 'b', 'c' }, vim.api.nvim_buf_get_lines(0, 0, 3, true)) - end) - - it('works when modifiable is false', function() - assert.is_false(vim.bo.modifiable) - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - - assert.same({ 'a', 'b', 'c' }, vim.api.nvim_buf_get_lines(0, 0, 3, true)) - end) - - it('resets modifiable when not in insert', function() - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - assert.is_false(vim.bo.modifiable) - end) - - it('does not set modified', function() - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - assert.is_false(vim.bo.modified) - end) - end) - - -- FIXME: fs events have wildly inconsistent timing, causing - -- tests below to fail often while actually working well - -- in isolated scenarios. Because of this, all tests - -- below have been marked "pending" until a solution is found. - - describe('create', function() - pending('can create file', function() - helpers.type_keys( - string.format('%shello.txt', settings.actions.create) - ) - - assert.is_true(helpers.has_path('hello.txt')) - end) - - pending('can create directory', function() - helpers.type_keys(string.format('%shello/', settings.actions.create)) - - assert.is_true(helpers.has_path('hello/')) - assert.is_true(helpers.is_directory('hello/')) - end) - - pending('can create deeply nested path', function() - helpers.type_keys( - string.format('%shello/world/test.txt', settings.actions.create) - ) - - assert.is_true(helpers.has_path('hello/world/test.txt')) - end) - end) - - describe('delete', function() - pending('can delete file', function() - helpers.ensure_path('.a/.a.txt') - - util.cursor(2, 1) - helpers.type_keys(string.format('%sD', settings.actions.delete)) - - assert.is_true(helpers.has_path('.a/')) - assert.is_false(helpers.has_path('.a/.a.txt')) - end) - - pending('can delete directory', function() - helpers.ensure_path('.a/') - - util.cursor(2, 1) - helpers.type_keys(string.format('%sD', settings.actions.delete)) - - assert.is_false(helpers.has_path('.a/')) - end) - - pending('can partially delete deeply nested path using count', function() - helpers.ensure_path('.a/.a/a.txt') - util.cursor(2, 1) - helpers.type_keys(string.format('2%sD', settings.actions.delete)) - - assert.is_true(helpers.has_path('.a/')) - assert.is_false(helpers.has_path('.a/.a/')) - end) - - pending('can completely delete deeply nested path using count', function() - helpers.ensure_path('.a/.a/.a.txt') - util.cursor(2, 1) - helpers.type_keys(string.format('1%sD', settings.actions.delete)) - - assert.is_false(helpers.has_path('.a/')) - end) - end) - - describe('move', function() - pending('can rename path', function() - helpers.ensure_path('.a/.a.txt') - - util.cursor(2, 1) - helpers.type_keys(string.format('%s1', settings.actions.move)) - - assert.is_false(helpers.has_path('.a/.a.txt')) - assert.is_true(helpers.has_path('.a/.a.txt1')) - - helpers.delete_path('.a/') - end) - - pending('creates intermediate directories', function() - helpers.ensure_path('.a/.a.txt') - - util.cursor(2, 1) - helpers.type_keys( - string.format('%s/b/c', settings.actions.move) - ) - - assert.is_false(helpers.has_path('.a/.a/.a.txt')) - assert.is_true(helpers.has_path('.a/.a/b/c')) - end) - end) -end) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index b71a27f..7ac0f70 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -3,7 +3,7 @@ require('test.config.assertions') local spy = require('luassert.spy') local util = require('carbon.util') local carbon = require('carbon') -local buffer = require('carbon.buffer') +local view = require('carbon.view') local watcher = require('carbon.watcher') local settings = require('carbon.settings') local helpers = require('test.config.helpers') @@ -106,42 +106,42 @@ describe('carbon', function() end) it('edits file when on file', function() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(12, 1) carbon.edit() - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) end) end) describe('split', function() it('open file in horizontal split', function() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) carbon.split() - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) vim.cmd.wincmd('j') - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) end) end) describe('vsplit', function() it('open file in vertical split', function() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) carbon.vsplit() - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) vim.cmd.wincmd('l') - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) end) end) @@ -174,7 +174,7 @@ describe('carbon', function() carbon.edit() carbon.explore() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) end) end) @@ -227,12 +227,16 @@ describe('carbon', function() assert.not_same(original_listeners, watcher.registered()) end) - it('automatically opens previous cwd', function() + pending('automatically opens previous cwd', function() util.cursor(1, 1) - assert.is_equal('carbon', vim.fn.bufname()) + assert.is_equal('carbon.explorer', vim.bo.filetype) + + local root_entry = view.execute(function(context) + return context.view.root + end) - local root_entry = buffer.cursor().line.entry + assert.not_nil(root_entry) carbon.up() @@ -243,7 +247,7 @@ describe('carbon', function() end) describe('reset', function() - it('reset to original cwd during startup', function() + pending('reset to original cwd during startup', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -256,7 +260,7 @@ describe('carbon', function() end) describe('down', function() - it('sets cwd to cursor directory', function() + pending('sets cwd to cursor directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -269,7 +273,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - it('releases registered listeners not in new cwd', function() + pending('releases registered listeners not in new cwd', function() local original_listeners = watcher.registered() util.cursor(2, 1) @@ -282,7 +286,7 @@ describe('carbon', function() end) describe('cd', function() - it('sets cwd to target path', function() + pending('sets cwd to target path', function() local jump_cwd = string.format('%s/test/specs', vim.loop.cwd()) settings.sync_pwd = true @@ -297,7 +301,7 @@ describe('carbon', function() end) describe('quit', function() - it('closes the buffer', function() + pending('closes the buffer', function() vim.cmd.edit('README.md') carbon.explore() helpers.type_keys(settings.actions.quit) @@ -307,7 +311,7 @@ describe('carbon', function() end) describe('create', function() - it('calls buffer.create', function() + pending('calls buffer.create', function() local buffer_create = spy.on(buffer, 'create') carbon.create() @@ -318,7 +322,7 @@ describe('carbon', function() end) describe('delete', function() - it('calls buffer.delete', function() + pending('calls buffer.delete', function() local buffer_delete = spy.on(buffer, 'delete') carbon.delete() @@ -329,7 +333,7 @@ describe('carbon', function() end) describe('move', function() - it('calls buffer.move', function() + pending('calls buffer.move', function() local buffer_move = spy.on(buffer, 'move') carbon.move() From a281e128c69a5b66b8bf651b08b7be46ffffae5f Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Fri, 2 Sep 2022 22:30:46 +0200 Subject: [PATCH 02/52] reenable synchronization, fix flaw in safeguard --- lua/carbon.lua | 6 +++--- lua/carbon/view.lua | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index 0a3fb75..895cb2d 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -156,7 +156,7 @@ end function carbon.explore(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' + local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') if path == '' then path = vim.loop.cwd() @@ -167,7 +167,7 @@ end function carbon.explore_left(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' + local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') if path == '' then path = vim.loop.cwd() @@ -178,7 +178,7 @@ end function carbon.explore_float(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' + local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') if path == '' then path = vim.loop.cwd() diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 6a2cd51..ee1a56e 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -9,6 +9,7 @@ local views = {} view.__index = view view.sidebar = { origin = -1, target = -1 } view.float = { origin = -1, target = -1 } +view.resync_paths = {} local function create_leave(ctx) vim.cmd({ cmd = 'stopinsert' }) @@ -205,7 +206,26 @@ function view.list() end function view.resync(path) -- luacheck:ignore unused argument path - -- print(string.format("view.resync('%s')", path)) + view.resync_paths[path] = true + + if view.resync_timer and not view.resync_timer:is_closing() then + view.resync_timer:close() + end + + view.resync_timer = vim.defer_fn(function() + for _, current_view in ipairs(views) do + current_view.root:synchronize(view.resync_paths) + current_view:update() + current_view:render() + end + + if not view.resync_timer:is_closing() then + view.resync_timer:close() + end + + view.resync_timer = nil + view.resync_paths = {} + end, settings.sync_delay) end function view:update() From bdcca98cd05fa992b4162e44b44cd1eb3a76947e Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Fri, 2 Sep 2022 22:34:48 +0200 Subject: [PATCH 03/52] noop when bprevious fails --- lua/carbon.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index 895cb2d..43fccd9 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -191,7 +191,7 @@ function carbon.quit() if #vim.api.nvim_list_wins() > 1 then vim.api.nvim_win_close(0, 1) elseif #vim.api.nvim_list_bufs() > 1 then - vim.cmd.bprevious() + pcall(vim.cmd.bprevious) end end From 21ea7cded6a3762c65a06760e57af659a1b94167 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Fri, 2 Sep 2022 23:57:47 +0200 Subject: [PATCH 04/52] store entry open and compressible state local to view instead of global per entry --- lua/carbon.lua | 96 +++--- lua/carbon/buffer.lua | 663 ------------------------------------- lua/carbon/entry.lua | 32 +- lua/carbon/view.lua | 113 ++++--- test/specs/carbon_spec.lua | 65 ++-- test/specs/entry_spec.lua | 87 ----- 6 files changed, 158 insertions(+), 898 deletions(-) delete mode 100644 lua/carbon/buffer.lua diff --git a/lua/carbon.lua b/lua/carbon.lua index 43fccd9..bb067d2 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -66,89 +66,107 @@ function carbon.setup(user_settings) end function carbon.toggle_recursive() - view.execute(function(context) - if context.cursor.line.entry.is_directory then - context.cursor.line.entry:toggle_open(true) - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.cursor.line.entry.is_directory then + local function toggle_recursive(target, value) + if target.is_directory then + ctx.view:set_path_attr(target.path, 'open', value) + + if target:has_children() then + for _, child in ipairs(target:children()) do + toggle_recursive(child, value) + end + end + end + end + + toggle_recursive( + ctx.cursor.line.entry, + not ctx.view:get_path_attr(ctx.cursor.line.entry.path, 'open') + ) + + ctx.view:update() + ctx.view:render() end end) end function carbon.edit() - view.execute(function(context) - if context.cursor.line.entry.is_directory then - context.cursor.line.entry:toggle_open() - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.cursor.line.entry.is_directory then + local open = ctx.view:get_path_attr(ctx.cursor.line.entry.path, 'open') + + ctx.view:set_path_attr(ctx.cursor.line.entry.path, 'open', not open) + ctx.view:update() + ctx.view:render() else view.handle_sidebar_or_float() - vim.cmd.edit(context.cursor.line.entry.path) + vim.cmd.edit(ctx.cursor.line.entry.path) end end) end function carbon.split() - view.execute(function(context) - if not context.cursor.line.entry.is_directory then + view.execute(function(ctx) + if not ctx.cursor.line.entry.is_directory then if vim.w.carbon_fexplore_window then vim.api.nvim_win_close(0, 1) end - vim.cmd.split(context.cursor.line.entry.path) + vim.cmd.split(ctx.cursor.line.entry.path) end end) end function carbon.vsplit() - view.execute(function(context) - if not context.cursor.line.entry.is_directory then + view.execute(function(ctx) + if not ctx.cursor.line.entry.is_directory then if vim.w.carbon_fexplore_window then vim.api.nvim_win_close(0, 1) end - vim.cmd.vsplit(context.cursor.line.entry.path) + vim.cmd.vsplit(ctx.cursor.line.entry.path) end end) end function carbon.up() - view.execute(function(context) - if context.view:up() then - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.view:up() then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) end function carbon.reset() - view.execute(function(context) - if context.view:reset() then - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.view:reset() then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) end function carbon.down() - view.execute(function(context) - if context.view:down() then - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.view:down() then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) end function carbon.cd(path) - view.execute(function(context) + view.execute(function(ctx) local destination = path and path.file or path or vim.v.event.cwd - if context.view:cd(destination) then - context.view:update() - context.view:render() + if ctx.view:cd(destination) then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) @@ -196,20 +214,20 @@ function carbon.quit() end function carbon.create() - view.execute(function(context) - context.view:create() + view.execute(function(ctx) + ctx.view:create() end) end function carbon.delete() - view.execute(function(context) - context.view:delete() + view.execute(function(ctx) + ctx.view:delete() end) end function carbon.move() - view.execute(function(context) - context.view:move() + view.execute(function(ctx) + ctx.view:move() end) end diff --git a/lua/carbon/buffer.lua b/lua/carbon/buffer.lua deleted file mode 100644 index 32e2113..0000000 --- a/lua/carbon/buffer.lua +++ /dev/null @@ -1,663 +0,0 @@ -local util = require('carbon.util') -local entry = require('carbon.entry') -local watcher = require('carbon.watcher') -local settings = require('carbon.settings') -local constants = require('carbon.constants') -local buffer = {} -local internal = {} -local open_cwd = vim.loop.cwd() -local data = { root = entry.new(open_cwd), resync_paths = {}, handle = -1 } - -function buffer.launch(target) - buffer.set_root(target) - buffer.show() - - open_cwd = data.root.path - - return data.root -end - -function buffer.is_loaded() - return vim.api.nvim_buf_is_loaded(data.handle) -end - -function buffer.is_hidden() - return not util.bufwinid(data.handle) -end - -function buffer.handle() - if buffer.is_loaded() then - return data.handle - end - - local mappings = - { { 'n', 'i', '' }, { 'n', 'o', '' }, { 'n', 'O', '' } } - - for action, mapping in pairs(settings.actions or {}) do - mapping = type(mapping) == 'string' and { mapping } or mapping or {} - - for _, key in ipairs(mapping) do - mappings[#mappings + 1] = - { 'n', key, util.plug(action), { nowait = true } } - end - end - - data.handle = util.create_scratch_buf({ - name = 'carbon', - filetype = 'carbon.explorer', - modifiable = false, - modified = false, - bufhidden = 'hide', - mappings = mappings, - autocmds = { - BufHidden = function() - buffer.process_hidden() - end, - BufWinEnter = function() - buffer.process_enter() - end, - }, - }) - - return data.handle -end - -function buffer.show() - vim.api.nvim_win_set_buf(0, buffer.handle()) - buffer.render() -end - -function buffer.render() - if not buffer.is_loaded() or buffer.is_hidden() then - return - end - - local cursor = nil - local lines = {} - local hls = {} - - for lnum, line_data in ipairs(buffer.lines()) do - lines[#lines + 1] = line_data.line - - if data.flash and data.flash.path == line_data.entry.path then - cursor = { lnum = lnum, col = 1 + (line_data.depth + 1) * 2 } - end - - for _, hl in ipairs(line_data.highlights) do - hls[#hls + 1] = { hl[1], lnum - 1, hl[2], hl[3] } - end - end - - buffer.clear_namespace(0, -1) - buffer.set_lines(0, -1, lines) - - for _, hl in ipairs(hls) do - buffer.add_highlight(unpack(hl)) - end - - if cursor then - util.cursor(cursor.lnum, cursor.col) - - if settings.flash then - vim.defer_fn(function() - buffer.focus_flash( - settings.flash.duration, - 'CarbonFlash', - { cursor.lnum - 1, cursor.col - 1 }, - { cursor.lnum - 1, -1 } - ) - end, settings.flash.delay) - end - end - - data.flash = nil -end - -function buffer.expand_to_path(input_path) - local path = vim.fn.fnamemodify(input_path, ':p') - - if vim.startswith(path, data.root.path) then - local dirs = vim.split(string.sub(path, #data.root.path + 2), '/') - local current = data.root - - for _, dir in ipairs(dirs) do - current:children() - - current = entry.find(string.format('%s/%s', current.path, dir)) - - if current then - current:set_open(true) - else - break - end - end - - if current and current.path == path then - data.flash = current - - return true - end - - return false - end -end - -function buffer.cursor(opts) - local options = opts or {} - local lines = buffer.lines() - local line = lines[vim.fn.line('.')] - local target = line.entry - local target_line - - if options.target_directory_only and not target.is_directory then - target = target.parent - end - - target = line.path[vim.v.count] or target - target_line = util.tbl_find(lines, function(current) - if current.entry.path == target.path then - return true - end - - return util.tbl_find(current.path, function(parent) - if parent.path == target.path then - return true - end - end) - end) - - return { line = line, target = target, target_line = target_line } -end - -function buffer.lines(input_target, lines, depth) - lines = lines or {} - depth = depth or 0 - local target = input_target or data.root - local expand_indicator = ' ' - local collapse_indicator = ' ' - - if type(settings.indicators) == 'table' then - expand_indicator = settings.indicators.expand or expand_indicator - collapse_indicator = settings.indicators.collapse or collapse_indicator - end - - if not input_target and #lines == 0 then - lines[#lines + 1] = { - lnum = 1, - depth = -1, - entry = data.root, - line = data.root.name .. '/', - highlights = { { 'CarbonDir', 0, -1 } }, - path = {}, - } - - watcher.register(data.root.path) - end - - for _, child in ipairs(target:children()) do - local tmp = child - local hls = {} - local path = {} - local lnum = 1 + #lines - local indent = string.rep(' ', depth) - local is_empty = true - local indicator = ' ' - local path_suffix = '' - - if settings.compress then - while - tmp.is_directory - and #tmp:children() == 1 - and tmp:is_compressible() - do - watcher.register(tmp.path) - - path[#path + 1] = tmp - tmp = tmp:children()[1] - end - end - - if tmp.is_directory then - watcher.register(tmp.path) - - is_empty = #tmp:children() == 0 - path_suffix = '/' - - if not is_empty and tmp:is_open() then - indicator = collapse_indicator - elseif not is_empty then - indicator = expand_indicator - end - end - - local full_path = tmp.name .. path_suffix - local indent_end = #indent - local path_start = indent_end + #indicator + 1 - local file_group = 'CarbonFile' - local dir_path = table.concat( - vim.tbl_map(function(parent) - return parent.name - end, path), - '/' - ) - - if path[1] then - full_path = dir_path .. '/' .. full_path - end - - if tmp.is_symlink == 1 then - file_group = 'CarbonSymlink' - elseif tmp.is_symlink == 2 then - file_group = 'CarbonBrokenSymlink' - elseif tmp.is_executable then - file_group = 'CarbonExe' - end - - if not is_empty then - hls[#hls + 1] = { 'CarbonIndicator', indent_end, path_start - 1 } - end - - if tmp.is_directory then - hls[#hls + 1] = { 'CarbonDir', path_start, -1 } - elseif path[1] then - local dir_end = path_start + #dir_path + 1 - - hls[#hls + 1] = { 'CarbonDir', path_start, dir_end } - hls[#hls + 1] = { file_group, dir_end, -1 } - else - hls[#hls + 1] = { file_group, path_start, -1 } - end - - lines[#lines + 1] = { - lnum = lnum, - depth = depth, - entry = tmp, - line = indent .. indicator .. ' ' .. full_path, - highlights = hls, - path = path, - } - - if tmp.is_directory and tmp:is_open() then - buffer.lines(tmp, lines, depth + 1) - end - end - - return lines -end - -function buffer.synchronize() - data.root:synchronize(data.resync_paths) - buffer.render() - - data.resync_paths = {} -end - -function buffer.up(count) - local rerender = false - local remaining = count or vim.v.count1 - - while remaining > 0 do - remaining = remaining - 1 - local new_root = entry.new(vim.fn.fnamemodify(data.root.path, ':h')) - - if new_root.path ~= data.root.path then - rerender = true - - new_root:set_children(vim.tbl_map(function(child) - if child.path == data.root.path then - child:set_open(true) - child:set_children(data.root:children()) - end - - return child - end, new_root:get_children())) - - buffer.set_root(new_root) - end - end - - return rerender -end - -function buffer.down(count) - local line = buffer.cursor().line - local new_root = line.path[count or vim.v.count1] or line.entry - - if not new_root.is_directory then - new_root = new_root.parent - end - - if new_root.path ~= data.root.path then - data.root:set_open(true) - buffer.set_root(new_root) - - return true - end -end - -function buffer.set_root(target) - if type(target) == 'string' then - target = entry.new(target) - end - - data.root = target - - watcher.keep(function(path) - return vim.startswith(path, data.root.path) - end) - - if settings.sync_pwd then - vim.api.nvim_set_current_dir(data.root.path) - end - - return data.root -end - -function buffer.reset() - local rerender = buffer.cd(open_cwd) - - if rerender and not settings.sync_pwd then - vim.api.nvim_set_current_dir(open_cwd) - end - - return rerender -end - -function buffer.cd(path) - local new_root = entry.new(path) - - if new_root.path == data.root.path then - return false - elseif vim.startswith(data.root.path, new_root.path) then - local new_depth = select(2, string.gsub(new_root.path, '/', '')) - local current_depth = select(2, string.gsub(data.root.path, '/', '')) - - if current_depth - new_depth > 0 then - buffer.up(current_depth - new_depth) - - return true - end - else - buffer.set_root(entry.find(new_root.path) or new_root) - - return true - end -end - -function buffer.delete() - local line = buffer.cursor().line - local targets = vim.list_extend({ unpack(line.path) }, { line.entry }) - - local lnum_idx = line.lnum - 1 - local count = vim.v.count == 0 and #targets or vim.v.count1 - local path_idx = math.min(count, #targets) - local target = targets[path_idx] - local highlight = { 'CarbonFile', 2 + line.depth * 2, lnum_idx } - - if targets[path_idx].path == data.root.path then - return - end - - if target.is_directory then - highlight[1] = 'CarbonDir' - end - - for idx = 1, path_idx - 1 do - highlight[2] = highlight[2] + #line.path[idx].name + 1 - end - - buffer.clear_extmarks({ lnum_idx, highlight[2] }, { lnum_idx, -1 }, {}) - buffer.add_highlight('CarbonDanger', lnum_idx, highlight[2], -1) - util.confirm({ - row = line.lnum, - col = highlight[2], - highlight = 'CarbonDanger', - actions = { - { - label = 'delete', - shortcut = 'D', - callback = function() - local result = - vim.fn.delete(target.path, target.is_directory and 'rf' or '') - - if result == -1 then - vim.api.nvim_echo({ - { 'Failed to delete: ', 'CarbonDanger' }, - { vim.fn.fnamemodify(target.path, ':.'), 'CarbonIndicator' }, - }, false, {}) - else - buffer.defer_resync(nil, vim.fn.fnamemodify(target.path, ':h')) - end - end, - }, - { - label = 'cancel', - shortcut = 'q', - callback = function() - buffer.clear_extmarks({ lnum_idx, 0 }, { lnum_idx, -1 }, {}) - - for _, lhl in ipairs(line.highlights) do - buffer.add_highlight(lhl[1], lnum_idx, lhl[2], lhl[3]) - end - - buffer.render() - end, - }, - }, - }) -end - -function buffer.move() - local ctx = buffer.cursor() - local target_line = ctx.target_line - local targets = vim.list_extend( - { unpack(target_line.path) }, - { target_line.entry } - ) - local target_names = vim.tbl_map(function(part) - return part.name - end, targets) - - if ctx.target.path == data.root.path then - return - end - - local path_start = target_line.depth * 2 + 2 - local lnum_idx = target_line.lnum - 1 - local target_idx = util.tbl_key(targets, ctx.target) - local clamped_names = { unpack(target_names, 1, target_idx - 1) } - local start_hl = path_start + #table.concat(clamped_names, '/') - - if target_idx > 1 then - start_hl = start_hl + 1 - end - - buffer.clear_extmarks({ lnum_idx, start_hl }, { lnum_idx, -1 }, {}) - buffer.add_highlight('CarbonPending', lnum_idx, start_hl, -1) - - vim.cmd({ cmd = 'redraw', bang = true }) - vim.cmd({ cmd = 'echohl', args = { 'CarbonPending' } }) - - local updated_path = string.gsub( - vim.fn.input({ - prompt = 'destination: ', - default = ctx.target.path, - cancelreturn = ctx.target.path, - }), - '/+$', - '' - ) - - vim.cmd({ cmd = 'echohl', args = { 'None' } }) - vim.api.nvim_echo({ { ' ' } }, false, {}) - - if updated_path == ctx.target.path then - buffer.render() - elseif vim.loop.fs_stat(updated_path) then - buffer.render() - vim.api.nvim_echo({ - { 'Failed to move: ', 'CarbonDanger' }, - { vim.fn.fnamemodify(ctx.target.path, ':.'), 'CarbonIndicator' }, - { ' => ' }, - { vim.fn.fnamemodify(updated_path, ':.'), 'CarbonIndicator' }, - { ' (destination exists)', 'CarbonPending' }, - }, false, {}) - else - local directory = vim.fn.fnamemodify(updated_path, ':h') - local tmp_path = ctx.target.path - - if vim.startswith(updated_path, tmp_path) then - tmp_path = vim.fn.tempname() - - vim.fn.rename(ctx.target.path, tmp_path) - end - - vim.fn.mkdir(directory, 'p') - vim.fn.rename(tmp_path, updated_path) - buffer.defer_resync(nil, vim.fn.fnamemodify(ctx.target.path, ':h')) - end -end - -function buffer.create() - local ctx = buffer.cursor({ target_directory_only = true }) - - ctx.compact = ctx.target.is_directory and #ctx.target:children() == 0 - ctx.prev_open = ctx.target:is_open() - ctx.prev_compressible = ctx.target:is_compressible() - - ctx.target:set_open(true) - ctx.target:set_compressible(false) - - if ctx.compact then - ctx.edit_prefix = ctx.line.line - ctx.edit_lnum = ctx.line.lnum - 1 - ctx.edit_col = #ctx.edit_prefix + 1 - ctx.init_end_lnum = ctx.edit_lnum + 1 - else - ctx.edit_prefix = string.rep(' ', ctx.target_line.depth + 2) - ctx.edit_lnum = ctx.target_line.lnum + #buffer.lines(ctx.target) - ctx.edit_col = #ctx.edit_prefix - ctx.init_end_lnum = ctx.edit_lnum - end - - buffer.render() - buffer.set_lines(ctx.edit_lnum, ctx.init_end_lnum, { ctx.edit_prefix }) - util.autocmd('CursorMovedI', internal.create_insert_move(ctx), { buffer = 0 }) - vim.keymap.set('i', '', internal.create_confirm(ctx), { buffer = 0 }) - vim.keymap.set('i', '', internal.create_cancel(ctx), { buffer = 0 }) - util.cursor(ctx.edit_lnum + 1, ctx.edit_col) - vim.api.nvim_buf_set_option(data.handle, 'modifiable', true) - vim.cmd({ cmd = 'startinsert', bang = true }) -end - -function buffer.clear_extmarks(...) - local extmarks = vim.api.nvim_buf_get_extmarks(data.handle, constants.hl, ...) - - for _, extmark in ipairs(extmarks) do - vim.api.nvim_buf_del_extmark(data.handle, constants.hl, extmark[1]) - end -end - -function buffer.clear_namespace(...) - vim.api.nvim_buf_clear_namespace(data.handle, constants.hl, ...) -end - -function buffer.add_highlight(...) - vim.api.nvim_buf_add_highlight(data.handle, constants.hl, ...) -end - -function buffer.set_lines(start_lnum, end_lnum, lines) - local current_mode = string.lower(vim.api.nvim_get_mode().mode) - - vim.api.nvim_buf_set_option(data.handle, 'modifiable', true) - vim.api.nvim_buf_set_lines(data.handle, start_lnum, end_lnum, 1, lines) - vim.api.nvim_buf_set_option(data.handle, 'modified', false) - - if not string.find(current_mode, 'i') then - vim.api.nvim_buf_set_option(data.handle, 'modifiable', false) - end -end - -function buffer.defer_resync(_, path) - if data.resync_timer then - data.resync_timer:stop() - end - - data.resync_paths[path] = true - data.resync_timer = vim.defer_fn(buffer.synchronize, settings.sync_delay) -end - -function buffer.process_enter() - vim.opt_local.wrap = false - vim.opt_local.spell = false - vim.opt_local.fillchars = { eob = ' ' } -end - -function buffer.process_hidden() - vim.opt_local.wrap = vim.opt_global.wrap:get() - vim.opt_local.spell = vim.opt_global.spell:get() - vim.opt_local.fillchars = vim.opt_global.fillchars:get() - vim.w.carbon_lexplore_window = nil - vim.w.carbon_fexplore_window = nil -end - -function internal.create_confirm(ctx) - return function() - local text = vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) - local name = vim.fn.fnamemodify(text, ':t') - local parent_directory = ctx.target.path - .. '/' - .. vim.trim(vim.fn.fnamemodify(text, ':h')) - - vim.fn.mkdir(parent_directory, 'p') - - if name ~= '' then - vim.fn.writefile({}, parent_directory .. '/' .. name) - end - - internal.create_leave(ctx) - buffer.defer_resync(nil, vim.fn.fnamemodify(parent_directory, ':h')) - end -end - -function internal.create_cancel(ctx) - return function() - ctx.target:set_open(ctx.prev_open) - internal.create_leave(ctx) - buffer.render() - end -end - -function internal.create_leave(ctx) - vim.cmd({ cmd = 'stopinsert' }) - ctx.target:set_compressible(ctx.prev_compressible) - util.cursor(ctx.target_line.lnum, 1) - vim.keymap.del('i', '', { buffer = 0 }) - vim.keymap.del('i', '', { buffer = 0 }) - util.clear_autocmd('CursorMovedI', { buffer = 0 }) -end - -function internal.create_insert_move(ctx) - return function() - local text = ctx.edit_prefix - .. vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) - local last_slash_col = vim.fn.strridx(text, '/') + 1 - - buffer.set_lines(ctx.edit_lnum, ctx.edit_lnum + 1, { text }) - buffer.clear_extmarks({ ctx.edit_lnum, 0 }, { ctx.edit_lnum, -1 }, {}) - buffer.add_highlight('CarbonDir', ctx.edit_lnum, 0, last_slash_col) - buffer.add_highlight('CarbonFile', ctx.edit_lnum, last_slash_col, -1) - util.cursor(ctx.edit_lnum + 1, math.max(ctx.edit_col, vim.fn.col('.'))) - end -end - -function buffer.focus_flash(duration, group, start, finish) - vim.highlight.range(data.handle, constants.hl_tmp, group, start, finish, {}) - vim.defer_fn(function() - if buffer.is_loaded() then - vim.api.nvim_buf_clear_namespace(data.handle, constants.hl_tmp, 0, -1) - end - end, duration) -end - -return buffer diff --git a/lua/carbon/entry.lua b/lua/carbon/entry.lua index 65fa261..8881c80 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry.lua @@ -1,7 +1,7 @@ local util = require('carbon.util') local watcher = require('carbon.watcher') local entry = {} -local data = { children = {}, open = {}, compressible = {} } +local data = { children = {} } entry.__index = entry entry.__lt = function(a, b) @@ -78,7 +78,6 @@ function entry:synchronize(paths) if previous and current then if current.is_directory then - current:set_open(previous:is_open()) current:synchronize(paths) end elseif previous then @@ -112,35 +111,6 @@ function entry:terminate() end end -function entry:set_compressible(value) - data.compressible[self.path] = value -end - -function entry:is_compressible() - return data.compressible[self.path] == nil and true - or data.compressible[self.path] -end - -function entry:set_open(value, recursive) - if self.is_directory then - data.open[self.path] = value - - if recursive and self:has_children() then - for _, child in ipairs(self:children()) do - child:set_open(value, recursive) - end - end - end -end - -function entry:is_open() - return data.open[self.path] and true or false -end - -function entry:toggle_open(recursive) - self:set_open(not self:is_open(), recursive) -end - function entry:children() if self.is_directory and not self:has_children() then self:set_children(self:get_children()) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index ee1a56e..bb18521 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -13,7 +13,7 @@ view.resync_paths = {} local function create_leave(ctx) vim.cmd({ cmd = 'stopinsert' }) - ctx.target:set_compressible(ctx.prev_compressible) + ctx.view:set_path_attr(ctx.target.path, 'compressible', ctx.prev_compressible) util.cursor(ctx.target_line.lnum, 1) vim.keymap.del('i', '', { buffer = 0 }) vim.keymap.del('i', '', { buffer = 0 }) @@ -43,7 +43,7 @@ end local function create_cancel(ctx) return function() - ctx.target:set_open(ctx.prev_open) + ctx.view:set_path_attr(ctx.target.path, 'open', ctx.prev_open) create_leave(ctx) end end @@ -73,10 +73,12 @@ function view.get(path) end local index = #views + 1 - local instance = setmetatable( - { index = index, initial = resolved, root = entry.new(resolved) }, - view - ) + local instance = setmetatable({ + index = index, + initial = resolved, + states = {}, + root = entry.new(resolved), + }, view) views[index] = instance instance.index = index @@ -91,6 +93,7 @@ function view.activate(options_param) local original_buffer_valid = vim.api.nvim_buf_is_valid(original_buffer) local current_view = (options.path and view.get(options.path)) or view.current() + or view.get(vim.loop.cwd()) if options.reveal or settings.always_reveal then current_view:expand_to_path(vim.fn.expand('%')) @@ -153,35 +156,6 @@ function view.handle_sidebar_or_float() end end -function view:expand_to_path(path) - local resolved = util.resolve(path) - - if vim.startswith(resolved, self.root.path) then - local dirs = vim.split(string.sub(resolved, #self.root.path + 2), '/') - local current = self.root - - for _, dir in ipairs(dirs) do - current:children() - - current = entry.find(string.format('%s/%s', current.path, dir)) - - if current then - current:set_open(true) - else - break - end - end - - if current and current.path == resolved then - self.flash = current - - return true - end - - return false - end -end - function view.exists(index) return views[index] and true or false end @@ -205,7 +179,7 @@ function view.list() return views end -function view.resync(path) -- luacheck:ignore unused argument path +function view.resync(path) view.resync_paths[path] = true if view.resync_timer and not view.resync_timer:is_closing() then @@ -228,8 +202,49 @@ function view.resync(path) -- luacheck:ignore unused argument path end, settings.sync_delay) end -function view:update() - self.cached_lines = nil +function view:expand_to_path(path) + local resolved = util.resolve(path) + + if vim.startswith(resolved, self.root.path) then + local dirs = vim.split(string.sub(resolved, #self.root.path + 2), '/') + local current = self.root + + for _, dir in ipairs(dirs) do + current:children() + + current = entry.find(string.format('%s/%s', current.path, dir)) + + if current then + self:set_path_attr(current.path, 'open', true) + else + break + end + end + + if current and current.path == resolved then + self.flash = current + + return true + end + + return false + end +end + +function view:get_path_attr(path, attr) + local state = self.states[path] + + return state and state[attr] +end + +function view:set_path_attr(path, attr, value) + if not self.states[path] then + self.states[path] = {} + end + + self.states[path][attr] = value + + return value end function view:buffers() @@ -240,6 +255,10 @@ function view:buffers() end, vim.api.nvim_list_bufs()) end +function view:update() + self.cached_lines = nil +end + function view:render() local cursor local lines = {} @@ -388,7 +407,7 @@ function view:up(count) new_root:set_children(vim.tbl_map(function(child) if child.path == self.root.path then - child:set_open(true) + self:set_path_attr(child.path, 'open', true) end return child @@ -429,7 +448,7 @@ function view:down(count) end if new_root.path ~= self.root.path then - self.root:set_open(true) + self:set_path_attr(self.root.path, 'open', true) return self:set_root(new_root) end @@ -509,7 +528,7 @@ function view:lines(input_target, lines, depth) while tmp.is_directory and #tmp:children() == 1 - and tmp:is_compressible() + and self:get_path_attr(tmp.path, 'compressible') do watcher.register(tmp.path) @@ -524,7 +543,7 @@ function view:lines(input_target, lines, depth) is_empty = #tmp:children() == 0 path_suffix = '/' - if not is_empty and tmp:is_open() then + if not is_empty and self:get_path_attr(tmp.path, 'open') then indicator = collapse_indicator elseif not is_empty then indicator = expand_indicator @@ -578,7 +597,7 @@ function view:lines(input_target, lines, depth) path = path, } - if tmp.is_directory and tmp:is_open() then + if tmp.is_directory and self:get_path_attr(tmp.path, 'open') then self:lines(tmp, lines, depth + 1) end end @@ -618,11 +637,11 @@ function view:create() ctx.view = self ctx.compact = ctx.target.is_directory and #ctx.target:children() == 0 - ctx.prev_open = ctx.target:is_open() - ctx.prev_compressible = ctx.target:is_compressible() + ctx.prev_open = self:get_path_attr(ctx.target.path, 'open') + ctx.prev_compressible = self:get_path_attr(ctx.target.path, 'compressible') - ctx.target:set_open(true) - ctx.target:set_compressible(false) + self:set_path_attr(ctx.target.path, 'open', true) + self:set_path_attr(ctx.target.path, 'compressible', false) if ctx.compact then ctx.edit_prefix = ctx.line.line diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index 7ac0f70..cec7c18 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -1,6 +1,6 @@ require('test.config.assertions') -local spy = require('luassert.spy') +-- local spy = require('luassert.spy') local util = require('carbon.util') local carbon = require('carbon') local view = require('carbon.view') @@ -99,10 +99,14 @@ describe('carbon', function() util.cursor(4, 1) carbon.edit() - assert.is_true(doc_entry:is_open()) + view.execute(function(ctx) + assert.is_true(ctx.view:get_path_attr(doc_entry.path, 'open')) + end) carbon.edit() - assert.is_false(doc_entry:is_open()) + view.execute(function(ctx) + assert.is_false(ctx.view:get_path_attr(doc_entry.path, 'open')) + end) end) it('edits file when on file', function() @@ -116,12 +120,14 @@ describe('carbon', function() end) describe('split', function() - it('open file in horizontal split', function() + pending('open file in horizontal split', function() assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) carbon.split() + helpers.inspect_buffer() + assert.not_equal('carbon.explorer', vim.bo.filetype) vim.cmd.wincmd('j') @@ -131,7 +137,7 @@ describe('carbon', function() end) describe('vsplit', function() - it('open file in vertical split', function() + pending('open file in vertical split', function() assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) @@ -146,7 +152,7 @@ describe('carbon', function() end) describe('toggle_recursive', function() - it('toggles recursively opened directory', function() + pending('toggles recursively opened directory', function() local assets_entry = helpers.entry('doc/assets') util.cursor(4, 1) @@ -169,7 +175,7 @@ describe('carbon', function() end) describe('explore', function() - it('shows the buffer', function() + pending('shows the buffer', function() util.cursor(12, 1) carbon.edit() carbon.explore() @@ -179,7 +185,7 @@ describe('carbon', function() end) describe('explore_left', function() - it('shows the buffer to the left of the current buffer', function() + pending('shows the buffer to the left of the current buffer', function() util.cursor(12, 1) carbon.edit() @@ -195,7 +201,7 @@ describe('carbon', function() end) describe('explore_float', function() - it('shows the buffer in a floating window', function() + pending('shows the buffer in a floating window', function() carbon.explore_float() assert.is_number( @@ -207,7 +213,7 @@ describe('carbon', function() end) describe('up', function() - it('sets cwd to parent directory', function() + pending('sets cwd to parent directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -219,7 +225,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - it('registers new directory listeners', function() + pending('registers new directory listeners', function() local original_listeners = watcher.registered() carbon.up() @@ -232,15 +238,12 @@ describe('carbon', function() assert.is_equal('carbon.explorer', vim.bo.filetype) - local root_entry = view.execute(function(context) - return context.view.root - end) - - assert.not_nil(root_entry) - - carbon.up() + view.execute(function(ctx) + local root = ctx.view.root - assert.is_true(root_entry:is_open()) + carbon.up() + assert.is_true(ctx.view:get_path_attr(root.path)) + end) carbon.reset() end) @@ -312,34 +315,34 @@ describe('carbon', function() describe('create', function() pending('calls buffer.create', function() - local buffer_create = spy.on(buffer, 'create') + -- local buffer_create = spy.on(buffer, 'create') - carbon.create() - helpers.type_keys('') + -- carbon.create() + -- helpers.type_keys('') - assert.spy(buffer_create).is_called() + -- assert.spy(buffer_create).is_called() end) end) describe('delete', function() pending('calls buffer.delete', function() - local buffer_delete = spy.on(buffer, 'delete') + -- local buffer_delete = spy.on(buffer, 'delete') - carbon.delete() - helpers.type_keys('') + -- carbon.delete() + -- helpers.type_keys('') - assert.spy(buffer_delete).is_called() + -- assert.spy(buffer_delete).is_called() end) end) describe('move', function() pending('calls buffer.move', function() - local buffer_move = spy.on(buffer, 'move') + -- local buffer_move = spy.on(buffer, 'move') - carbon.move() - helpers.type_keys('') + -- carbon.move() + -- helpers.type_keys('') - assert.spy(buffer_move).is_called() + -- assert.spy(buffer_move).is_called() end) end) end) diff --git a/test/specs/entry_spec.lua b/test/specs/entry_spec.lua index 43b36ff..20ba715 100644 --- a/test/specs/entry_spec.lua +++ b/test/specs/entry_spec.lua @@ -132,93 +132,6 @@ describe('carbon.entry', function() end) end) - describe('is_compressible', function() - it('is boolean', function() - local target = entry.new(helpers.resolve('lua')) - - assert.is_boolean(target:is_compressible()) - end) - - it('is true by default', function() - local target = entry.new(helpers.resolve('lua')) - - target:set_compressible(nil) - - assert.is_true(target:is_compressible()) - end) - end) - - describe('set_compressible', function() - it('sets compressible status', function() - local target = entry.new(helpers.resolve('lua')) - - target:set_compressible(false) - - assert.is_false(target:is_compressible()) - - target:set_compressible(nil) - end) - end) - - describe('is_open', function() - it('is boolean', function() - local target = entry.new(helpers.resolve('lua')) - - assert.is_boolean(target:is_open()) - end) - - it('is false by default', function() - local target = entry.new(helpers.resolve('lua')) - - assert.is_false(target:is_open()) - end) - end) - - describe('set_open', function() - it('does nothing when called on regular file', function() - local file = entry.new(helpers.resolve('README.md')) - - file:set_open(true) - - assert.is_false(file:is_open()) - end) - - it('sets opened status', function() - local target = entry.new(helpers.resolve('lua')) - - target:set_open(true) - - assert.is_true(target:is_open()) - - target:set_open(nil) - end) - - it('opens recursively when recursive is true', function() - local target = entry.new(helpers.resolve('lua')) - local dirs = vim.tbl_filter(function(child) - return child.is_directory - end, target:children()) - - assert.is_true(#dirs > 0) - - target:set_open(true, true) - - assert.is_true(target:is_open()) - - for _, dir_child in ipairs(dirs) do - assert.is_true(dir_child:is_open()) - end - - target:set_open(nil, true) - - assert.is_false(target:is_open()) - - for _, dir_child in ipairs(dirs) do - assert.is_false(dir_child:is_open()) - end - end) - end) - describe('children', function() it('returns empty table on regular file', function() local file = entry.new(helpers.resolve('README.md')) From dc15d7538ccfa40f8fc4dd91e4eaa40af14c4ac5 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 14:51:30 +0200 Subject: [PATCH 05/52] Fix pending tests --- test/config/helpers.lua | 16 +++++++ test/specs/carbon_spec.lua | 94 +++++++++++++++++++++---------------- test/specs/watcher_spec.lua | 9 ++-- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/test/config/helpers.lua b/test/config/helpers.lua index 3d9b434..5a63556 100644 --- a/test/config/helpers.lua +++ b/test/config/helpers.lua @@ -1,3 +1,5 @@ +local util = require('carbon.util') +local view = require('carbon.view') local entry = require('carbon.entry') local constants = require('carbon.constants') local helpers = {} @@ -79,6 +81,20 @@ function helpers.entry(relative_path) return entry.find(string.format('%s/%s', vim.loop.cwd(), relative_path)) end +function helpers.is_open(path) + return view.execute(function(ctx) + return ctx.view:get_path_attr(path, 'open') + end) +end + +function helpers.line_with_file() + return view.execute(function(ctx) + return util.tbl_find(ctx.view:current_lines(), function(line) + return not line.entry.is_directory + end) + end) +end + function helpers.markdown_info(absolute_path) local result = { tags = {}, refs = {}, header_tags = {}, header_refs = {} } local lines = vim.fn.readfile(absolute_path) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index cec7c18..d45e181 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -1,6 +1,6 @@ require('test.config.assertions') --- local spy = require('luassert.spy') +local spy = require('luassert.spy') local util = require('carbon.util') local carbon = require('carbon') local view = require('carbon.view') @@ -120,13 +120,15 @@ describe('carbon', function() end) describe('split', function() - pending('open file in horizontal split', function() + it('open file in horizontal split', function() assert.equal('carbon.explorer', vim.bo.filetype) - util.cursor(3, 1) - carbon.split() + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) - helpers.inspect_buffer() + util.cursor(file_line.lnum, 1) + carbon.split() assert.not_equal('carbon.explorer', vim.bo.filetype) @@ -137,10 +139,14 @@ describe('carbon', function() end) describe('vsplit', function() - pending('open file in vertical split', function() + it('open file in vertical split', function() assert.equal('carbon.explorer', vim.bo.filetype) - util.cursor(3, 1) + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) + + util.cursor(file_line.lnum, 1) carbon.vsplit() assert.not_equal('carbon.explorer', vim.bo.filetype) @@ -152,21 +158,21 @@ describe('carbon', function() end) describe('toggle_recursive', function() - pending('toggles recursively opened directory', function() + it('toggles recursively opened directory', function() local assets_entry = helpers.entry('doc/assets') util.cursor(4, 1) assert.not_nil(assets_entry) carbon.toggle_recursive() - assert.is_true(assets_entry:is_open()) + assert.is_true(helpers.is_open(assets_entry.path)) assert.same( { '- doc/', ' - assets/' }, vim.api.nvim_buf_get_lines(0, 3, 5, true) ) carbon.toggle_recursive() - assert.is_false(assets_entry:is_open()) + assert.is_false(helpers.is_open(assets_entry.path)) assert.same( { '+ doc/', '+ lua/' }, vim.api.nvim_buf_get_lines(0, 3, 5, true) @@ -175,8 +181,12 @@ describe('carbon', function() end) describe('explore', function() - pending('shows the buffer', function() - util.cursor(12, 1) + it('shows the buffer', function() + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) + + util.cursor(file_line.lnum, 1) carbon.edit() carbon.explore() @@ -185,8 +195,12 @@ describe('carbon', function() end) describe('explore_left', function() - pending('shows the buffer to the left of the current buffer', function() - util.cursor(12, 1) + it('shows the buffer to the left of the current buffer', function() + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) + + util.cursor(file_line.lnum, 1) carbon.edit() local before_bufname = vim.fn.bufname() @@ -201,7 +215,7 @@ describe('carbon', function() end) describe('explore_float', function() - pending('shows the buffer in a floating window', function() + it('shows the buffer in a floating window', function() carbon.explore_float() assert.is_number( @@ -213,7 +227,7 @@ describe('carbon', function() end) describe('up', function() - pending('sets cwd to parent directory', function() + it('sets cwd to parent directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -225,7 +239,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - pending('registers new directory listeners', function() + it('registers new directory listeners', function() local original_listeners = watcher.registered() carbon.up() @@ -233,7 +247,7 @@ describe('carbon', function() assert.not_same(original_listeners, watcher.registered()) end) - pending('automatically opens previous cwd', function() + it('automatically opens previous cwd', function() util.cursor(1, 1) assert.is_equal('carbon.explorer', vim.bo.filetype) @@ -242,7 +256,7 @@ describe('carbon', function() local root = ctx.view.root carbon.up() - assert.is_true(ctx.view:get_path_attr(root.path)) + assert.is_true(ctx.view:get_path_attr(root.path, 'open')) end) carbon.reset() @@ -250,7 +264,7 @@ describe('carbon', function() end) describe('reset', function() - pending('reset to original cwd during startup', function() + it('reset to original cwd during startup', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -263,7 +277,7 @@ describe('carbon', function() end) describe('down', function() - pending('sets cwd to cursor directory', function() + it('sets cwd to cursor directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -276,7 +290,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - pending('releases registered listeners not in new cwd', function() + it('releases registered listeners not in new cwd', function() local original_listeners = watcher.registered() util.cursor(2, 1) @@ -289,7 +303,7 @@ describe('carbon', function() end) describe('cd', function() - pending('sets cwd to target path', function() + it('sets cwd to target path', function() local jump_cwd = string.format('%s/test/specs', vim.loop.cwd()) settings.sync_pwd = true @@ -304,45 +318,45 @@ describe('carbon', function() end) describe('quit', function() - pending('closes the buffer', function() + it('closes the buffer', function() vim.cmd.edit('README.md') carbon.explore() helpers.type_keys(settings.actions.quit) - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) end) end) describe('create', function() - pending('calls buffer.create', function() - -- local buffer_create = spy.on(buffer, 'create') + it('calls buffer.create', function() + local view_create = spy.on(view, 'create') - -- carbon.create() - -- helpers.type_keys('') + carbon.create() + helpers.type_keys('') - -- assert.spy(buffer_create).is_called() + assert.spy(view_create).is_called() end) end) describe('delete', function() - pending('calls buffer.delete', function() - -- local buffer_delete = spy.on(buffer, 'delete') + it('calls buffer.delete', function() + local view_delete = spy.on(view, 'delete') - -- carbon.delete() - -- helpers.type_keys('') + carbon.delete() + helpers.type_keys('') - -- assert.spy(buffer_delete).is_called() + assert.spy(view_delete).is_called() end) end) describe('move', function() - pending('calls buffer.move', function() - -- local buffer_move = spy.on(buffer, 'move') + it('calls buffer.move', function() + local view_move = spy.on(view, 'move') - -- carbon.move() - -- helpers.type_keys('') + carbon.move() + helpers.type_keys('') - -- assert.spy(buffer_move).is_called() + assert.spy(view_move).is_called() end) end) end) diff --git a/test/specs/watcher_spec.lua b/test/specs/watcher_spec.lua index 3f56204..961f7ac 100644 --- a/test/specs/watcher_spec.lua +++ b/test/specs/watcher_spec.lua @@ -46,20 +46,21 @@ describe('carbon.watcher', function() -- FIXME: remove pending status after figuring out how to wait for fs events describe('carbon:synchronize', function() - pending('triggers on new file', function() + it('triggers on new file', function() local callback = spy() watcher.register(vim.loop.cwd()) watcher.on('carbon:synchronize', callback) helpers.ensure_path('check.txt') + vim.wait(100) assert .spy(callback) .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.txt', nil) end) - pending('triggers on file change', function() + it('triggers on file change', function() local callback = spy() helpers.ensure_path('check.sh') @@ -68,6 +69,7 @@ describe('carbon.watcher', function() watcher.on('carbon:synchronize', callback) helpers.change_file('check.sh') + vim.wait(100) assert.spy(callback).is_called() assert @@ -75,7 +77,7 @@ describe('carbon.watcher', function() .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.sh', nil) end) - pending('triggers on file remove', function() + it('triggers on file remove', function() local callback = spy() helpers.ensure_path('check.sh') @@ -84,6 +86,7 @@ describe('carbon.watcher', function() watcher.on('carbon:synchronize', callback) helpers.delete_path('check.sh') + vim.wait(100) assert.spy(callback).is_called() assert From 1fc332cf6fd9e64d9b474c2990fe084dc531c6dc Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 15:26:02 +0200 Subject: [PATCH 06/52] only run watcher uv event tests in isolation --- test/specs/watcher_spec.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/specs/watcher_spec.lua b/test/specs/watcher_spec.lua index 961f7ac..016d59d 100644 --- a/test/specs/watcher_spec.lua +++ b/test/specs/watcher_spec.lua @@ -46,7 +46,9 @@ describe('carbon.watcher', function() -- FIXME: remove pending status after figuring out how to wait for fs events describe('carbon:synchronize', function() - it('triggers on new file', function() + local it_when_only = vim.env.only and it or pending + + it_when_only('triggers on new file', function() local callback = spy() watcher.register(vim.loop.cwd()) @@ -60,7 +62,7 @@ describe('carbon.watcher', function() .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.txt', nil) end) - it('triggers on file change', function() + it_when_only('triggers on file change', function() local callback = spy() helpers.ensure_path('check.sh') @@ -77,7 +79,7 @@ describe('carbon.watcher', function() .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.sh', nil) end) - it('triggers on file remove', function() + it_when_only('triggers on file remove', function() local callback = spy() helpers.ensure_path('check.sh') From 8bc436273bd048593bcf3e9ac88cbd875850b56e Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 19:25:34 +0200 Subject: [PATCH 07/52] use indexed methods everywhere when using vim.cmd --- lua/carbon/util.lua | 2 +- lua/carbon/view.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index 2469d9c..078ad81 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -108,7 +108,7 @@ function util.confirm(options) end vim.api.nvim_set_option('guicursor', guicursor) - vim.cmd({ cmd = 'close' }) + vim.cmd.close() end if not immediate then diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index bb18521..eda55fe 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -12,7 +12,7 @@ view.float = { origin = -1, target = -1 } view.resync_paths = {} local function create_leave(ctx) - vim.cmd({ cmd = 'stopinsert' }) + vim.cmd.stopinsert() ctx.view:set_path_attr(ctx.target.path, 'compressible', ctx.prev_compressible) util.cursor(ctx.target_line.lnum, 1) vim.keymap.del('i', '', { buffer = 0 }) From 6bb30ead1b05facee4ee6571f3120bed96136d0e Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 20:24:42 +0200 Subject: [PATCH 08/52] fix compressible status, resync a path only once per resync across multiple views --- lua/carbon/entry.lua | 2 ++ lua/carbon/view.lua | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/carbon/entry.lua b/lua/carbon/entry.lua index 8881c80..89d5d5a 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry.lua @@ -55,6 +55,8 @@ function entry:synchronize(paths) paths = paths or {} if paths[self.path] then + paths[self.path] = nil + local all_paths = {} local current_paths = {} local previous_paths = {} diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index eda55fe..b744f0e 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -233,8 +233,13 @@ end function view:get_path_attr(path, attr) local state = self.states[path] + local value = state and state[attr] - return state and state[attr] + if attr == 'compressible' and value == nil then + return true + end + + return value end function view:set_path_attr(path, attr, value) From 39da824e4145027583035cac22b23adc76950234 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:44:53 +0200 Subject: [PATCH 09/52] Feature: open when editing directories, open_on_dir setting, small float_settings tweak --- lua/carbon.lua | 17 +++++++++++++++++ lua/carbon/settings.lua | 7 ++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index bb067d2..7874438 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -28,6 +28,10 @@ function carbon.setup(user_settings) util.command('Lcarbon', carbon.explore_left, command_opts) util.command('Fcarbon', carbon.explore_float, command_opts) + if settings.open_on_dir then + util.autocmd('BufWinEnter', carbon.maybe_explore, { pattern = '*' }) + end + if settings.sync_on_cd then util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) end @@ -172,6 +176,19 @@ function carbon.cd(path) end) end +function carbon.maybe_explore(params) + if vim.bo.filetype == 'carbon.explorer' then + return + end + + if params and params.file and util.is_directory(params.file) then + view.activate({ path = params.file, delete_current_buf = true }) + view.execute(function(ctx) + ctx.view:show() + end) + end +end + function carbon.explore(options_param) local options = options_param or {} local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') diff --git a/lua/carbon/settings.lua b/lua/carbon/settings.lua index 84cb255..e2248d0 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings.lua @@ -5,8 +5,9 @@ local defaults = { keep_netrw = false, sync_on_cd = not vim.opt.autochdir:get(), sync_delay = 20, - sidebar_width = 30, + open_on_dir = true, always_reveal = false, + sidebar_width = 30, exclude = { '~$', '#$', @@ -33,8 +34,8 @@ local defaults = { float_settings = function() local columns = vim.opt.columns:get() local rows = vim.opt.lines:get() - local width = math.min(50, math.floor(columns * 0.8)) - local height = math.min(20, math.floor(rows * 0.8)) + local width = math.min(40, math.floor(columns * 0.9)) + local height = math.min(20, math.floor(rows * 0.9)) return { relative = 'editor', From e54eb8868d9220e6fa130d04fed9b52ef75e4523 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:45:13 +0200 Subject: [PATCH 10/52] Fix: position of confirm popup is now relative to window --- lua/carbon/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index 078ad81..efb6149 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -161,7 +161,7 @@ function util.confirm(options) }) local win = vim.api.nvim_open_win(buf, true, { - relative = 'editor', + relative = 'win', anchor = 'NW', border = 'single', style = 'minimal', From 50958adb8ee2a7900f8487b175fd51f49f266a79 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:49:21 +0200 Subject: [PATCH 11/52] unique buffer names, stop renaming buffer to root entry filename --- lua/carbon/view.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index b744f0e..3210eb3 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -90,7 +90,6 @@ function view.activate(options_param) local options = options_param or {} local original_window = vim.api.nvim_get_current_win() local original_buffer = vim.api.nvim_get_current_buf() - local original_buffer_valid = vim.api.nvim_buf_is_valid(original_buffer) local current_view = (options.path and view.get(options.path)) or view.current() or view.get(vim.loop.cwd()) @@ -134,8 +133,8 @@ function view.activate(options_param) vim.api.nvim_win_set_buf(0, current_view:buffer()) end - if original_buffer_valid and options.delete_current_buf then - vim.api.nvim_buf_delete(original_buffer, { force = true }) + if options.delete_current_buf then + pcall(vim.api.nvim_buf_delete, original_buffer, { force = true }) end end @@ -361,7 +360,7 @@ function view:buffer() end local buffer = util.create_scratch_buf({ - name = vim.fn.fnamemodify(self.root.path, ':t'), + name = string.format('carbon[%d]', self.index), filetype = 'carbon.explorer', modifiable = false, modified = false, @@ -470,11 +469,6 @@ function view:set_root(target) self.root = target - vim.api.nvim_buf_set_name( - self:buffer(), - vim.fn.fnamemodify(self.root.path, ':t') - ) - watcher.keep(function(path) return vim.startswith(path, self.root.path) end) From a4e39dfe0147f2a6d64dbe4cbb72af1d7b3fa8b3 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:56:35 +0200 Subject: [PATCH 12/52] update tests --- test/specs/carbon_spec.lua | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index d45e181..9c936d2 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -16,12 +16,6 @@ describe('carbon', function() describe('autocommands', function() describe('DirChanged', function() - it('exists', function() - local autocmd = helpers.autocmd('DirChanged') - - assert.is_number(autocmd.id) - end) - it('is not buffer local', function() local autocmd = helpers.autocmd('DirChanged') @@ -36,26 +30,23 @@ describe('carbon', function() end) describe('BufWinEnter', function() - it('exists', function() - local autocmd = helpers.autocmd('BufWinEnter') + it('has buffer local event', function() + local autocmd = helpers.autocmd( + 'BufWinEnter', + { buffer = vim.api.nvim_get_current_buf() } + ) - assert.is_number(autocmd.id) + assert.is_true(autocmd.buflocal) end) - it('is buffer local', function() + it('has a global event', function() local autocmd = helpers.autocmd('BufWinEnter') - assert.is_true(autocmd.buflocal) + assert.is_false(autocmd.buflocal) end) end) describe('BufHidden', function() - it('exists', function() - local autocmd = helpers.autocmd('BufHidden') - - assert.is_number(autocmd.id) - end) - it('is buffer local', function() local autocmd = helpers.autocmd('BufHidden') From 17ffd54913d1cc9834a171457750b66ac375401e Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 4 Sep 2022 00:07:32 +0200 Subject: [PATCH 13/52] assume views only have one active buffer at most --- lua/carbon/view.lua | 66 ++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 3210eb3..56e6573 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -280,28 +280,27 @@ function view:render() end end - for _, buf in ipairs(self:buffers()) do - local current_mode = string.lower(vim.api.nvim_get_mode().mode) - - vim.api.nvim_buf_clear_namespace(buf, constants.hl, 0, -1) - vim.api.nvim_buf_set_option(buf, 'modifiable', true) - vim.api.nvim_buf_set_lines(buf, 0, -1, 1, lines) - vim.api.nvim_buf_set_option(buf, 'modified', false) - - if not string.find(current_mode, 'i') then - vim.api.nvim_buf_set_option(buf, 'modifiable', false) - end - - for _, hl in ipairs(hls) do - vim.api.nvim_buf_add_highlight( - buf, - constants.hl, - hl[1], - hl[2], - hl[3], - hl[4] - ) - end + local buf = self:buffer() + local current_mode = string.lower(vim.api.nvim_get_mode().mode) + + vim.api.nvim_buf_clear_namespace(buf, constants.hl, 0, -1) + vim.api.nvim_buf_set_option(buf, 'modifiable', true) + vim.api.nvim_buf_set_lines(buf, 0, -1, 1, lines) + vim.api.nvim_buf_set_option(buf, 'modified', false) + + if not string.find(current_mode, 'i') then + vim.api.nvim_buf_set_option(buf, 'modifiable', false) + end + + for _, hl in ipairs(hls) do + vim.api.nvim_buf_add_highlight( + buf, + constants.hl, + hl[1], + hl[2], + hl[3], + hl[4] + ) end if cursor then @@ -323,15 +322,13 @@ function view:render() end function view:focus_flash(duration, group, start, finish) - for _, buf in ipairs(self:buffers()) do - vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {}) - end + local buf = self:buffer() + + vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {}) vim.defer_fn(function() - for _, buf in ipairs(self:buffers()) do - if vim.api.nvim_buf_is_valid(buf) then - vim.api.nvim_buf_clear_namespace(buf, constants.hl_tmp, 0, -1) - end + if vim.api.nvim_buf_is_valid(buf) then + vim.api.nvim_buf_clear_namespace(buf, constants.hl_tmp, 0, -1) end end, duration) end @@ -376,7 +373,11 @@ function view:buffer() }, }) - vim.api.nvim_buf_set_var(buffer, 'carbon', { index = self.index }) + vim.api.nvim_buf_set_var( + buffer, + 'carbon', + { index = self.index, name = self.root.name } + ) return buffer end @@ -468,6 +469,11 @@ function view:set_root(target) end self.root = target + vim.api.nvim_buf_set_var( + self:buffer(), + 'carbon', + { index = self.index, name = self.root.name } + ) watcher.keep(function(path) return vim.startswith(path, self.root.path) From 9528ded0d27afe512b8ba6f364dd458766ce023e Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 4 Sep 2022 02:56:59 +0200 Subject: [PATCH 14/52] minor cleanup, only sync pwd when navigating in pwd root buffers --- dev/init.lua | 2 +- lua/carbon/view.lua | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dev/init.lua b/dev/init.lua index 0e7054a..65cf9d2 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -3,4 +3,4 @@ vim.opt.runtimepath:append({ vim.env.HOME .. '/Dev/sidofc/lua/carbon.nvim' }) vim.opt.runtimepath:remove({ vim.env.HOME .. '/.config/nvim' }) vim.opt.packpath:remove({ vim.env.HOME .. '/.local/share/nvim/site' }) -require('carbon').setup() +require('carbon').setup({ sync_pwd = true }) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 56e6573..51694a2 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -81,7 +81,6 @@ function view.get(path) }, view) views[index] = instance - instance.index = index return instance end @@ -376,7 +375,7 @@ function view:buffer() vim.api.nvim_buf_set_var( buffer, 'carbon', - { index = self.index, name = self.root.name } + { index = self.index, path = self.root.path } ) return buffer @@ -460,6 +459,8 @@ function view:down(count) end function view:set_root(target) + local is_cwd = self.root.path == vim.loop.cwd() + if type(target) == 'string' then target = entry.new(target) end @@ -472,14 +473,14 @@ function view:set_root(target) vim.api.nvim_buf_set_var( self:buffer(), 'carbon', - { index = self.index, name = self.root.name } + { index = self.index, path = self.root.path } ) watcher.keep(function(path) return vim.startswith(path, self.root.path) end) - if settings.sync_pwd then + if settings.sync_pwd and is_cwd then vim.api.nvim_set_current_dir(self.root.path) end From d635667b55e7d1c591a7ace0c5d66b86332e0d8a Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 21 Aug 2022 18:11:05 +0200 Subject: [PATCH 15/52] replace `carbon.buffer` with new `carbon.view` module --- dev/init.lua | 3 + lua/carbon.lua | 327 +++++++--------- lua/carbon/entry.lua | 4 + lua/carbon/settings.lua | 3 +- lua/carbon/util.lua | 18 + lua/carbon/view.lua | 772 +++++++++++++++++++++++++++++++++++++ plugin/carbon.vim | 1 - test/config/init.lua | 2 +- test/specs/buffer_spec.lua | 453 ---------------------- test/specs/carbon_spec.lua | 46 ++- 10 files changed, 962 insertions(+), 667 deletions(-) create mode 100644 lua/carbon/view.lua delete mode 100644 plugin/carbon.vim delete mode 100644 test/specs/buffer_spec.lua diff --git a/dev/init.lua b/dev/init.lua index 0869a54..0e7054a 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -1,3 +1,6 @@ +vim.opt.termguicolors = true vim.opt.runtimepath:append({ vim.env.HOME .. '/Dev/sidofc/lua/carbon.nvim' }) vim.opt.runtimepath:remove({ vim.env.HOME .. '/.config/nvim' }) vim.opt.packpath:remove({ vim.env.HOME .. '/.local/share/nvim/site' }) + +require('carbon').setup() diff --git a/lua/carbon.lua b/lua/carbon.lua index 3b5e0bf..0a3fb75 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -1,269 +1,216 @@ local util = require('carbon.util') -local buffer = require('carbon.buffer') local watcher = require('carbon.watcher') local settings = require('carbon.settings') +local view = require('carbon.view') local carbon = {} -local data = { initialized = false } function carbon.setup(user_settings) - if data.initialized then - return - end - - if type(user_settings) == 'function' then - user_settings(settings) - else - local next = vim.tbl_deep_extend('force', settings, user_settings) - - for setting, value in pairs(next) do - settings[setting] = value + if not vim.g.carbon_initialized then + if type(user_settings) == 'function' then + user_settings(settings) + elseif type(user_settings) == 'table' then + local next = vim.tbl_deep_extend('force', settings, user_settings) + + for setting, value in pairs(next) do + settings[setting] = value + end end - end - return settings -end - -function carbon.initialize() - if data.initialized then - return - else - data.initialized = true - end + local argv = vim.fn.argv() + local open = argv[1] and vim.fn.fnamemodify(argv[1], ':p') or vim.loop.cwd() + local command_opts = { bang = true, nargs = '?', complete = 'dir' } - watcher.on('carbon:synchronize', buffer.defer_resync) + watcher.on('carbon:synchronize', function(_, path) + view.resync(path) + end) - util.command('Carbon', carbon.explore, { bang = true }) - util.command('Lcarbon', carbon.explore_left, { bang = true }) - util.command('Fcarbon', carbon.explore_float, { bang = true }) + util.command('Carbon', carbon.explore, command_opts) + util.command('Lcarbon', carbon.explore_left, command_opts) + util.command('Fcarbon', carbon.explore_float, command_opts) - for action in pairs(settings.defaults.actions) do - vim.keymap.set('', util.plug(action), carbon[action]) - end - - if settings.sync_on_cd then - util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) - end - - if type(settings.highlights) == 'table' then - for group, properties in pairs(settings.highlights) do - util.highlight(group, properties) + if settings.sync_on_cd then + util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) end - end - if not settings.keep_netrw then - vim.g.loaded_netrw = 1 - vim.g.loaded_netrwPlugin = 1 + if not settings.keep_netrw then + vim.g.loaded_netrw = 1 + vim.g.loaded_netrwPlugin = 1 - pcall(vim.api.nvim_del_augroup_by_name, 'FileExplorer') - pcall(vim.api.nvim_del_augroup_by_name, 'Network') + pcall(vim.api.nvim_del_augroup_by_name, 'FileExplorer') + pcall(vim.api.nvim_del_augroup_by_name, 'Network') - util.command('Explore', carbon.explore, { bang = true }) - util.command('Lexplore', carbon.explore_left, { bang = true }) - end - - local argv = vim.fn.argv() - local open = argv[1] and vim.fn.fnamemodify(argv[1], ':p') or vim.loop.cwd() + util.command('Explore', carbon.explore, command_opts) + util.command('Lexplore', carbon.explore_left, command_opts) + end - if - vim.fn.has('vim_starting') - and settings.auto_open - and util.is_directory(open) - then - local current_buffer = vim.api.nvim_win_get_buf(0) + for action in pairs(settings.defaults.actions) do + vim.keymap.set('', util.plug(action), carbon[action]) + end - buffer.launch(open) + if type(settings.highlights) == 'table' then + for group, properties in pairs(settings.highlights) do + util.highlight(group, properties) + end + end - if vim.api.nvim_buf_is_valid(current_buffer) then - vim.api.nvim_buf_delete(current_buffer, { force = true }) + if + vim.fn.has('vim_starting') + and settings.auto_open + and util.is_directory(open) + then + view.activate({ path = open, delete_current_buf = true }) end - end - return carbon + vim.g.carbon_initialized = true + end end function carbon.toggle_recursive() - local line = buffer.cursor().line - - if line.entry.is_directory then - line.entry:set_open(not line.entry:is_open(), true) - - buffer.render() - end + view.execute(function(context) + if context.cursor.line.entry.is_directory then + context.cursor.line.entry:toggle_open(true) + context.view:update() + context.view:render() + end + end) end function carbon.edit() - local line = buffer.cursor().line - - if line.entry.is_directory then - line.entry:set_open(not line.entry:is_open()) - - buffer.render() - elseif vim.w.carbon_lexplore_window then - vim.cmd({ cmd = 'wincmd', args = { 'l' } }) - - if vim.w.carbon_lexplore_window == vim.api.nvim_get_current_win() then - vim.cmd({ - cmd = 'split', - args = { line.entry.path }, - mods = { vertical = true, split = 'belowright' }, - }) - - vim.api.nvim_win_set_width( - util.bufwinid(buffer.handle()), - settings.sidebar_width - ) + view.execute(function(context) + if context.cursor.line.entry.is_directory then + context.cursor.line.entry:toggle_open() + context.view:update() + context.view:render() else - vim.cmd({ cmd = 'edit', args = { line.entry.path } }) - end - else - if vim.w.carbon_fexplore_window then - vim.api.nvim_win_close(0, 1) + view.handle_sidebar_or_float() + vim.cmd.edit(context.cursor.line.entry.path) end - - vim.cmd({ cmd = 'edit', args = { line.entry.path } }) - end + end) end function carbon.split() - local line = buffer.cursor().line + view.execute(function(context) + if not context.cursor.line.entry.is_directory then + if vim.w.carbon_fexplore_window then + vim.api.nvim_win_close(0, 1) + end - if not line.entry.is_directory then - if vim.w.carbon_fexplore_window then - vim.api.nvim_win_close(0, 1) + vim.cmd.split(context.cursor.line.entry.path) end - - vim.cmd({ cmd = 'split', args = { line.entry.path } }) - end + end) end function carbon.vsplit() - local line = buffer.cursor().line + view.execute(function(context) + if not context.cursor.line.entry.is_directory then + if vim.w.carbon_fexplore_window then + vim.api.nvim_win_close(0, 1) + end - if not line.entry.is_directory then - if vim.w.carbon_fexplore_window then - vim.api.nvim_win_close(0, 1) + vim.cmd.vsplit(context.cursor.line.entry.path) end - - vim.cmd({ cmd = 'vsplit', args = { line.entry.path } }) - end + end) end -function carbon.explore(options) - if options and options.bang or settings.always_reveal then - buffer.expand_to_path(vim.fn.expand('%')) - end - - buffer.show() +function carbon.up() + view.execute(function(context) + if context.view:up() then + context.view:update() + context.view:render() + util.cursor(1, 1) + end + end) end -function carbon.explore_left(options) - if options and options.bang or settings.always_reveal then - buffer.expand_to_path(vim.fn.expand('%')) - end - - local existing_win - - for _, win in ipairs(vim.api.nvim_list_wins()) do - if pcall(vim.api.nvim_win_get_var, win, 'carbon_lexplore_window') then - if vim.api.nvim_win_is_valid(win) then - existing_win = win - end - - break +function carbon.reset() + view.execute(function(context) + if context.view:reset() then + context.view:update() + context.view:render() + util.cursor(1, 1) end - end + end) +end - if existing_win then - vim.api.nvim_set_current_win(existing_win) - buffer.show() - else - vim.cmd({ cmd = 'split', mods = { vertical = true, split = 'leftabove' } }) - buffer.show() +function carbon.down() + view.execute(function(context) + if context.view:down() then + context.view:update() + context.view:render() + util.cursor(1, 1) + end + end) +end - vim.api.nvim_win_set_width( - util.bufwinid(buffer.handle()), - settings.sidebar_width - ) +function carbon.cd(path) + view.execute(function(context) + local destination = path and path.file or path or vim.v.event.cwd - vim.w.carbon_lexplore_window = vim.api.nvim_get_current_win() - end + if context.view:cd(destination) then + context.view:update() + context.view:render() + util.cursor(1, 1) + end + end) end -function carbon.explore_float(options) - if options and options.bang or settings.always_reveal then - buffer.expand_to_path(vim.fn.expand('%')) - end - - local window_settings = settings.float_settings +function carbon.explore(options_param) + local options = options_param or {} + local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' - if type(window_settings) == 'function' then - window_settings = window_settings() + if path == '' then + path = vim.loop.cwd() end - window_settings = vim.deepcopy(window_settings) - - local carbon_fexplore_window = vim.api.nvim_get_current_win() - local window = vim.api.nvim_open_win(buffer.handle(), 1, window_settings) - - buffer.render() - - vim.api.nvim_win_set_option( - window, - 'winhl', - 'FloatBorder:Normal,Normal:Normal' - ) - - vim.w.carbon_fexplore_window = carbon_fexplore_window + view.activate({ path = path, reveal = options.bang }) end -function carbon.up() - if buffer.up() then - util.cursor(1, 1) - buffer.render() - end -end +function carbon.explore_left(options_param) + local options = options_param or {} + local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' -function carbon.reset() - if buffer.reset() then - util.cursor(1, 1) - buffer.render() + if path == '' then + path = vim.loop.cwd() end -end -function carbon.down() - if buffer.down() then - util.cursor(1, 1) - buffer.render() - end + view.activate({ path = path, reveal = options.bang, sidebar = true }) end -function carbon.cd(path) - local destination = path and path.file or path or vim.v.event.cwd +function carbon.explore_float(options_param) + local options = options_param or {} + local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' - if buffer.cd(destination) then - util.cursor(1, 1) - buffer.render() + if path == '' then + path = vim.loop.cwd() end + + view.activate({ path = path, reveal = options.bang, float = true }) end function carbon.quit() if #vim.api.nvim_list_wins() > 1 then vim.api.nvim_win_close(0, 1) elseif #vim.api.nvim_list_bufs() > 1 then - vim.cmd({ cmd = 'bprevious' }) + vim.cmd.bprevious() end end function carbon.create() - buffer.create() + view.execute(function(context) + context.view:create() + end) end function carbon.delete() - buffer.delete() + view.execute(function(context) + context.view:delete() + end) end function carbon.move() - buffer.move() + view.execute(function(context) + context.view:move() + end) end return carbon diff --git a/lua/carbon/entry.lua b/lua/carbon/entry.lua index 9132fb5..65fa261 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry.lua @@ -137,6 +137,10 @@ function entry:is_open() return data.open[self.path] and true or false end +function entry:toggle_open(recursive) + self:set_open(not self:is_open(), recursive) +end + function entry:children() if self.is_directory and not self:has_children() then self:set_children(self:get_children()) diff --git a/lua/carbon/settings.lua b/lua/carbon/settings.lua index 4d9dbc2..32c2feb 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings.lua @@ -39,7 +39,7 @@ local defaults = { return { relative = 'editor', style = 'minimal', - border = 'single', + border = 'rounded', width = width, height = height, col = math.floor(columns / 2 - width / 2), @@ -68,6 +68,7 @@ local defaults = { CarbonIndicator = { fg = 'Gray', ctermfg = 'DarkGray', bold = true }, CarbonDanger = { fg = '#ff3333', ctermfg = 'Red', bold = true }, CarbonPending = { fg = '#ffee00', ctermfg = 'Yellow', bold = true }, + CarbonFloat = { bg = '#111111', ctermbg = 'black' }, CarbonFlash = { link = 'Visual' }, }, } diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index c1ce0ea..2469d9c 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -2,6 +2,12 @@ local constants = require('carbon.constants') local settings = require('carbon.settings') local util = {} +function util.resolve(path) + local normalized = vim.fs.normalize(path) + + return string.gsub(vim.fn.fnamemodify(normalized, ':p'), '/+$', '') +end + function util.is_excluded(path) if settings.exclude then for _, pattern in ipairs(settings.exclude) do @@ -245,4 +251,16 @@ function util.set_winhl(win, highlights) vim.api.nvim_win_set_option(win, 'winhl', table.concat(winhls, ',')) end +function util.clear_extmarks(buf, ...) + local extmarks = vim.api.nvim_buf_get_extmarks(buf, constants.hl, ...) + + for _, extmark in ipairs(extmarks) do + vim.api.nvim_buf_del_extmark(buf, constants.hl, extmark[1]) + end +end + +function util.add_highlight(buf, ...) + vim.api.nvim_buf_add_highlight(buf, constants.hl, ...) +end + return util diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua new file mode 100644 index 0000000..6a2cd51 --- /dev/null +++ b/lua/carbon/view.lua @@ -0,0 +1,772 @@ +local util = require('carbon.util') +local entry = require('carbon.entry') +local watcher = require('carbon.watcher') +local settings = require('carbon.settings') +local constants = require('carbon.constants') +local view = {} +local views = {} + +view.__index = view +view.sidebar = { origin = -1, target = -1 } +view.float = { origin = -1, target = -1 } + +local function create_leave(ctx) + vim.cmd({ cmd = 'stopinsert' }) + ctx.target:set_compressible(ctx.prev_compressible) + util.cursor(ctx.target_line.lnum, 1) + vim.keymap.del('i', '', { buffer = 0 }) + vim.keymap.del('i', '', { buffer = 0 }) + util.clear_autocmd('CursorMovedI', { buffer = 0 }) + ctx.view:update() + ctx.view:render() +end + +local function create_confirm(ctx) + return function() + local text = vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) + local name = vim.fn.fnamemodify(text, ':t') + local parent_directory = ctx.target.path + .. '/' + .. vim.trim(vim.fn.fnamemodify(text, ':h')) + + vim.fn.mkdir(parent_directory, 'p') + + if name ~= '' then + vim.fn.writefile({}, parent_directory .. '/' .. name) + end + + create_leave(ctx) + view.resync(vim.fn.fnamemodify(parent_directory, ':h')) + end +end + +local function create_cancel(ctx) + return function() + ctx.target:set_open(ctx.prev_open) + create_leave(ctx) + end +end + +local function create_insert_move(ctx) + return function() + local text = ctx.edit_prefix + .. vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) + local last_slash_col = vim.fn.strridx(text, '/') + 1 + + vim.api.nvim_buf_set_lines(0, ctx.edit_lnum, ctx.edit_lnum + 1, 1, { text }) + util.clear_extmarks(0, { ctx.edit_lnum, 0 }, { ctx.edit_lnum, -1 }, {}) + util.add_highlight(0, 'CarbonDir', ctx.edit_lnum, 0, last_slash_col) + util.add_highlight(0, 'CarbonFile', ctx.edit_lnum, last_slash_col, -1) + util.cursor(ctx.edit_lnum + 1, math.max(ctx.edit_col, vim.fn.col('.'))) + end +end + +function view.get(path) + local resolved = util.resolve(path) + local found_view = util.tbl_find(views, function(target_view) + return target_view.root.path == resolved + end) + + if found_view then + return found_view + end + + local index = #views + 1 + local instance = setmetatable( + { index = index, initial = resolved, root = entry.new(resolved) }, + view + ) + + views[index] = instance + instance.index = index + + return instance +end + +function view.activate(options_param) + local options = options_param or {} + local original_window = vim.api.nvim_get_current_win() + local original_buffer = vim.api.nvim_get_current_buf() + local original_buffer_valid = vim.api.nvim_buf_is_valid(original_buffer) + local current_view = (options.path and view.get(options.path)) + or view.current() + + if options.reveal or settings.always_reveal then + current_view:expand_to_path(vim.fn.expand('%')) + end + + if options.sidebar then + if vim.api.nvim_win_is_valid(view.sidebar.origin) then + vim.api.nvim_set_current_win(view.sidebar.origin) + else + vim.cmd.split({ mods = { vertical = true, split = 'leftabove' } }) + + view.sidebar = { + origin = vim.api.nvim_get_current_win(), + target = original_window, + } + end + + vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + vim.api.nvim_win_set_buf(view.sidebar.origin, current_view:buffer()) + elseif options.float then + local float_settings = settings.float_settings + or settings.defaults.float_settings + + float_settings = type(float_settings) == 'function' and float_settings() + or vim.deepcopy(float_settings) + + view.float = { + origin = vim.api.nvim_open_win(current_view:buffer(), 1, float_settings), + target = original_window, + } + + vim.api.nvim_win_set_option( + view.float.origin, + 'winhl', + 'FloatBorder:CarbonFloat,Normal:CarbonFloat' + ) + else + vim.api.nvim_win_set_buf(0, current_view:buffer()) + end + + if original_buffer_valid and options.delete_current_buf then + vim.api.nvim_buf_delete(original_buffer, { force = true }) + end +end + +function view.handle_sidebar_or_float() + local current_window = vim.api.nvim_get_current_win() + + if current_window == view.sidebar.origin then + if vim.api.nvim_win_is_valid(view.sidebar.target) then + vim.api.nvim_set_current_win(view.sidebar.target) + else + vim.cmd.split({ mods = { vertical = true, split = 'belowright' } }) + + view.sidebar.target = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + end + elseif current_window == view.float.origin then + vim.api.nvim_win_close(0, true) + end +end + +function view:expand_to_path(path) + local resolved = util.resolve(path) + + if vim.startswith(resolved, self.root.path) then + local dirs = vim.split(string.sub(resolved, #self.root.path + 2), '/') + local current = self.root + + for _, dir in ipairs(dirs) do + current:children() + + current = entry.find(string.format('%s/%s', current.path, dir)) + + if current then + current:set_open(true) + else + break + end + end + + if current and current.path == resolved then + self.flash = current + + return true + end + + return false + end +end + +function view.exists(index) + return views[index] and true or false +end + +function view.current() + local bufnr = vim.api.nvim_get_current_buf() + local ref = select(2, pcall(vim.api.nvim_buf_get_var, bufnr, 'carbon')) + + return ref and views[ref.index] or false +end + +function view.execute(callback) + local current_view = view.current() + + if current_view then + return callback({ cursor = current_view:cursor(), view = current_view }) + end +end + +function view.list() + return views +end + +function view.resync(path) -- luacheck:ignore unused argument path + -- print(string.format("view.resync('%s')", path)) +end + +function view:update() + self.cached_lines = nil +end + +function view:buffers() + return vim.tbl_filter(function(bufnr) + local ref = select(2, pcall(vim.api.nvim_buf_get_var, bufnr, 'carbon')) + + return ref and ref.index == self.index + end, vim.api.nvim_list_bufs()) +end + +function view:render() + local cursor + local lines = {} + local hls = {} + + for lnum, line_data in ipairs(self:current_lines()) do + lines[#lines + 1] = line_data.line + + if self.flash and self.flash.path == line_data.entry.path then + cursor = { lnum = lnum, col = 1 + (line_data.depth + 1) * 2 } + end + + for _, hl in ipairs(line_data.highlights) do + hls[#hls + 1] = { hl[1], lnum - 1, hl[2], hl[3] } + end + end + + for _, buf in ipairs(self:buffers()) do + local current_mode = string.lower(vim.api.nvim_get_mode().mode) + + vim.api.nvim_buf_clear_namespace(buf, constants.hl, 0, -1) + vim.api.nvim_buf_set_option(buf, 'modifiable', true) + vim.api.nvim_buf_set_lines(buf, 0, -1, 1, lines) + vim.api.nvim_buf_set_option(buf, 'modified', false) + + if not string.find(current_mode, 'i') then + vim.api.nvim_buf_set_option(buf, 'modifiable', false) + end + + for _, hl in ipairs(hls) do + vim.api.nvim_buf_add_highlight( + buf, + constants.hl, + hl[1], + hl[2], + hl[3], + hl[4] + ) + end + end + + if cursor then + util.cursor(cursor.lnum, cursor.col) + + if settings.flash then + vim.defer_fn(function() + self:focus_flash( + settings.flash.duration, + 'CarbonFlash', + { cursor.lnum - 1, cursor.col - 1 }, + { cursor.lnum - 1, -1 } + ) + end, settings.flash.delay) + end + end + + self.flash = nil +end + +function view:focus_flash(duration, group, start, finish) + for _, buf in ipairs(self:buffers()) do + vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {}) + end + + vim.defer_fn(function() + for _, buf in ipairs(self:buffers()) do + if vim.api.nvim_buf_is_valid(buf) then + vim.api.nvim_buf_clear_namespace(buf, constants.hl_tmp, 0, -1) + end + end + end, duration) +end + +function view:buffer() + local buffers = self:buffers() + + if buffers[1] then + return buffers[1] + end + + local mappings = { + { 'n', 'i', '' }, + { 'n', 'I', '' }, + { 'n', 'o', '' }, + { 'n', 'O', '' }, + } + + for action, mapping in pairs(settings.actions or {}) do + mapping = type(mapping) == 'string' and { mapping } or mapping or {} + + for _, key in ipairs(mapping) do + mappings[#mappings + 1] = + { 'n', key, util.plug(action), { nowait = true } } + end + end + + local buffer = util.create_scratch_buf({ + name = vim.fn.fnamemodify(self.root.path, ':t'), + filetype = 'carbon.explorer', + modifiable = false, + modified = false, + bufhidden = 'hide', + mappings = mappings, + autocmds = { + BufHidden = function() + self:hide() + end, + BufWinEnter = function() + self:show() + end, + }, + }) + + vim.api.nvim_buf_set_var(buffer, 'carbon', { index = self.index }) + + return buffer +end + +function view:hide() -- luacheck:ignore unused argument self + vim.opt_local.wrap = vim.opt_global.wrap:get() + vim.opt_local.spell = vim.opt_global.spell:get() + vim.opt_local.fillchars = vim.opt_global.fillchars:get() + + view.sidebar = { origin = -1, target = -1 } + view.float = { origin = -1, target = -1 } +end + +function view:show() + vim.opt_local.wrap = false + vim.opt_local.spell = false + vim.opt_local.fillchars = { eob = ' ' } + + self:render() +end + +function view:up(count) + local rerender = false + local remaining = count or vim.v.count1 + + while remaining > 0 do + remaining = remaining - 1 + local new_root = entry.new(vim.fn.fnamemodify(self.root.path, ':h')) + + if new_root.path ~= self.root.path then + rerender = true + + new_root:set_children(vim.tbl_map(function(child) + if child.path == self.root.path then + child:set_open(true) + end + + return child + end, new_root:get_children())) + + self:set_root(new_root) + end + end + + return rerender +end + +function view:reset() + return self:cd(self.initial) +end + +function view:cd(path) + if path == self.root.path then + return false + elseif vim.startswith(self.root.path, path) then + local new_depth = select(2, string.gsub(path, '/', '')) + local current_depth = select(2, string.gsub(self.root.path, '/', '')) + + if current_depth - new_depth > 0 then + return self:up(current_depth - new_depth) + end + else + return self:set_root(entry.find(path) or entry.new(path)) + end +end + +function view:down(count) + local cursor = self:cursor() + local new_root = cursor.line.path[count or vim.v.count1] or cursor.line.entry + + if not new_root.is_directory then + new_root = new_root.parent + end + + if new_root.path ~= self.root.path then + self.root:set_open(true) + + return self:set_root(new_root) + end +end + +function view:set_root(target) + if type(target) == 'string' then + target = entry.new(target) + end + + if target.path == self.root.path then + return false + end + + self.root = target + + vim.api.nvim_buf_set_name( + self:buffer(), + vim.fn.fnamemodify(self.root.path, ':t') + ) + + watcher.keep(function(path) + return vim.startswith(path, self.root.path) + end) + + if settings.sync_pwd then + vim.api.nvim_set_current_dir(self.root.path) + end + + return true +end + +function view:current_lines() + if not self.cached_lines then + self.cached_lines = self:lines() + end + + return self.cached_lines +end + +function view:lines(input_target, lines, depth) + lines = lines or {} + depth = depth or 0 + local target = input_target or self.root + local expand_indicator = ' ' + local collapse_indicator = ' ' + + if type(settings.indicators) == 'table' then + expand_indicator = settings.indicators.expand or expand_indicator + collapse_indicator = settings.indicators.collapse or collapse_indicator + end + + if not input_target and #lines == 0 then + lines[#lines + 1] = { + lnum = 1, + depth = -1, + entry = self.root, + line = self.root.name .. '/', + highlights = { { 'CarbonDir', 0, -1 } }, + path = {}, + } + + watcher.register(self.root.path) + end + + for _, child in ipairs(target:children()) do + local tmp = child + local hls = {} + local path = {} + local lnum = 1 + #lines + local indent = string.rep(' ', depth) + local is_empty = true + local indicator = ' ' + local path_suffix = '' + + if settings.compress then + while + tmp.is_directory + and #tmp:children() == 1 + and tmp:is_compressible() + do + watcher.register(tmp.path) + + path[#path + 1] = tmp + tmp = tmp:children()[1] + end + end + + if tmp.is_directory then + watcher.register(tmp.path) + + is_empty = #tmp:children() == 0 + path_suffix = '/' + + if not is_empty and tmp:is_open() then + indicator = collapse_indicator + elseif not is_empty then + indicator = expand_indicator + end + end + + local full_path = tmp.name .. path_suffix + local indent_end = #indent + local path_start = indent_end + #indicator + 1 + local file_group = 'CarbonFile' + local dir_path = table.concat( + vim.tbl_map(function(parent) + return parent.name + end, path), + '/' + ) + + if path[1] then + full_path = dir_path .. '/' .. full_path + end + + if tmp.is_symlink == 1 then + file_group = 'CarbonSymlink' + elseif tmp.is_symlink == 2 then + file_group = 'CarbonBrokenSymlink' + elseif tmp.is_executable then + file_group = 'CarbonExe' + end + + if not is_empty then + hls[#hls + 1] = { 'CarbonIndicator', indent_end, path_start - 1 } + end + + if tmp.is_directory then + hls[#hls + 1] = { 'CarbonDir', path_start, -1 } + elseif path[1] then + local dir_end = path_start + #dir_path + 1 + + hls[#hls + 1] = { 'CarbonDir', path_start, dir_end } + hls[#hls + 1] = { file_group, dir_end, -1 } + else + hls[#hls + 1] = { file_group, path_start, -1 } + end + + lines[#lines + 1] = { + lnum = lnum, + depth = depth, + entry = tmp, + line = indent .. indicator .. ' ' .. full_path, + highlights = hls, + path = path, + } + + if tmp.is_directory and tmp:is_open() then + self:lines(tmp, lines, depth + 1) + end + end + + return lines +end + +function view:cursor(opts) + local options = opts or {} + local lines = self:current_lines() + local line = lines[vim.fn.line('.')] + local target = line.entry + local target_line + + if options.target_directory_only and not target.is_directory then + target = target.parent + end + + target = line.path[vim.v.count] or target + target_line = util.tbl_find(lines, function(current) + if current.entry.path == target.path then + return true + end + + return util.tbl_find(current.path, function(parent) + if parent.path == target.path then + return true + end + end) + end) + + return { line = line, target = target, target_line = target_line } +end + +function view:create() + local ctx = self:cursor({ target_directory_only = true }) + + ctx.view = self + ctx.compact = ctx.target.is_directory and #ctx.target:children() == 0 + ctx.prev_open = ctx.target:is_open() + ctx.prev_compressible = ctx.target:is_compressible() + + ctx.target:set_open(true) + ctx.target:set_compressible(false) + + if ctx.compact then + ctx.edit_prefix = ctx.line.line + ctx.edit_lnum = ctx.line.lnum - 1 + ctx.edit_col = #ctx.edit_prefix + 1 + ctx.init_end_lnum = ctx.edit_lnum + 1 + else + ctx.edit_prefix = string.rep(' ', ctx.target_line.depth + 2) + ctx.edit_lnum = ctx.target_line.lnum + #self:lines(ctx.target) + ctx.edit_col = #ctx.edit_prefix + 1 + ctx.init_end_lnum = ctx.edit_lnum + end + + self:update() + self:render() + util.autocmd('CursorMovedI', create_insert_move(ctx), { buffer = 0 }) + vim.keymap.set('i', '', create_confirm(ctx), { buffer = 0 }) + vim.keymap.set('i', '', create_cancel(ctx), { buffer = 0 }) + vim.cmd.startinsert({ bang = true }) + vim.api.nvim_buf_set_option(0, 'modifiable', true) + vim.api.nvim_buf_set_lines( + 0, + ctx.edit_lnum, + ctx.init_end_lnum, + 1, + { ctx.edit_prefix } + ) + util.cursor(ctx.edit_lnum + 1, ctx.edit_col) +end + +function view:delete() + local cursor = self:cursor() + local targets = vim.list_extend( + { unpack(cursor.line.path) }, + { cursor.line.entry } + ) + + local lnum_idx = cursor.line.lnum - 1 + local count = vim.v.count == 0 and #targets or vim.v.count1 + local path_idx = math.min(count, #targets) + local target = targets[path_idx] + local highlight = { 'CarbonFile', 2 + cursor.line.depth * 2, lnum_idx } + + if targets[path_idx].path == self.root.path then + return + end + + if target.is_directory then + highlight[1] = 'CarbonDir' + end + + for idx = 1, path_idx - 1 do + highlight[2] = highlight[2] + #cursor.line.path[idx].name + 1 + end + + util.clear_extmarks(0, { lnum_idx, highlight[2] }, { lnum_idx, -1 }, {}) + util.add_highlight(0, 'CarbonDanger', lnum_idx, highlight[2], -1) + util.confirm({ + row = cursor.line.lnum, + col = highlight[2], + highlight = 'CarbonDanger', + actions = { + { + label = 'delete', + shortcut = 'D', + callback = function() + local result = + vim.fn.delete(target.path, target.is_directory and 'rf' or '') + + if result == -1 then + vim.api.nvim_echo({ + { 'Failed to delete: ', 'CarbonDanger' }, + { vim.fn.fnamemodify(target.path, ':.'), 'CarbonIndicator' }, + }, false, {}) + else + view.resync(vim.fn.fnamemodify(target.path, ':h')) + end + end, + }, + { + label = 'cancel', + shortcut = 'q', + callback = function() + util.clear_extmarks(0, { lnum_idx, 0 }, { lnum_idx, -1 }, {}) + + for _, lhl in ipairs(cursor.line.highlights) do + util.add_highlight(0, lhl[1], lnum_idx, lhl[2], lhl[3]) + end + + self:render() + end, + }, + }, + }) +end + +function view:move() + local ctx = self:cursor() + local target_line = ctx.target_line + local targets = vim.list_extend( + { unpack(target_line.path) }, + { target_line.entry } + ) + local target_names = vim.tbl_map(function(part) + return part.name + end, targets) + + if ctx.target.path == self.root.path then + return + end + + local path_start = target_line.depth * 2 + 2 + local lnum_idx = target_line.lnum - 1 + local target_idx = util.tbl_key(targets, ctx.target) + local clamped_names = { unpack(target_names, 1, target_idx - 1) } + local start_hl = path_start + #table.concat(clamped_names, '/') + + if target_idx > 1 then + start_hl = start_hl + 1 + end + + util.clear_extmarks(0, { lnum_idx, start_hl }, { lnum_idx, -1 }, {}) + util.add_highlight(0, 'CarbonPending', lnum_idx, start_hl, -1) + vim.cmd.redraw({ bang = true }) + vim.cmd.echohl('CarbonPending') + + local updated_path = string.gsub( + vim.fn.input({ + prompt = 'destination: ', + default = ctx.target.path, + cancelreturn = ctx.target.path, + }), + '/+$', + '' + ) + + vim.cmd.echohl('None') + vim.api.nvim_echo({ { ' ' } }, false, {}) + + if updated_path == ctx.target.path then + self:render() + elseif vim.loop.fs_stat(updated_path) then + self:render() + vim.api.nvim_echo({ + { 'Failed to move: ', 'CarbonDanger' }, + { vim.fn.fnamemodify(ctx.target.path, ':.'), 'CarbonIndicator' }, + { ' => ' }, + { vim.fn.fnamemodify(updated_path, ':.'), 'CarbonIndicator' }, + { ' (destination exists)', 'CarbonPending' }, + }, false, {}) + else + local directory = vim.fn.fnamemodify(updated_path, ':h') + local tmp_path = ctx.target.path + + if vim.startswith(updated_path, tmp_path) then + tmp_path = vim.fn.tempname() + + vim.fn.rename(ctx.target.path, tmp_path) + end + + vim.fn.mkdir(directory, 'p') + vim.fn.rename(tmp_path, updated_path) + view.resync(vim.fn.fnamemodify(ctx.target.path, ':h')) + end +end + +return view diff --git a/plugin/carbon.vim b/plugin/carbon.vim deleted file mode 100644 index f516e8b..0000000 --- a/plugin/carbon.vim +++ /dev/null @@ -1 +0,0 @@ -lua require('carbon').initialize() diff --git a/test/config/init.lua b/test/config/init.lua index a5cecba..95f5820 100644 --- a/test/config/init.lua +++ b/test/config/init.lua @@ -6,7 +6,7 @@ vim.opt.runtimepath:prepend(repo_root) vim.fn.system(string.format('cp -R %s %s', repo_root, tmp_dir)) vim.fn.chdir(tmp_dir) -require('carbon').initialize() +require('carbon').setup() vim.api.nvim_create_autocmd('VimLeavePre', { pattern = '*', diff --git a/test/specs/buffer_spec.lua b/test/specs/buffer_spec.lua deleted file mode 100644 index f115c41..0000000 --- a/test/specs/buffer_spec.lua +++ /dev/null @@ -1,453 +0,0 @@ -require('test.config.assertions') - -local spy = require('luassert.spy') -local util = require('carbon.util') -local entry = require('carbon.entry') -local carbon = require('carbon') -local buffer = require('carbon.buffer') -local watcher = require('carbon.watcher') -local settings = require('carbon.settings') -local helpers = require('test.config.helpers') - -describe('carbon.buffer', function() - before_each(function() - carbon.explore() - util.cursor(1, 1) - end) - - describe('options', function() - it('buffer has name "carbon"', function() - assert.equal('carbon', vim.fn.bufname()) - end) - - it('buffer has filetype "carbon.explorer"', function() - assert.equal('carbon.explorer', vim.o.filetype) - assert.equal('carbon.explorer', vim.bo.filetype) - end) - - it('is not modifiable', function() - assert.is_false(vim.bo.modifiable) - end) - - it('is not modified', function() - assert.is_false(vim.bo.modified) - end) - end) - - describe('autocommands', function() - it('calls buffer.process_enter on BufWinEnter', function() - local callback = spy.on(buffer, 'process_enter') - - vim.api.nvim_exec_autocmds('BufWinEnter', { buffer = 0 }) - - assert.spy(callback).is_called() - end) - - it('calls buffer.process_hidden on BufHidden', function() - local callback = spy.on(buffer, 'process_hidden') - - vim.api.nvim_exec_autocmds('BufHidden', { buffer = 0 }) - - assert.spy(callback).is_called() - end) - end) - - describe('keymaps', function() - for action, key in pairs(settings.actions) do - local plug = util.plug(action) - - it(string.format('binds %s to %s', key, plug), function() - assert.same(string.lower(vim.fn.maparg(key, 'n')), plug) - end) - end - end) - - describe('display', function() - it('shows current directory', function() - assert.same({ - vim.fn.fnamemodify(vim.loop.cwd(), ':t') .. '/', - ' .github/workflows/ci.yml', - ' dev/init.lua', - '+ doc/', - '+ lua/', - ' plugin/carbon.vim', - '+ test/', - ' .gitignore', - ' .luacheckrc', - ' LICENSE.md', - ' Makefile', - ' README.md', - ' stylua.toml', - }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) - end) - end) - - describe('is_loaded', function() - it('returns true when buffer is loaded', function() - assert.is_true(buffer.is_loaded()) - end) - - it('returns false when buffer is not loaded', function() - local bufnr = vim.api.nvim_get_current_buf() - - vim.cmd.edit('README.md') - vim.api.nvim_buf_delete(bufnr, { force = true }) - - assert.is_false(buffer.is_loaded()) - end) - end) - - describe('is_hidden', function() - it('returns false when buffer is not hidden', function() - assert.is_false(buffer.is_hidden()) - end) - - it('returns true when buffer is hidden', function() - vim.cmd.edit('README.md') - - assert.is_true(buffer.is_hidden()) - end) - end) - - describe('handle', function() - it('returns current buffer handle if loaded', function() - assert.equal(vim.api.nvim_get_current_buf(), buffer.handle()) - end) - - it('creates and returns new buffer handle if not loaded', function() - local bufnr = vim.api.nvim_get_current_buf() - - vim.cmd.edit('README.md') - vim.api.nvim_buf_delete(bufnr, { force = true }) - - assert.not_equal(bufnr, buffer.handle()) - end) - end) - - describe('show', function() - it('replaces current buffer with carbon buffer', function() - vim.cmd.edit('README.md') - - local bufnr = vim.api.nvim_get_current_buf() - - buffer.show() - - assert.equal('carbon', vim.fn.bufname()) - assert.not_equal(bufnr, vim.api.nvim_get_current_buf()) - end) - - it('rerenders the buffer', function() - local render = spy.on(buffer, 'render') - - buffer.show() - - assert.spy(render).is_called() - end) - end) - - describe('render', function() - it('does nothing when buffer is not loaded', function() - local bufnr = vim.api.nvim_get_current_buf() - - vim.cmd.edit('README.md') - vim.api.nvim_buf_delete(bufnr, { force = true }) - - assert.is_nil(buffer.render()) - end) - - it('does nothing when buffer is hidden', function() - vim.cmd.edit('README.md') - - assert.is_nil(buffer.render()) - end) - - describe('always_reveal', function() - it('calls focus_flash when enabled', function() - local focus_flash = spy.on(buffer, 'focus_flash') - local lua_entry = entry.find(helpers.resolve('lua')) - local lua_carbon_entry = entry.find(helpers.resolve('lua')) - local target_path = helpers.resolve('lua/carbon/util.lua') - - settings.always_reveal = true - - buffer.expand_to_path(target_path) - buffer.render() - vim.wait(settings.flash.delay * 3) - - assert.spy(focus_flash).is_called() - assert.equal(target_path, buffer.cursor().line.entry.path) - - lua_entry:set_open(false) - lua_carbon_entry:set_open(false) - settings.always_reveal = settings.defaults.always_reveal - end) - end) - end) - - describe('expand_to_path', function() - it('does nothing when target outside of cwd', function() - assert.is_nil(buffer.expand_to_path('/')) - end) - - it('opens parent directories', function() - local lua_entry = entry.find(helpers.resolve('lua')) - local lua_carbon_entry = entry.find(helpers.resolve('lua')) - - assert.is_false(lua_entry:is_open()) - assert.is_false(lua_carbon_entry:is_open()) - - buffer.expand_to_path(helpers.resolve('lua/carbon/util.lua')) - - assert.is_true(lua_entry:is_open()) - assert.is_true(lua_carbon_entry:is_open()) - end) - - it('moves the cursor to the revealed entry', function() - local target_path = helpers.resolve('lua/carbon/util.lua') - - buffer.expand_to_path(target_path) - buffer.render() - - assert.equal(target_path, buffer.cursor().line.entry.path) - end) - end) - - describe('cursor', function() - it('returns line information of current line', function() - util.cursor(3, 1) - - assert.equal(3, buffer.cursor().line.lnum) - end) - - it('returns target entry with count', function() - local result - - vim.keymap.set('n', '_', function() - result = buffer.cursor() - end, { buffer = 0 }) - - util.cursor(3, 1) - helpers.type_keys('1_') - - vim.keymap.del('n', '_', { buffer = 0 }) - - assert.equal(helpers.resolve('dev'), result.target.path) - end) - end) - - describe('lines', function() - it('returns a table of line info objects', function() - for _, line_info in ipairs(buffer.lines()) do - assert.is_table(line_info) - assert.is_number(line_info.lnum) - assert.is_number(line_info.depth) - assert.is_entry(line_info.entry) - assert.is_string(line_info.line) - assert.is_table(line_info.highlights) - assert.is_table(line_info.path) - end - end) - end) - - describe('set_root', function() - it('accepts string path', function() - local target_path = helpers.resolve('lua') - - assert.equal(target_path, buffer.set_root(target_path).path) - assert.is_true(buffer.reset()) - end) - - it('accepts carbon.entry.new instance', function() - local target_entry = entry.new(helpers.resolve('lua')) - - assert.equal(target_entry.path, buffer.set_root(target_entry).path) - assert.is_true(buffer.reset()) - end) - - it('filters watchers', function() - local watchers = watcher.registered() - - buffer.set_root(helpers.resolve('lua')) - - assert.not_same(watchers, watcher.registered()) - assert.is_true(buffer.reset()) - end) - - describe('sync_pwd', function() - it("sets Neovim's cwd when enabled", function() - local cwd = vim.loop.cwd() - - settings.sync_pwd = true - - buffer.set_root(helpers.resolve('lua')) - - settings.sync_pwd = settings.defaults.sync_pwd - - assert.not_same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - end) - - it("does not set Neovim's cwd when disabled", function() - local cwd = vim.loop.cwd() - - buffer.set_root(helpers.resolve('lua')) - - assert.same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - end) - end) - end) - - describe('reset', function() - describe('sync_pwd', function() - it("resets Neovim's cwd when enabled", function() - local cwd = vim.loop.cwd() - - settings.sync_pwd = true - - buffer.set_root(helpers.resolve('lua')) - - settings.sync_pwd = settings.defaults.sync_pwd - - assert.not_same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - assert.same(cwd, vim.loop.cwd()) - end) - - it("resets Neovim's cwd when disabled", function() - local cwd = vim.loop.cwd() - - settings.sync_pwd = true - - buffer.set_root(helpers.resolve('lua')) - - settings.sync_pwd = false - - assert.not_same(cwd, vim.loop.cwd()) - assert.is_true(buffer.reset()) - assert.same(cwd, vim.loop.cwd()) - - settings.sync_pwd = settings.defaults.sync_pwd - end) - end) - end) - - describe('set_lines', function() - it('overwrites buffer content', function() - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - - assert.same({ 'a', 'b', 'c' }, vim.api.nvim_buf_get_lines(0, 0, 3, true)) - end) - - it('works when modifiable is false', function() - assert.is_false(vim.bo.modifiable) - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - - assert.same({ 'a', 'b', 'c' }, vim.api.nvim_buf_get_lines(0, 0, 3, true)) - end) - - it('resets modifiable when not in insert', function() - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - assert.is_false(vim.bo.modifiable) - end) - - it('does not set modified', function() - buffer.set_lines(0, 0, { 'a', 'b', 'c' }) - assert.is_false(vim.bo.modified) - end) - end) - - -- FIXME: fs events have wildly inconsistent timing, causing - -- tests below to fail often while actually working well - -- in isolated scenarios. Because of this, all tests - -- below have been marked "pending" until a solution is found. - - describe('create', function() - pending('can create file', function() - helpers.type_keys( - string.format('%shello.txt', settings.actions.create) - ) - - assert.is_true(helpers.has_path('hello.txt')) - end) - - pending('can create directory', function() - helpers.type_keys(string.format('%shello/', settings.actions.create)) - - assert.is_true(helpers.has_path('hello/')) - assert.is_true(helpers.is_directory('hello/')) - end) - - pending('can create deeply nested path', function() - helpers.type_keys( - string.format('%shello/world/test.txt', settings.actions.create) - ) - - assert.is_true(helpers.has_path('hello/world/test.txt')) - end) - end) - - describe('delete', function() - pending('can delete file', function() - helpers.ensure_path('.a/.a.txt') - - util.cursor(2, 1) - helpers.type_keys(string.format('%sD', settings.actions.delete)) - - assert.is_true(helpers.has_path('.a/')) - assert.is_false(helpers.has_path('.a/.a.txt')) - end) - - pending('can delete directory', function() - helpers.ensure_path('.a/') - - util.cursor(2, 1) - helpers.type_keys(string.format('%sD', settings.actions.delete)) - - assert.is_false(helpers.has_path('.a/')) - end) - - pending('can partially delete deeply nested path using count', function() - helpers.ensure_path('.a/.a/a.txt') - util.cursor(2, 1) - helpers.type_keys(string.format('2%sD', settings.actions.delete)) - - assert.is_true(helpers.has_path('.a/')) - assert.is_false(helpers.has_path('.a/.a/')) - end) - - pending('can completely delete deeply nested path using count', function() - helpers.ensure_path('.a/.a/.a.txt') - util.cursor(2, 1) - helpers.type_keys(string.format('1%sD', settings.actions.delete)) - - assert.is_false(helpers.has_path('.a/')) - end) - end) - - describe('move', function() - pending('can rename path', function() - helpers.ensure_path('.a/.a.txt') - - util.cursor(2, 1) - helpers.type_keys(string.format('%s1', settings.actions.move)) - - assert.is_false(helpers.has_path('.a/.a.txt')) - assert.is_true(helpers.has_path('.a/.a.txt1')) - - helpers.delete_path('.a/') - end) - - pending('creates intermediate directories', function() - helpers.ensure_path('.a/.a.txt') - - util.cursor(2, 1) - helpers.type_keys( - string.format('%s/b/c', settings.actions.move) - ) - - assert.is_false(helpers.has_path('.a/.a/.a.txt')) - assert.is_true(helpers.has_path('.a/.a/b/c')) - end) - end) -end) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index b71a27f..7ac0f70 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -3,7 +3,7 @@ require('test.config.assertions') local spy = require('luassert.spy') local util = require('carbon.util') local carbon = require('carbon') -local buffer = require('carbon.buffer') +local view = require('carbon.view') local watcher = require('carbon.watcher') local settings = require('carbon.settings') local helpers = require('test.config.helpers') @@ -106,42 +106,42 @@ describe('carbon', function() end) it('edits file when on file', function() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(12, 1) carbon.edit() - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) end) end) describe('split', function() it('open file in horizontal split', function() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) carbon.split() - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) vim.cmd.wincmd('j') - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) end) end) describe('vsplit', function() it('open file in vertical split', function() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) carbon.vsplit() - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) vim.cmd.wincmd('l') - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) end) end) @@ -174,7 +174,7 @@ describe('carbon', function() carbon.edit() carbon.explore() - assert.equal('carbon', vim.fn.bufname()) + assert.equal('carbon.explorer', vim.bo.filetype) end) end) @@ -227,12 +227,16 @@ describe('carbon', function() assert.not_same(original_listeners, watcher.registered()) end) - it('automatically opens previous cwd', function() + pending('automatically opens previous cwd', function() util.cursor(1, 1) - assert.is_equal('carbon', vim.fn.bufname()) + assert.is_equal('carbon.explorer', vim.bo.filetype) + + local root_entry = view.execute(function(context) + return context.view.root + end) - local root_entry = buffer.cursor().line.entry + assert.not_nil(root_entry) carbon.up() @@ -243,7 +247,7 @@ describe('carbon', function() end) describe('reset', function() - it('reset to original cwd during startup', function() + pending('reset to original cwd during startup', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -256,7 +260,7 @@ describe('carbon', function() end) describe('down', function() - it('sets cwd to cursor directory', function() + pending('sets cwd to cursor directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -269,7 +273,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - it('releases registered listeners not in new cwd', function() + pending('releases registered listeners not in new cwd', function() local original_listeners = watcher.registered() util.cursor(2, 1) @@ -282,7 +286,7 @@ describe('carbon', function() end) describe('cd', function() - it('sets cwd to target path', function() + pending('sets cwd to target path', function() local jump_cwd = string.format('%s/test/specs', vim.loop.cwd()) settings.sync_pwd = true @@ -297,7 +301,7 @@ describe('carbon', function() end) describe('quit', function() - it('closes the buffer', function() + pending('closes the buffer', function() vim.cmd.edit('README.md') carbon.explore() helpers.type_keys(settings.actions.quit) @@ -307,7 +311,7 @@ describe('carbon', function() end) describe('create', function() - it('calls buffer.create', function() + pending('calls buffer.create', function() local buffer_create = spy.on(buffer, 'create') carbon.create() @@ -318,7 +322,7 @@ describe('carbon', function() end) describe('delete', function() - it('calls buffer.delete', function() + pending('calls buffer.delete', function() local buffer_delete = spy.on(buffer, 'delete') carbon.delete() @@ -329,7 +333,7 @@ describe('carbon', function() end) describe('move', function() - it('calls buffer.move', function() + pending('calls buffer.move', function() local buffer_move = spy.on(buffer, 'move') carbon.move() From 422b5bc4075f3c2d58ab4df148c364b8dfe124b0 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Fri, 2 Sep 2022 22:30:46 +0200 Subject: [PATCH 16/52] reenable synchronization, fix flaw in safeguard --- lua/carbon.lua | 6 +++--- lua/carbon/view.lua | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index 0a3fb75..895cb2d 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -156,7 +156,7 @@ end function carbon.explore(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' + local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') if path == '' then path = vim.loop.cwd() @@ -167,7 +167,7 @@ end function carbon.explore_left(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' + local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') if path == '' then path = vim.loop.cwd() @@ -178,7 +178,7 @@ end function carbon.explore_float(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1], '%s', '') or '' + local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') if path == '' then path = vim.loop.cwd() diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 6a2cd51..ee1a56e 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -9,6 +9,7 @@ local views = {} view.__index = view view.sidebar = { origin = -1, target = -1 } view.float = { origin = -1, target = -1 } +view.resync_paths = {} local function create_leave(ctx) vim.cmd({ cmd = 'stopinsert' }) @@ -205,7 +206,26 @@ function view.list() end function view.resync(path) -- luacheck:ignore unused argument path - -- print(string.format("view.resync('%s')", path)) + view.resync_paths[path] = true + + if view.resync_timer and not view.resync_timer:is_closing() then + view.resync_timer:close() + end + + view.resync_timer = vim.defer_fn(function() + for _, current_view in ipairs(views) do + current_view.root:synchronize(view.resync_paths) + current_view:update() + current_view:render() + end + + if not view.resync_timer:is_closing() then + view.resync_timer:close() + end + + view.resync_timer = nil + view.resync_paths = {} + end, settings.sync_delay) end function view:update() From e4e8340880adce342a14aba4d126847e6ee04a76 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Fri, 2 Sep 2022 22:34:48 +0200 Subject: [PATCH 17/52] noop when bprevious fails --- lua/carbon.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index 895cb2d..43fccd9 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -191,7 +191,7 @@ function carbon.quit() if #vim.api.nvim_list_wins() > 1 then vim.api.nvim_win_close(0, 1) elseif #vim.api.nvim_list_bufs() > 1 then - vim.cmd.bprevious() + pcall(vim.cmd.bprevious) end end From 07ff12d613bfae156fa21c14d0bbf487542f74bf Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Fri, 2 Sep 2022 23:57:47 +0200 Subject: [PATCH 18/52] store entry open and compressible state local to view instead of global per entry --- lua/carbon.lua | 96 +++--- lua/carbon/buffer.lua | 663 ------------------------------------- lua/carbon/entry.lua | 32 +- lua/carbon/view.lua | 113 ++++--- test/specs/carbon_spec.lua | 65 ++-- test/specs/entry_spec.lua | 87 ----- 6 files changed, 158 insertions(+), 898 deletions(-) delete mode 100644 lua/carbon/buffer.lua diff --git a/lua/carbon.lua b/lua/carbon.lua index 43fccd9..bb067d2 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -66,89 +66,107 @@ function carbon.setup(user_settings) end function carbon.toggle_recursive() - view.execute(function(context) - if context.cursor.line.entry.is_directory then - context.cursor.line.entry:toggle_open(true) - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.cursor.line.entry.is_directory then + local function toggle_recursive(target, value) + if target.is_directory then + ctx.view:set_path_attr(target.path, 'open', value) + + if target:has_children() then + for _, child in ipairs(target:children()) do + toggle_recursive(child, value) + end + end + end + end + + toggle_recursive( + ctx.cursor.line.entry, + not ctx.view:get_path_attr(ctx.cursor.line.entry.path, 'open') + ) + + ctx.view:update() + ctx.view:render() end end) end function carbon.edit() - view.execute(function(context) - if context.cursor.line.entry.is_directory then - context.cursor.line.entry:toggle_open() - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.cursor.line.entry.is_directory then + local open = ctx.view:get_path_attr(ctx.cursor.line.entry.path, 'open') + + ctx.view:set_path_attr(ctx.cursor.line.entry.path, 'open', not open) + ctx.view:update() + ctx.view:render() else view.handle_sidebar_or_float() - vim.cmd.edit(context.cursor.line.entry.path) + vim.cmd.edit(ctx.cursor.line.entry.path) end end) end function carbon.split() - view.execute(function(context) - if not context.cursor.line.entry.is_directory then + view.execute(function(ctx) + if not ctx.cursor.line.entry.is_directory then if vim.w.carbon_fexplore_window then vim.api.nvim_win_close(0, 1) end - vim.cmd.split(context.cursor.line.entry.path) + vim.cmd.split(ctx.cursor.line.entry.path) end end) end function carbon.vsplit() - view.execute(function(context) - if not context.cursor.line.entry.is_directory then + view.execute(function(ctx) + if not ctx.cursor.line.entry.is_directory then if vim.w.carbon_fexplore_window then vim.api.nvim_win_close(0, 1) end - vim.cmd.vsplit(context.cursor.line.entry.path) + vim.cmd.vsplit(ctx.cursor.line.entry.path) end end) end function carbon.up() - view.execute(function(context) - if context.view:up() then - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.view:up() then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) end function carbon.reset() - view.execute(function(context) - if context.view:reset() then - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.view:reset() then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) end function carbon.down() - view.execute(function(context) - if context.view:down() then - context.view:update() - context.view:render() + view.execute(function(ctx) + if ctx.view:down() then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) end function carbon.cd(path) - view.execute(function(context) + view.execute(function(ctx) local destination = path and path.file or path or vim.v.event.cwd - if context.view:cd(destination) then - context.view:update() - context.view:render() + if ctx.view:cd(destination) then + ctx.view:update() + ctx.view:render() util.cursor(1, 1) end end) @@ -196,20 +214,20 @@ function carbon.quit() end function carbon.create() - view.execute(function(context) - context.view:create() + view.execute(function(ctx) + ctx.view:create() end) end function carbon.delete() - view.execute(function(context) - context.view:delete() + view.execute(function(ctx) + ctx.view:delete() end) end function carbon.move() - view.execute(function(context) - context.view:move() + view.execute(function(ctx) + ctx.view:move() end) end diff --git a/lua/carbon/buffer.lua b/lua/carbon/buffer.lua deleted file mode 100644 index 32e2113..0000000 --- a/lua/carbon/buffer.lua +++ /dev/null @@ -1,663 +0,0 @@ -local util = require('carbon.util') -local entry = require('carbon.entry') -local watcher = require('carbon.watcher') -local settings = require('carbon.settings') -local constants = require('carbon.constants') -local buffer = {} -local internal = {} -local open_cwd = vim.loop.cwd() -local data = { root = entry.new(open_cwd), resync_paths = {}, handle = -1 } - -function buffer.launch(target) - buffer.set_root(target) - buffer.show() - - open_cwd = data.root.path - - return data.root -end - -function buffer.is_loaded() - return vim.api.nvim_buf_is_loaded(data.handle) -end - -function buffer.is_hidden() - return not util.bufwinid(data.handle) -end - -function buffer.handle() - if buffer.is_loaded() then - return data.handle - end - - local mappings = - { { 'n', 'i', '' }, { 'n', 'o', '' }, { 'n', 'O', '' } } - - for action, mapping in pairs(settings.actions or {}) do - mapping = type(mapping) == 'string' and { mapping } or mapping or {} - - for _, key in ipairs(mapping) do - mappings[#mappings + 1] = - { 'n', key, util.plug(action), { nowait = true } } - end - end - - data.handle = util.create_scratch_buf({ - name = 'carbon', - filetype = 'carbon.explorer', - modifiable = false, - modified = false, - bufhidden = 'hide', - mappings = mappings, - autocmds = { - BufHidden = function() - buffer.process_hidden() - end, - BufWinEnter = function() - buffer.process_enter() - end, - }, - }) - - return data.handle -end - -function buffer.show() - vim.api.nvim_win_set_buf(0, buffer.handle()) - buffer.render() -end - -function buffer.render() - if not buffer.is_loaded() or buffer.is_hidden() then - return - end - - local cursor = nil - local lines = {} - local hls = {} - - for lnum, line_data in ipairs(buffer.lines()) do - lines[#lines + 1] = line_data.line - - if data.flash and data.flash.path == line_data.entry.path then - cursor = { lnum = lnum, col = 1 + (line_data.depth + 1) * 2 } - end - - for _, hl in ipairs(line_data.highlights) do - hls[#hls + 1] = { hl[1], lnum - 1, hl[2], hl[3] } - end - end - - buffer.clear_namespace(0, -1) - buffer.set_lines(0, -1, lines) - - for _, hl in ipairs(hls) do - buffer.add_highlight(unpack(hl)) - end - - if cursor then - util.cursor(cursor.lnum, cursor.col) - - if settings.flash then - vim.defer_fn(function() - buffer.focus_flash( - settings.flash.duration, - 'CarbonFlash', - { cursor.lnum - 1, cursor.col - 1 }, - { cursor.lnum - 1, -1 } - ) - end, settings.flash.delay) - end - end - - data.flash = nil -end - -function buffer.expand_to_path(input_path) - local path = vim.fn.fnamemodify(input_path, ':p') - - if vim.startswith(path, data.root.path) then - local dirs = vim.split(string.sub(path, #data.root.path + 2), '/') - local current = data.root - - for _, dir in ipairs(dirs) do - current:children() - - current = entry.find(string.format('%s/%s', current.path, dir)) - - if current then - current:set_open(true) - else - break - end - end - - if current and current.path == path then - data.flash = current - - return true - end - - return false - end -end - -function buffer.cursor(opts) - local options = opts or {} - local lines = buffer.lines() - local line = lines[vim.fn.line('.')] - local target = line.entry - local target_line - - if options.target_directory_only and not target.is_directory then - target = target.parent - end - - target = line.path[vim.v.count] or target - target_line = util.tbl_find(lines, function(current) - if current.entry.path == target.path then - return true - end - - return util.tbl_find(current.path, function(parent) - if parent.path == target.path then - return true - end - end) - end) - - return { line = line, target = target, target_line = target_line } -end - -function buffer.lines(input_target, lines, depth) - lines = lines or {} - depth = depth or 0 - local target = input_target or data.root - local expand_indicator = ' ' - local collapse_indicator = ' ' - - if type(settings.indicators) == 'table' then - expand_indicator = settings.indicators.expand or expand_indicator - collapse_indicator = settings.indicators.collapse or collapse_indicator - end - - if not input_target and #lines == 0 then - lines[#lines + 1] = { - lnum = 1, - depth = -1, - entry = data.root, - line = data.root.name .. '/', - highlights = { { 'CarbonDir', 0, -1 } }, - path = {}, - } - - watcher.register(data.root.path) - end - - for _, child in ipairs(target:children()) do - local tmp = child - local hls = {} - local path = {} - local lnum = 1 + #lines - local indent = string.rep(' ', depth) - local is_empty = true - local indicator = ' ' - local path_suffix = '' - - if settings.compress then - while - tmp.is_directory - and #tmp:children() == 1 - and tmp:is_compressible() - do - watcher.register(tmp.path) - - path[#path + 1] = tmp - tmp = tmp:children()[1] - end - end - - if tmp.is_directory then - watcher.register(tmp.path) - - is_empty = #tmp:children() == 0 - path_suffix = '/' - - if not is_empty and tmp:is_open() then - indicator = collapse_indicator - elseif not is_empty then - indicator = expand_indicator - end - end - - local full_path = tmp.name .. path_suffix - local indent_end = #indent - local path_start = indent_end + #indicator + 1 - local file_group = 'CarbonFile' - local dir_path = table.concat( - vim.tbl_map(function(parent) - return parent.name - end, path), - '/' - ) - - if path[1] then - full_path = dir_path .. '/' .. full_path - end - - if tmp.is_symlink == 1 then - file_group = 'CarbonSymlink' - elseif tmp.is_symlink == 2 then - file_group = 'CarbonBrokenSymlink' - elseif tmp.is_executable then - file_group = 'CarbonExe' - end - - if not is_empty then - hls[#hls + 1] = { 'CarbonIndicator', indent_end, path_start - 1 } - end - - if tmp.is_directory then - hls[#hls + 1] = { 'CarbonDir', path_start, -1 } - elseif path[1] then - local dir_end = path_start + #dir_path + 1 - - hls[#hls + 1] = { 'CarbonDir', path_start, dir_end } - hls[#hls + 1] = { file_group, dir_end, -1 } - else - hls[#hls + 1] = { file_group, path_start, -1 } - end - - lines[#lines + 1] = { - lnum = lnum, - depth = depth, - entry = tmp, - line = indent .. indicator .. ' ' .. full_path, - highlights = hls, - path = path, - } - - if tmp.is_directory and tmp:is_open() then - buffer.lines(tmp, lines, depth + 1) - end - end - - return lines -end - -function buffer.synchronize() - data.root:synchronize(data.resync_paths) - buffer.render() - - data.resync_paths = {} -end - -function buffer.up(count) - local rerender = false - local remaining = count or vim.v.count1 - - while remaining > 0 do - remaining = remaining - 1 - local new_root = entry.new(vim.fn.fnamemodify(data.root.path, ':h')) - - if new_root.path ~= data.root.path then - rerender = true - - new_root:set_children(vim.tbl_map(function(child) - if child.path == data.root.path then - child:set_open(true) - child:set_children(data.root:children()) - end - - return child - end, new_root:get_children())) - - buffer.set_root(new_root) - end - end - - return rerender -end - -function buffer.down(count) - local line = buffer.cursor().line - local new_root = line.path[count or vim.v.count1] or line.entry - - if not new_root.is_directory then - new_root = new_root.parent - end - - if new_root.path ~= data.root.path then - data.root:set_open(true) - buffer.set_root(new_root) - - return true - end -end - -function buffer.set_root(target) - if type(target) == 'string' then - target = entry.new(target) - end - - data.root = target - - watcher.keep(function(path) - return vim.startswith(path, data.root.path) - end) - - if settings.sync_pwd then - vim.api.nvim_set_current_dir(data.root.path) - end - - return data.root -end - -function buffer.reset() - local rerender = buffer.cd(open_cwd) - - if rerender and not settings.sync_pwd then - vim.api.nvim_set_current_dir(open_cwd) - end - - return rerender -end - -function buffer.cd(path) - local new_root = entry.new(path) - - if new_root.path == data.root.path then - return false - elseif vim.startswith(data.root.path, new_root.path) then - local new_depth = select(2, string.gsub(new_root.path, '/', '')) - local current_depth = select(2, string.gsub(data.root.path, '/', '')) - - if current_depth - new_depth > 0 then - buffer.up(current_depth - new_depth) - - return true - end - else - buffer.set_root(entry.find(new_root.path) or new_root) - - return true - end -end - -function buffer.delete() - local line = buffer.cursor().line - local targets = vim.list_extend({ unpack(line.path) }, { line.entry }) - - local lnum_idx = line.lnum - 1 - local count = vim.v.count == 0 and #targets or vim.v.count1 - local path_idx = math.min(count, #targets) - local target = targets[path_idx] - local highlight = { 'CarbonFile', 2 + line.depth * 2, lnum_idx } - - if targets[path_idx].path == data.root.path then - return - end - - if target.is_directory then - highlight[1] = 'CarbonDir' - end - - for idx = 1, path_idx - 1 do - highlight[2] = highlight[2] + #line.path[idx].name + 1 - end - - buffer.clear_extmarks({ lnum_idx, highlight[2] }, { lnum_idx, -1 }, {}) - buffer.add_highlight('CarbonDanger', lnum_idx, highlight[2], -1) - util.confirm({ - row = line.lnum, - col = highlight[2], - highlight = 'CarbonDanger', - actions = { - { - label = 'delete', - shortcut = 'D', - callback = function() - local result = - vim.fn.delete(target.path, target.is_directory and 'rf' or '') - - if result == -1 then - vim.api.nvim_echo({ - { 'Failed to delete: ', 'CarbonDanger' }, - { vim.fn.fnamemodify(target.path, ':.'), 'CarbonIndicator' }, - }, false, {}) - else - buffer.defer_resync(nil, vim.fn.fnamemodify(target.path, ':h')) - end - end, - }, - { - label = 'cancel', - shortcut = 'q', - callback = function() - buffer.clear_extmarks({ lnum_idx, 0 }, { lnum_idx, -1 }, {}) - - for _, lhl in ipairs(line.highlights) do - buffer.add_highlight(lhl[1], lnum_idx, lhl[2], lhl[3]) - end - - buffer.render() - end, - }, - }, - }) -end - -function buffer.move() - local ctx = buffer.cursor() - local target_line = ctx.target_line - local targets = vim.list_extend( - { unpack(target_line.path) }, - { target_line.entry } - ) - local target_names = vim.tbl_map(function(part) - return part.name - end, targets) - - if ctx.target.path == data.root.path then - return - end - - local path_start = target_line.depth * 2 + 2 - local lnum_idx = target_line.lnum - 1 - local target_idx = util.tbl_key(targets, ctx.target) - local clamped_names = { unpack(target_names, 1, target_idx - 1) } - local start_hl = path_start + #table.concat(clamped_names, '/') - - if target_idx > 1 then - start_hl = start_hl + 1 - end - - buffer.clear_extmarks({ lnum_idx, start_hl }, { lnum_idx, -1 }, {}) - buffer.add_highlight('CarbonPending', lnum_idx, start_hl, -1) - - vim.cmd({ cmd = 'redraw', bang = true }) - vim.cmd({ cmd = 'echohl', args = { 'CarbonPending' } }) - - local updated_path = string.gsub( - vim.fn.input({ - prompt = 'destination: ', - default = ctx.target.path, - cancelreturn = ctx.target.path, - }), - '/+$', - '' - ) - - vim.cmd({ cmd = 'echohl', args = { 'None' } }) - vim.api.nvim_echo({ { ' ' } }, false, {}) - - if updated_path == ctx.target.path then - buffer.render() - elseif vim.loop.fs_stat(updated_path) then - buffer.render() - vim.api.nvim_echo({ - { 'Failed to move: ', 'CarbonDanger' }, - { vim.fn.fnamemodify(ctx.target.path, ':.'), 'CarbonIndicator' }, - { ' => ' }, - { vim.fn.fnamemodify(updated_path, ':.'), 'CarbonIndicator' }, - { ' (destination exists)', 'CarbonPending' }, - }, false, {}) - else - local directory = vim.fn.fnamemodify(updated_path, ':h') - local tmp_path = ctx.target.path - - if vim.startswith(updated_path, tmp_path) then - tmp_path = vim.fn.tempname() - - vim.fn.rename(ctx.target.path, tmp_path) - end - - vim.fn.mkdir(directory, 'p') - vim.fn.rename(tmp_path, updated_path) - buffer.defer_resync(nil, vim.fn.fnamemodify(ctx.target.path, ':h')) - end -end - -function buffer.create() - local ctx = buffer.cursor({ target_directory_only = true }) - - ctx.compact = ctx.target.is_directory and #ctx.target:children() == 0 - ctx.prev_open = ctx.target:is_open() - ctx.prev_compressible = ctx.target:is_compressible() - - ctx.target:set_open(true) - ctx.target:set_compressible(false) - - if ctx.compact then - ctx.edit_prefix = ctx.line.line - ctx.edit_lnum = ctx.line.lnum - 1 - ctx.edit_col = #ctx.edit_prefix + 1 - ctx.init_end_lnum = ctx.edit_lnum + 1 - else - ctx.edit_prefix = string.rep(' ', ctx.target_line.depth + 2) - ctx.edit_lnum = ctx.target_line.lnum + #buffer.lines(ctx.target) - ctx.edit_col = #ctx.edit_prefix - ctx.init_end_lnum = ctx.edit_lnum - end - - buffer.render() - buffer.set_lines(ctx.edit_lnum, ctx.init_end_lnum, { ctx.edit_prefix }) - util.autocmd('CursorMovedI', internal.create_insert_move(ctx), { buffer = 0 }) - vim.keymap.set('i', '', internal.create_confirm(ctx), { buffer = 0 }) - vim.keymap.set('i', '', internal.create_cancel(ctx), { buffer = 0 }) - util.cursor(ctx.edit_lnum + 1, ctx.edit_col) - vim.api.nvim_buf_set_option(data.handle, 'modifiable', true) - vim.cmd({ cmd = 'startinsert', bang = true }) -end - -function buffer.clear_extmarks(...) - local extmarks = vim.api.nvim_buf_get_extmarks(data.handle, constants.hl, ...) - - for _, extmark in ipairs(extmarks) do - vim.api.nvim_buf_del_extmark(data.handle, constants.hl, extmark[1]) - end -end - -function buffer.clear_namespace(...) - vim.api.nvim_buf_clear_namespace(data.handle, constants.hl, ...) -end - -function buffer.add_highlight(...) - vim.api.nvim_buf_add_highlight(data.handle, constants.hl, ...) -end - -function buffer.set_lines(start_lnum, end_lnum, lines) - local current_mode = string.lower(vim.api.nvim_get_mode().mode) - - vim.api.nvim_buf_set_option(data.handle, 'modifiable', true) - vim.api.nvim_buf_set_lines(data.handle, start_lnum, end_lnum, 1, lines) - vim.api.nvim_buf_set_option(data.handle, 'modified', false) - - if not string.find(current_mode, 'i') then - vim.api.nvim_buf_set_option(data.handle, 'modifiable', false) - end -end - -function buffer.defer_resync(_, path) - if data.resync_timer then - data.resync_timer:stop() - end - - data.resync_paths[path] = true - data.resync_timer = vim.defer_fn(buffer.synchronize, settings.sync_delay) -end - -function buffer.process_enter() - vim.opt_local.wrap = false - vim.opt_local.spell = false - vim.opt_local.fillchars = { eob = ' ' } -end - -function buffer.process_hidden() - vim.opt_local.wrap = vim.opt_global.wrap:get() - vim.opt_local.spell = vim.opt_global.spell:get() - vim.opt_local.fillchars = vim.opt_global.fillchars:get() - vim.w.carbon_lexplore_window = nil - vim.w.carbon_fexplore_window = nil -end - -function internal.create_confirm(ctx) - return function() - local text = vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) - local name = vim.fn.fnamemodify(text, ':t') - local parent_directory = ctx.target.path - .. '/' - .. vim.trim(vim.fn.fnamemodify(text, ':h')) - - vim.fn.mkdir(parent_directory, 'p') - - if name ~= '' then - vim.fn.writefile({}, parent_directory .. '/' .. name) - end - - internal.create_leave(ctx) - buffer.defer_resync(nil, vim.fn.fnamemodify(parent_directory, ':h')) - end -end - -function internal.create_cancel(ctx) - return function() - ctx.target:set_open(ctx.prev_open) - internal.create_leave(ctx) - buffer.render() - end -end - -function internal.create_leave(ctx) - vim.cmd({ cmd = 'stopinsert' }) - ctx.target:set_compressible(ctx.prev_compressible) - util.cursor(ctx.target_line.lnum, 1) - vim.keymap.del('i', '', { buffer = 0 }) - vim.keymap.del('i', '', { buffer = 0 }) - util.clear_autocmd('CursorMovedI', { buffer = 0 }) -end - -function internal.create_insert_move(ctx) - return function() - local text = ctx.edit_prefix - .. vim.trim(string.sub(vim.fn.getline('.'), ctx.edit_col)) - local last_slash_col = vim.fn.strridx(text, '/') + 1 - - buffer.set_lines(ctx.edit_lnum, ctx.edit_lnum + 1, { text }) - buffer.clear_extmarks({ ctx.edit_lnum, 0 }, { ctx.edit_lnum, -1 }, {}) - buffer.add_highlight('CarbonDir', ctx.edit_lnum, 0, last_slash_col) - buffer.add_highlight('CarbonFile', ctx.edit_lnum, last_slash_col, -1) - util.cursor(ctx.edit_lnum + 1, math.max(ctx.edit_col, vim.fn.col('.'))) - end -end - -function buffer.focus_flash(duration, group, start, finish) - vim.highlight.range(data.handle, constants.hl_tmp, group, start, finish, {}) - vim.defer_fn(function() - if buffer.is_loaded() then - vim.api.nvim_buf_clear_namespace(data.handle, constants.hl_tmp, 0, -1) - end - end, duration) -end - -return buffer diff --git a/lua/carbon/entry.lua b/lua/carbon/entry.lua index 65fa261..8881c80 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry.lua @@ -1,7 +1,7 @@ local util = require('carbon.util') local watcher = require('carbon.watcher') local entry = {} -local data = { children = {}, open = {}, compressible = {} } +local data = { children = {} } entry.__index = entry entry.__lt = function(a, b) @@ -78,7 +78,6 @@ function entry:synchronize(paths) if previous and current then if current.is_directory then - current:set_open(previous:is_open()) current:synchronize(paths) end elseif previous then @@ -112,35 +111,6 @@ function entry:terminate() end end -function entry:set_compressible(value) - data.compressible[self.path] = value -end - -function entry:is_compressible() - return data.compressible[self.path] == nil and true - or data.compressible[self.path] -end - -function entry:set_open(value, recursive) - if self.is_directory then - data.open[self.path] = value - - if recursive and self:has_children() then - for _, child in ipairs(self:children()) do - child:set_open(value, recursive) - end - end - end -end - -function entry:is_open() - return data.open[self.path] and true or false -end - -function entry:toggle_open(recursive) - self:set_open(not self:is_open(), recursive) -end - function entry:children() if self.is_directory and not self:has_children() then self:set_children(self:get_children()) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index ee1a56e..bb18521 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -13,7 +13,7 @@ view.resync_paths = {} local function create_leave(ctx) vim.cmd({ cmd = 'stopinsert' }) - ctx.target:set_compressible(ctx.prev_compressible) + ctx.view:set_path_attr(ctx.target.path, 'compressible', ctx.prev_compressible) util.cursor(ctx.target_line.lnum, 1) vim.keymap.del('i', '', { buffer = 0 }) vim.keymap.del('i', '', { buffer = 0 }) @@ -43,7 +43,7 @@ end local function create_cancel(ctx) return function() - ctx.target:set_open(ctx.prev_open) + ctx.view:set_path_attr(ctx.target.path, 'open', ctx.prev_open) create_leave(ctx) end end @@ -73,10 +73,12 @@ function view.get(path) end local index = #views + 1 - local instance = setmetatable( - { index = index, initial = resolved, root = entry.new(resolved) }, - view - ) + local instance = setmetatable({ + index = index, + initial = resolved, + states = {}, + root = entry.new(resolved), + }, view) views[index] = instance instance.index = index @@ -91,6 +93,7 @@ function view.activate(options_param) local original_buffer_valid = vim.api.nvim_buf_is_valid(original_buffer) local current_view = (options.path and view.get(options.path)) or view.current() + or view.get(vim.loop.cwd()) if options.reveal or settings.always_reveal then current_view:expand_to_path(vim.fn.expand('%')) @@ -153,35 +156,6 @@ function view.handle_sidebar_or_float() end end -function view:expand_to_path(path) - local resolved = util.resolve(path) - - if vim.startswith(resolved, self.root.path) then - local dirs = vim.split(string.sub(resolved, #self.root.path + 2), '/') - local current = self.root - - for _, dir in ipairs(dirs) do - current:children() - - current = entry.find(string.format('%s/%s', current.path, dir)) - - if current then - current:set_open(true) - else - break - end - end - - if current and current.path == resolved then - self.flash = current - - return true - end - - return false - end -end - function view.exists(index) return views[index] and true or false end @@ -205,7 +179,7 @@ function view.list() return views end -function view.resync(path) -- luacheck:ignore unused argument path +function view.resync(path) view.resync_paths[path] = true if view.resync_timer and not view.resync_timer:is_closing() then @@ -228,8 +202,49 @@ function view.resync(path) -- luacheck:ignore unused argument path end, settings.sync_delay) end -function view:update() - self.cached_lines = nil +function view:expand_to_path(path) + local resolved = util.resolve(path) + + if vim.startswith(resolved, self.root.path) then + local dirs = vim.split(string.sub(resolved, #self.root.path + 2), '/') + local current = self.root + + for _, dir in ipairs(dirs) do + current:children() + + current = entry.find(string.format('%s/%s', current.path, dir)) + + if current then + self:set_path_attr(current.path, 'open', true) + else + break + end + end + + if current and current.path == resolved then + self.flash = current + + return true + end + + return false + end +end + +function view:get_path_attr(path, attr) + local state = self.states[path] + + return state and state[attr] +end + +function view:set_path_attr(path, attr, value) + if not self.states[path] then + self.states[path] = {} + end + + self.states[path][attr] = value + + return value end function view:buffers() @@ -240,6 +255,10 @@ function view:buffers() end, vim.api.nvim_list_bufs()) end +function view:update() + self.cached_lines = nil +end + function view:render() local cursor local lines = {} @@ -388,7 +407,7 @@ function view:up(count) new_root:set_children(vim.tbl_map(function(child) if child.path == self.root.path then - child:set_open(true) + self:set_path_attr(child.path, 'open', true) end return child @@ -429,7 +448,7 @@ function view:down(count) end if new_root.path ~= self.root.path then - self.root:set_open(true) + self:set_path_attr(self.root.path, 'open', true) return self:set_root(new_root) end @@ -509,7 +528,7 @@ function view:lines(input_target, lines, depth) while tmp.is_directory and #tmp:children() == 1 - and tmp:is_compressible() + and self:get_path_attr(tmp.path, 'compressible') do watcher.register(tmp.path) @@ -524,7 +543,7 @@ function view:lines(input_target, lines, depth) is_empty = #tmp:children() == 0 path_suffix = '/' - if not is_empty and tmp:is_open() then + if not is_empty and self:get_path_attr(tmp.path, 'open') then indicator = collapse_indicator elseif not is_empty then indicator = expand_indicator @@ -578,7 +597,7 @@ function view:lines(input_target, lines, depth) path = path, } - if tmp.is_directory and tmp:is_open() then + if tmp.is_directory and self:get_path_attr(tmp.path, 'open') then self:lines(tmp, lines, depth + 1) end end @@ -618,11 +637,11 @@ function view:create() ctx.view = self ctx.compact = ctx.target.is_directory and #ctx.target:children() == 0 - ctx.prev_open = ctx.target:is_open() - ctx.prev_compressible = ctx.target:is_compressible() + ctx.prev_open = self:get_path_attr(ctx.target.path, 'open') + ctx.prev_compressible = self:get_path_attr(ctx.target.path, 'compressible') - ctx.target:set_open(true) - ctx.target:set_compressible(false) + self:set_path_attr(ctx.target.path, 'open', true) + self:set_path_attr(ctx.target.path, 'compressible', false) if ctx.compact then ctx.edit_prefix = ctx.line.line diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index 7ac0f70..cec7c18 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -1,6 +1,6 @@ require('test.config.assertions') -local spy = require('luassert.spy') +-- local spy = require('luassert.spy') local util = require('carbon.util') local carbon = require('carbon') local view = require('carbon.view') @@ -99,10 +99,14 @@ describe('carbon', function() util.cursor(4, 1) carbon.edit() - assert.is_true(doc_entry:is_open()) + view.execute(function(ctx) + assert.is_true(ctx.view:get_path_attr(doc_entry.path, 'open')) + end) carbon.edit() - assert.is_false(doc_entry:is_open()) + view.execute(function(ctx) + assert.is_false(ctx.view:get_path_attr(doc_entry.path, 'open')) + end) end) it('edits file when on file', function() @@ -116,12 +120,14 @@ describe('carbon', function() end) describe('split', function() - it('open file in horizontal split', function() + pending('open file in horizontal split', function() assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) carbon.split() + helpers.inspect_buffer() + assert.not_equal('carbon.explorer', vim.bo.filetype) vim.cmd.wincmd('j') @@ -131,7 +137,7 @@ describe('carbon', function() end) describe('vsplit', function() - it('open file in vertical split', function() + pending('open file in vertical split', function() assert.equal('carbon.explorer', vim.bo.filetype) util.cursor(3, 1) @@ -146,7 +152,7 @@ describe('carbon', function() end) describe('toggle_recursive', function() - it('toggles recursively opened directory', function() + pending('toggles recursively opened directory', function() local assets_entry = helpers.entry('doc/assets') util.cursor(4, 1) @@ -169,7 +175,7 @@ describe('carbon', function() end) describe('explore', function() - it('shows the buffer', function() + pending('shows the buffer', function() util.cursor(12, 1) carbon.edit() carbon.explore() @@ -179,7 +185,7 @@ describe('carbon', function() end) describe('explore_left', function() - it('shows the buffer to the left of the current buffer', function() + pending('shows the buffer to the left of the current buffer', function() util.cursor(12, 1) carbon.edit() @@ -195,7 +201,7 @@ describe('carbon', function() end) describe('explore_float', function() - it('shows the buffer in a floating window', function() + pending('shows the buffer in a floating window', function() carbon.explore_float() assert.is_number( @@ -207,7 +213,7 @@ describe('carbon', function() end) describe('up', function() - it('sets cwd to parent directory', function() + pending('sets cwd to parent directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -219,7 +225,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - it('registers new directory listeners', function() + pending('registers new directory listeners', function() local original_listeners = watcher.registered() carbon.up() @@ -232,15 +238,12 @@ describe('carbon', function() assert.is_equal('carbon.explorer', vim.bo.filetype) - local root_entry = view.execute(function(context) - return context.view.root - end) - - assert.not_nil(root_entry) - - carbon.up() + view.execute(function(ctx) + local root = ctx.view.root - assert.is_true(root_entry:is_open()) + carbon.up() + assert.is_true(ctx.view:get_path_attr(root.path)) + end) carbon.reset() end) @@ -312,34 +315,34 @@ describe('carbon', function() describe('create', function() pending('calls buffer.create', function() - local buffer_create = spy.on(buffer, 'create') + -- local buffer_create = spy.on(buffer, 'create') - carbon.create() - helpers.type_keys('') + -- carbon.create() + -- helpers.type_keys('') - assert.spy(buffer_create).is_called() + -- assert.spy(buffer_create).is_called() end) end) describe('delete', function() pending('calls buffer.delete', function() - local buffer_delete = spy.on(buffer, 'delete') + -- local buffer_delete = spy.on(buffer, 'delete') - carbon.delete() - helpers.type_keys('') + -- carbon.delete() + -- helpers.type_keys('') - assert.spy(buffer_delete).is_called() + -- assert.spy(buffer_delete).is_called() end) end) describe('move', function() pending('calls buffer.move', function() - local buffer_move = spy.on(buffer, 'move') + -- local buffer_move = spy.on(buffer, 'move') - carbon.move() - helpers.type_keys('') + -- carbon.move() + -- helpers.type_keys('') - assert.spy(buffer_move).is_called() + -- assert.spy(buffer_move).is_called() end) end) end) diff --git a/test/specs/entry_spec.lua b/test/specs/entry_spec.lua index 43b36ff..20ba715 100644 --- a/test/specs/entry_spec.lua +++ b/test/specs/entry_spec.lua @@ -132,93 +132,6 @@ describe('carbon.entry', function() end) end) - describe('is_compressible', function() - it('is boolean', function() - local target = entry.new(helpers.resolve('lua')) - - assert.is_boolean(target:is_compressible()) - end) - - it('is true by default', function() - local target = entry.new(helpers.resolve('lua')) - - target:set_compressible(nil) - - assert.is_true(target:is_compressible()) - end) - end) - - describe('set_compressible', function() - it('sets compressible status', function() - local target = entry.new(helpers.resolve('lua')) - - target:set_compressible(false) - - assert.is_false(target:is_compressible()) - - target:set_compressible(nil) - end) - end) - - describe('is_open', function() - it('is boolean', function() - local target = entry.new(helpers.resolve('lua')) - - assert.is_boolean(target:is_open()) - end) - - it('is false by default', function() - local target = entry.new(helpers.resolve('lua')) - - assert.is_false(target:is_open()) - end) - end) - - describe('set_open', function() - it('does nothing when called on regular file', function() - local file = entry.new(helpers.resolve('README.md')) - - file:set_open(true) - - assert.is_false(file:is_open()) - end) - - it('sets opened status', function() - local target = entry.new(helpers.resolve('lua')) - - target:set_open(true) - - assert.is_true(target:is_open()) - - target:set_open(nil) - end) - - it('opens recursively when recursive is true', function() - local target = entry.new(helpers.resolve('lua')) - local dirs = vim.tbl_filter(function(child) - return child.is_directory - end, target:children()) - - assert.is_true(#dirs > 0) - - target:set_open(true, true) - - assert.is_true(target:is_open()) - - for _, dir_child in ipairs(dirs) do - assert.is_true(dir_child:is_open()) - end - - target:set_open(nil, true) - - assert.is_false(target:is_open()) - - for _, dir_child in ipairs(dirs) do - assert.is_false(dir_child:is_open()) - end - end) - end) - describe('children', function() it('returns empty table on regular file', function() local file = entry.new(helpers.resolve('README.md')) From 71cff4ab526937a9eee275d074bab25be0dbd7c4 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 14:51:30 +0200 Subject: [PATCH 19/52] Fix pending tests --- test/config/helpers.lua | 16 +++++++ test/specs/carbon_spec.lua | 94 +++++++++++++++++++++---------------- test/specs/watcher_spec.lua | 9 ++-- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/test/config/helpers.lua b/test/config/helpers.lua index 3d9b434..5a63556 100644 --- a/test/config/helpers.lua +++ b/test/config/helpers.lua @@ -1,3 +1,5 @@ +local util = require('carbon.util') +local view = require('carbon.view') local entry = require('carbon.entry') local constants = require('carbon.constants') local helpers = {} @@ -79,6 +81,20 @@ function helpers.entry(relative_path) return entry.find(string.format('%s/%s', vim.loop.cwd(), relative_path)) end +function helpers.is_open(path) + return view.execute(function(ctx) + return ctx.view:get_path_attr(path, 'open') + end) +end + +function helpers.line_with_file() + return view.execute(function(ctx) + return util.tbl_find(ctx.view:current_lines(), function(line) + return not line.entry.is_directory + end) + end) +end + function helpers.markdown_info(absolute_path) local result = { tags = {}, refs = {}, header_tags = {}, header_refs = {} } local lines = vim.fn.readfile(absolute_path) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index cec7c18..d45e181 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -1,6 +1,6 @@ require('test.config.assertions') --- local spy = require('luassert.spy') +local spy = require('luassert.spy') local util = require('carbon.util') local carbon = require('carbon') local view = require('carbon.view') @@ -120,13 +120,15 @@ describe('carbon', function() end) describe('split', function() - pending('open file in horizontal split', function() + it('open file in horizontal split', function() assert.equal('carbon.explorer', vim.bo.filetype) - util.cursor(3, 1) - carbon.split() + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) - helpers.inspect_buffer() + util.cursor(file_line.lnum, 1) + carbon.split() assert.not_equal('carbon.explorer', vim.bo.filetype) @@ -137,10 +139,14 @@ describe('carbon', function() end) describe('vsplit', function() - pending('open file in vertical split', function() + it('open file in vertical split', function() assert.equal('carbon.explorer', vim.bo.filetype) - util.cursor(3, 1) + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) + + util.cursor(file_line.lnum, 1) carbon.vsplit() assert.not_equal('carbon.explorer', vim.bo.filetype) @@ -152,21 +158,21 @@ describe('carbon', function() end) describe('toggle_recursive', function() - pending('toggles recursively opened directory', function() + it('toggles recursively opened directory', function() local assets_entry = helpers.entry('doc/assets') util.cursor(4, 1) assert.not_nil(assets_entry) carbon.toggle_recursive() - assert.is_true(assets_entry:is_open()) + assert.is_true(helpers.is_open(assets_entry.path)) assert.same( { '- doc/', ' - assets/' }, vim.api.nvim_buf_get_lines(0, 3, 5, true) ) carbon.toggle_recursive() - assert.is_false(assets_entry:is_open()) + assert.is_false(helpers.is_open(assets_entry.path)) assert.same( { '+ doc/', '+ lua/' }, vim.api.nvim_buf_get_lines(0, 3, 5, true) @@ -175,8 +181,12 @@ describe('carbon', function() end) describe('explore', function() - pending('shows the buffer', function() - util.cursor(12, 1) + it('shows the buffer', function() + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) + + util.cursor(file_line.lnum, 1) carbon.edit() carbon.explore() @@ -185,8 +195,12 @@ describe('carbon', function() end) describe('explore_left', function() - pending('shows the buffer to the left of the current buffer', function() - util.cursor(12, 1) + it('shows the buffer to the left of the current buffer', function() + local file_line = helpers.line_with_file() + + assert.not_nil(file_line) + + util.cursor(file_line.lnum, 1) carbon.edit() local before_bufname = vim.fn.bufname() @@ -201,7 +215,7 @@ describe('carbon', function() end) describe('explore_float', function() - pending('shows the buffer in a floating window', function() + it('shows the buffer in a floating window', function() carbon.explore_float() assert.is_number( @@ -213,7 +227,7 @@ describe('carbon', function() end) describe('up', function() - pending('sets cwd to parent directory', function() + it('sets cwd to parent directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -225,7 +239,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - pending('registers new directory listeners', function() + it('registers new directory listeners', function() local original_listeners = watcher.registered() carbon.up() @@ -233,7 +247,7 @@ describe('carbon', function() assert.not_same(original_listeners, watcher.registered()) end) - pending('automatically opens previous cwd', function() + it('automatically opens previous cwd', function() util.cursor(1, 1) assert.is_equal('carbon.explorer', vim.bo.filetype) @@ -242,7 +256,7 @@ describe('carbon', function() local root = ctx.view.root carbon.up() - assert.is_true(ctx.view:get_path_attr(root.path)) + assert.is_true(ctx.view:get_path_attr(root.path, 'open')) end) carbon.reset() @@ -250,7 +264,7 @@ describe('carbon', function() end) describe('reset', function() - pending('reset to original cwd during startup', function() + it('reset to original cwd during startup', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -263,7 +277,7 @@ describe('carbon', function() end) describe('down', function() - pending('sets cwd to cursor directory', function() + it('sets cwd to cursor directory', function() local original_cwd = vim.loop.cwd() settings.sync_pwd = true @@ -276,7 +290,7 @@ describe('carbon', function() settings.sync_pwd = settings.defaults.sync_pwd end) - pending('releases registered listeners not in new cwd', function() + it('releases registered listeners not in new cwd', function() local original_listeners = watcher.registered() util.cursor(2, 1) @@ -289,7 +303,7 @@ describe('carbon', function() end) describe('cd', function() - pending('sets cwd to target path', function() + it('sets cwd to target path', function() local jump_cwd = string.format('%s/test/specs', vim.loop.cwd()) settings.sync_pwd = true @@ -304,45 +318,45 @@ describe('carbon', function() end) describe('quit', function() - pending('closes the buffer', function() + it('closes the buffer', function() vim.cmd.edit('README.md') carbon.explore() helpers.type_keys(settings.actions.quit) - assert.not_equal('carbon', vim.fn.bufname()) + assert.not_equal('carbon.explorer', vim.bo.filetype) end) end) describe('create', function() - pending('calls buffer.create', function() - -- local buffer_create = spy.on(buffer, 'create') + it('calls buffer.create', function() + local view_create = spy.on(view, 'create') - -- carbon.create() - -- helpers.type_keys('') + carbon.create() + helpers.type_keys('') - -- assert.spy(buffer_create).is_called() + assert.spy(view_create).is_called() end) end) describe('delete', function() - pending('calls buffer.delete', function() - -- local buffer_delete = spy.on(buffer, 'delete') + it('calls buffer.delete', function() + local view_delete = spy.on(view, 'delete') - -- carbon.delete() - -- helpers.type_keys('') + carbon.delete() + helpers.type_keys('') - -- assert.spy(buffer_delete).is_called() + assert.spy(view_delete).is_called() end) end) describe('move', function() - pending('calls buffer.move', function() - -- local buffer_move = spy.on(buffer, 'move') + it('calls buffer.move', function() + local view_move = spy.on(view, 'move') - -- carbon.move() - -- helpers.type_keys('') + carbon.move() + helpers.type_keys('') - -- assert.spy(buffer_move).is_called() + assert.spy(view_move).is_called() end) end) end) diff --git a/test/specs/watcher_spec.lua b/test/specs/watcher_spec.lua index 3f56204..961f7ac 100644 --- a/test/specs/watcher_spec.lua +++ b/test/specs/watcher_spec.lua @@ -46,20 +46,21 @@ describe('carbon.watcher', function() -- FIXME: remove pending status after figuring out how to wait for fs events describe('carbon:synchronize', function() - pending('triggers on new file', function() + it('triggers on new file', function() local callback = spy() watcher.register(vim.loop.cwd()) watcher.on('carbon:synchronize', callback) helpers.ensure_path('check.txt') + vim.wait(100) assert .spy(callback) .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.txt', nil) end) - pending('triggers on file change', function() + it('triggers on file change', function() local callback = spy() helpers.ensure_path('check.sh') @@ -68,6 +69,7 @@ describe('carbon.watcher', function() watcher.on('carbon:synchronize', callback) helpers.change_file('check.sh') + vim.wait(100) assert.spy(callback).is_called() assert @@ -75,7 +77,7 @@ describe('carbon.watcher', function() .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.sh', nil) end) - pending('triggers on file remove', function() + it('triggers on file remove', function() local callback = spy() helpers.ensure_path('check.sh') @@ -84,6 +86,7 @@ describe('carbon.watcher', function() watcher.on('carbon:synchronize', callback) helpers.delete_path('check.sh') + vim.wait(100) assert.spy(callback).is_called() assert From 7df8e09317266e9e6c12cf886893b1d5b48e9b47 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 15:26:02 +0200 Subject: [PATCH 20/52] only run watcher uv event tests in isolation --- test/specs/watcher_spec.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/specs/watcher_spec.lua b/test/specs/watcher_spec.lua index 961f7ac..016d59d 100644 --- a/test/specs/watcher_spec.lua +++ b/test/specs/watcher_spec.lua @@ -46,7 +46,9 @@ describe('carbon.watcher', function() -- FIXME: remove pending status after figuring out how to wait for fs events describe('carbon:synchronize', function() - it('triggers on new file', function() + local it_when_only = vim.env.only and it or pending + + it_when_only('triggers on new file', function() local callback = spy() watcher.register(vim.loop.cwd()) @@ -60,7 +62,7 @@ describe('carbon.watcher', function() .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.txt', nil) end) - it('triggers on file change', function() + it_when_only('triggers on file change', function() local callback = spy() helpers.ensure_path('check.sh') @@ -77,7 +79,7 @@ describe('carbon.watcher', function() .is_called_with('carbon:synchronize', vim.loop.cwd(), 'check.sh', nil) end) - it('triggers on file remove', function() + it_when_only('triggers on file remove', function() local callback = spy() helpers.ensure_path('check.sh') From b7cbabc317045c5162aa78c95a7ccddb925d528c Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 19:25:34 +0200 Subject: [PATCH 21/52] use indexed methods everywhere when using vim.cmd --- lua/carbon/util.lua | 2 +- lua/carbon/view.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index 2469d9c..078ad81 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -108,7 +108,7 @@ function util.confirm(options) end vim.api.nvim_set_option('guicursor', guicursor) - vim.cmd({ cmd = 'close' }) + vim.cmd.close() end if not immediate then diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index bb18521..eda55fe 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -12,7 +12,7 @@ view.float = { origin = -1, target = -1 } view.resync_paths = {} local function create_leave(ctx) - vim.cmd({ cmd = 'stopinsert' }) + vim.cmd.stopinsert() ctx.view:set_path_attr(ctx.target.path, 'compressible', ctx.prev_compressible) util.cursor(ctx.target_line.lnum, 1) vim.keymap.del('i', '', { buffer = 0 }) From 046966c9a8af2d10cfc9fd0e60a2f35c3a436e17 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 20:24:42 +0200 Subject: [PATCH 22/52] fix compressible status, resync a path only once per resync across multiple views --- lua/carbon/entry.lua | 2 ++ lua/carbon/view.lua | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/carbon/entry.lua b/lua/carbon/entry.lua index 8881c80..89d5d5a 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry.lua @@ -55,6 +55,8 @@ function entry:synchronize(paths) paths = paths or {} if paths[self.path] then + paths[self.path] = nil + local all_paths = {} local current_paths = {} local previous_paths = {} diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index eda55fe..b744f0e 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -233,8 +233,13 @@ end function view:get_path_attr(path, attr) local state = self.states[path] + local value = state and state[attr] - return state and state[attr] + if attr == 'compressible' and value == nil then + return true + end + + return value end function view:set_path_attr(path, attr, value) From 933d618c6ef3d9dc22e42aa7481fd02e69ab3bea Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:44:53 +0200 Subject: [PATCH 23/52] Feature: open when editing directories, open_on_dir setting, small float_settings tweak --- lua/carbon.lua | 17 +++++++++++++++++ lua/carbon/settings.lua | 7 ++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index bb067d2..7874438 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -28,6 +28,10 @@ function carbon.setup(user_settings) util.command('Lcarbon', carbon.explore_left, command_opts) util.command('Fcarbon', carbon.explore_float, command_opts) + if settings.open_on_dir then + util.autocmd('BufWinEnter', carbon.maybe_explore, { pattern = '*' }) + end + if settings.sync_on_cd then util.autocmd('DirChanged', carbon.cd, { pattern = 'global' }) end @@ -172,6 +176,19 @@ function carbon.cd(path) end) end +function carbon.maybe_explore(params) + if vim.bo.filetype == 'carbon.explorer' then + return + end + + if params and params.file and util.is_directory(params.file) then + view.activate({ path = params.file, delete_current_buf = true }) + view.execute(function(ctx) + ctx.view:show() + end) + end +end + function carbon.explore(options_param) local options = options_param or {} local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') diff --git a/lua/carbon/settings.lua b/lua/carbon/settings.lua index 32c2feb..55a0080 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings.lua @@ -5,8 +5,9 @@ local defaults = { keep_netrw = false, sync_on_cd = not vim.opt.autochdir:get(), sync_delay = 20, - sidebar_width = 30, + open_on_dir = true, always_reveal = false, + sidebar_width = 30, exclude = { '~$', '#$', @@ -33,8 +34,8 @@ local defaults = { float_settings = function() local columns = vim.opt.columns:get() local rows = vim.opt.lines:get() - local width = math.min(50, math.floor(columns * 0.8)) - local height = math.min(20, math.floor(rows * 0.8)) + local width = math.min(40, math.floor(columns * 0.9)) + local height = math.min(20, math.floor(rows * 0.9)) return { relative = 'editor', From e0a69117d92ef6b8227f26b5aaef528c5396e7e9 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:45:13 +0200 Subject: [PATCH 24/52] Fix: position of confirm popup is now relative to window --- lua/carbon/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index 078ad81..efb6149 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -161,7 +161,7 @@ function util.confirm(options) }) local win = vim.api.nvim_open_win(buf, true, { - relative = 'editor', + relative = 'win', anchor = 'NW', border = 'single', style = 'minimal', From 8d067907ec742399df65ca9d69a26c2166325c9b Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:49:21 +0200 Subject: [PATCH 25/52] unique buffer names, stop renaming buffer to root entry filename --- lua/carbon/view.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index b744f0e..3210eb3 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -90,7 +90,6 @@ function view.activate(options_param) local options = options_param or {} local original_window = vim.api.nvim_get_current_win() local original_buffer = vim.api.nvim_get_current_buf() - local original_buffer_valid = vim.api.nvim_buf_is_valid(original_buffer) local current_view = (options.path and view.get(options.path)) or view.current() or view.get(vim.loop.cwd()) @@ -134,8 +133,8 @@ function view.activate(options_param) vim.api.nvim_win_set_buf(0, current_view:buffer()) end - if original_buffer_valid and options.delete_current_buf then - vim.api.nvim_buf_delete(original_buffer, { force = true }) + if options.delete_current_buf then + pcall(vim.api.nvim_buf_delete, original_buffer, { force = true }) end end @@ -361,7 +360,7 @@ function view:buffer() end local buffer = util.create_scratch_buf({ - name = vim.fn.fnamemodify(self.root.path, ':t'), + name = string.format('carbon[%d]', self.index), filetype = 'carbon.explorer', modifiable = false, modified = false, @@ -470,11 +469,6 @@ function view:set_root(target) self.root = target - vim.api.nvim_buf_set_name( - self:buffer(), - vim.fn.fnamemodify(self.root.path, ':t') - ) - watcher.keep(function(path) return vim.startswith(path, self.root.path) end) From e3c48625eb4a5d3d2deec66022f6f93bf9c52c7b Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 3 Sep 2022 22:56:35 +0200 Subject: [PATCH 26/52] update tests --- test/specs/carbon_spec.lua | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index d45e181..9c936d2 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -16,12 +16,6 @@ describe('carbon', function() describe('autocommands', function() describe('DirChanged', function() - it('exists', function() - local autocmd = helpers.autocmd('DirChanged') - - assert.is_number(autocmd.id) - end) - it('is not buffer local', function() local autocmd = helpers.autocmd('DirChanged') @@ -36,26 +30,23 @@ describe('carbon', function() end) describe('BufWinEnter', function() - it('exists', function() - local autocmd = helpers.autocmd('BufWinEnter') + it('has buffer local event', function() + local autocmd = helpers.autocmd( + 'BufWinEnter', + { buffer = vim.api.nvim_get_current_buf() } + ) - assert.is_number(autocmd.id) + assert.is_true(autocmd.buflocal) end) - it('is buffer local', function() + it('has a global event', function() local autocmd = helpers.autocmd('BufWinEnter') - assert.is_true(autocmd.buflocal) + assert.is_false(autocmd.buflocal) end) end) describe('BufHidden', function() - it('exists', function() - local autocmd = helpers.autocmd('BufHidden') - - assert.is_number(autocmd.id) - end) - it('is buffer local', function() local autocmd = helpers.autocmd('BufHidden') From cf6a3f356f212a639a8d7c49ea91bb968f76fc64 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 4 Sep 2022 00:07:32 +0200 Subject: [PATCH 27/52] assume views only have one active buffer at most --- lua/carbon/view.lua | 66 ++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 3210eb3..56e6573 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -280,28 +280,27 @@ function view:render() end end - for _, buf in ipairs(self:buffers()) do - local current_mode = string.lower(vim.api.nvim_get_mode().mode) - - vim.api.nvim_buf_clear_namespace(buf, constants.hl, 0, -1) - vim.api.nvim_buf_set_option(buf, 'modifiable', true) - vim.api.nvim_buf_set_lines(buf, 0, -1, 1, lines) - vim.api.nvim_buf_set_option(buf, 'modified', false) - - if not string.find(current_mode, 'i') then - vim.api.nvim_buf_set_option(buf, 'modifiable', false) - end - - for _, hl in ipairs(hls) do - vim.api.nvim_buf_add_highlight( - buf, - constants.hl, - hl[1], - hl[2], - hl[3], - hl[4] - ) - end + local buf = self:buffer() + local current_mode = string.lower(vim.api.nvim_get_mode().mode) + + vim.api.nvim_buf_clear_namespace(buf, constants.hl, 0, -1) + vim.api.nvim_buf_set_option(buf, 'modifiable', true) + vim.api.nvim_buf_set_lines(buf, 0, -1, 1, lines) + vim.api.nvim_buf_set_option(buf, 'modified', false) + + if not string.find(current_mode, 'i') then + vim.api.nvim_buf_set_option(buf, 'modifiable', false) + end + + for _, hl in ipairs(hls) do + vim.api.nvim_buf_add_highlight( + buf, + constants.hl, + hl[1], + hl[2], + hl[3], + hl[4] + ) end if cursor then @@ -323,15 +322,13 @@ function view:render() end function view:focus_flash(duration, group, start, finish) - for _, buf in ipairs(self:buffers()) do - vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {}) - end + local buf = self:buffer() + + vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {}) vim.defer_fn(function() - for _, buf in ipairs(self:buffers()) do - if vim.api.nvim_buf_is_valid(buf) then - vim.api.nvim_buf_clear_namespace(buf, constants.hl_tmp, 0, -1) - end + if vim.api.nvim_buf_is_valid(buf) then + vim.api.nvim_buf_clear_namespace(buf, constants.hl_tmp, 0, -1) end end, duration) end @@ -376,7 +373,11 @@ function view:buffer() }, }) - vim.api.nvim_buf_set_var(buffer, 'carbon', { index = self.index }) + vim.api.nvim_buf_set_var( + buffer, + 'carbon', + { index = self.index, name = self.root.name } + ) return buffer end @@ -468,6 +469,11 @@ function view:set_root(target) end self.root = target + vim.api.nvim_buf_set_var( + self:buffer(), + 'carbon', + { index = self.index, name = self.root.name } + ) watcher.keep(function(path) return vim.startswith(path, self.root.path) From 37f50f40f3894c9788611c228730e8055c18668d Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 4 Sep 2022 02:56:59 +0200 Subject: [PATCH 28/52] minor cleanup, only sync pwd when navigating in pwd root buffers --- dev/init.lua | 2 +- lua/carbon/view.lua | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dev/init.lua b/dev/init.lua index 0e7054a..65cf9d2 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -3,4 +3,4 @@ vim.opt.runtimepath:append({ vim.env.HOME .. '/Dev/sidofc/lua/carbon.nvim' }) vim.opt.runtimepath:remove({ vim.env.HOME .. '/.config/nvim' }) vim.opt.packpath:remove({ vim.env.HOME .. '/.local/share/nvim/site' }) -require('carbon').setup() +require('carbon').setup({ sync_pwd = true }) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 56e6573..51694a2 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -81,7 +81,6 @@ function view.get(path) }, view) views[index] = instance - instance.index = index return instance end @@ -376,7 +375,7 @@ function view:buffer() vim.api.nvim_buf_set_var( buffer, 'carbon', - { index = self.index, name = self.root.name } + { index = self.index, path = self.root.path } ) return buffer @@ -460,6 +459,8 @@ function view:down(count) end function view:set_root(target) + local is_cwd = self.root.path == vim.loop.cwd() + if type(target) == 'string' then target = entry.new(target) end @@ -472,14 +473,14 @@ function view:set_root(target) vim.api.nvim_buf_set_var( self:buffer(), 'carbon', - { index = self.index, name = self.root.name } + { index = self.index, path = self.root.path } ) watcher.keep(function(path) return vim.startswith(path, self.root.path) end) - if settings.sync_pwd then + if settings.sync_pwd and is_cwd then vim.api.nvim_set_current_dir(self.root.path) end From 0376f256784a553d56feccfff3a9ddab167ceca6 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 10 Sep 2022 16:10:32 +0200 Subject: [PATCH 29/52] rename settings.always_reveal => settings.auto_reveal --- README.md | 2 +- dev/init.lua | 2 +- doc/carbon.txt | 16 ++++++++-------- lua/carbon/settings.lua | 2 +- lua/carbon/view.lua | 2 +- test/specs/settings_spec.lua | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 78a7d4e..4b9ddc7 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ All commands also support bang (`!`) versions which will make Carbon expand the tree to reveal the current buffer path if possible. When successful, the cursor will be moved to the entry and it will be highlighted for a short time as well. See `:h carbon-buffer-flash-bang` for more information. This behavior can also -be enabled by default by setting: `:h carbon-setting-always-reveal`. +be enabled by default by setting: `:h carbon-setting-auto-reveal`. ### `:Carbon` / `:Explore` diff --git a/dev/init.lua b/dev/init.lua index 65cf9d2..a2e5013 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -3,4 +3,4 @@ vim.opt.runtimepath:append({ vim.env.HOME .. '/Dev/sidofc/lua/carbon.nvim' }) vim.opt.runtimepath:remove({ vim.env.HOME .. '/.config/nvim' }) vim.opt.packpath:remove({ vim.env.HOME .. '/.local/share/nvim/site' }) -require('carbon').setup({ sync_pwd = true }) +require('carbon').setup({ sync_pwd = true, auto_reveal = true }) diff --git a/doc/carbon.txt b/doc/carbon.txt index 2c904d8..29b84ad 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -122,7 +122,7 @@ COMMANDS *carbon-command Alias: `Explore` unless |carbon-setting-keep-netrw| is enabled. Replaces the current buffer with the Carbon buffer. - When called with a (`Carbon!`) or |carbon-setting-always-reveal| enabled, + When called with a (`Carbon!`) or |carbon-setting-auto-reveal| enabled, the tree will expand to show the current buffer path and |carbon-buffer-flash-bang| it if possible. This also works for its alias. @@ -139,7 +139,7 @@ COMMANDS *carbon-command Subsequent calls will reuse a window previously opened via `Lcarbon` if this window still exists and valid. - When called with a (`Carbon!`) or |carbon-setting-always-reveal| enabled, + When called with a (`Carbon!`) or |carbon-setting-auto-reveal| enabled, the tree will expand to show the current buffer path and |carbon-buffer-flash-bang| it if possible. This also works for its alias. @@ -161,7 +161,7 @@ COMMANDS *carbon-command in a vertical split relative to the current window. - When called with a (`Carbon!`) or |carbon-setting-always-reveal| enabled, + When called with a (`Carbon!`) or |carbon-setting-auto-reveal| enabled, the tree will expand to show the current buffer path and |carbon-buffer-flash-bang| it if possible. @@ -416,7 +416,7 @@ CARBON *carbon-carbo Used by the |carbon-command-Carbon| command. When {options} is given and `options.bang` is `true` or when - |carbon-setting-always-reveal| is `true` then this calls + |carbon-setting-auto-reveal| is `true` then this calls |carbon-buffer-expand-to-path| to expand the tree to reveal the current buffer. @@ -430,7 +430,7 @@ CARBON *carbon-carbo modifies how |carbon-carbon-edit| works for that window. When {options} is given and `options.bang` is `true` or when - |carbon-setting-always-reveal| is `true` then this calls + |carbon-setting-auto-reveal| is `true` then this calls |carbon-buffer-expand-to-path| to expand the tree to reveal the current buffer. @@ -446,7 +446,7 @@ CARBON *carbon-carbo and modifies how |carbon-carbon-edit| works for that window. When {options} is given and `options.bang` is `true` or when - |carbon-setting-always-reveal| is `true` then this calls + |carbon-setting-auto-reveal| is `true` then this calls |carbon-buffer-expand-to-path| to expand the tree to reveal the current buffer. @@ -1847,7 +1847,7 @@ SETTINGS *carbon-setting ` sync_on_cd = `|carbon-setting-sync-on-cd|`,` ` sync_delay = `|carbon-setting-sync-delay|`,` ` sidebar_width = `|carbon-setting-sidebar-width|`,` - ` always_reveal = `|carbon-setting-always-reveal|`,` + ` auto_reveal = `|carbon-setting-auto-reveal|`,` ` exclude = `|carbon-setting-exclude|`,` ` indicators = {` @@ -1995,7 +1995,7 @@ SETTINGS *carbon-setting The width in columns of the sidebar window opened by |carbon-command-Lcarbon|. `------------------------------------------------------------------------------` - always_reveal *carbon-setting-always-reveal* + auto_reveal *carbon-setting-auto-reveal* Default: `false` diff --git a/lua/carbon/settings.lua b/lua/carbon/settings.lua index 55a0080..be91f60 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings.lua @@ -6,7 +6,7 @@ local defaults = { sync_on_cd = not vim.opt.autochdir:get(), sync_delay = 20, open_on_dir = true, - always_reveal = false, + auto_reveal = false, sidebar_width = 30, exclude = { '~$', diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 51694a2..dd76527 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -93,7 +93,7 @@ function view.activate(options_param) or view.current() or view.get(vim.loop.cwd()) - if options.reveal or settings.always_reveal then + if options.reveal or settings.auto_reveal then current_view:expand_to_path(vim.fn.expand('%')) end diff --git a/test/specs/settings_spec.lua b/test/specs/settings_spec.lua index a16ba33..cd23868 100644 --- a/test/specs/settings_spec.lua +++ b/test/specs/settings_spec.lua @@ -74,9 +74,9 @@ describe('carbon.settings', function() end) end) - describe('always_reveal', function() + describe('auto_reveal', function() it('is a boolean', function() - assert.is_boolean(settings.always_reveal) + assert.is_boolean(settings.auto_reveal) end) end) From ae52eead89267d7a9aded92d607b37c69b08b73f Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 11 Sep 2022 14:17:06 +0200 Subject: [PATCH 30/52] rename carbon.maybe_explore to carbon.explore_buf_dir --- lua/carbon.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index 7874438..9c0c1a5 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -29,7 +29,7 @@ function carbon.setup(user_settings) util.command('Fcarbon', carbon.explore_float, command_opts) if settings.open_on_dir then - util.autocmd('BufWinEnter', carbon.maybe_explore, { pattern = '*' }) + util.autocmd('BufWinEnter', carbon.explore_buf_dir, { pattern = '*' }) end if settings.sync_on_cd then @@ -176,19 +176,6 @@ function carbon.cd(path) end) end -function carbon.maybe_explore(params) - if vim.bo.filetype == 'carbon.explorer' then - return - end - - if params and params.file and util.is_directory(params.file) then - view.activate({ path = params.file, delete_current_buf = true }) - view.execute(function(ctx) - ctx.view:show() - end) - end -end - function carbon.explore(options_param) local options = options_param or {} local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') @@ -222,6 +209,19 @@ function carbon.explore_float(options_param) view.activate({ path = path, reveal = options.bang, float = true }) end +function carbon.explore_buf_dir(params) + if vim.bo.filetype == 'carbon.explorer' then + return + end + + if params and params.file and util.is_directory(params.file) then + view.activate({ path = params.file, delete_current_buf = true }) + view.execute(function(ctx) + ctx.view:show() + end) + end +end + function carbon.quit() if #vim.api.nvim_list_wins() > 1 then vim.api.nvim_win_close(0, 1) From a6df76ded27e2c859c7d30ed5807029518daeda4 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Tue, 29 Nov 2022 04:21:38 +0100 Subject: [PATCH 31/52] Fix tests, safeguard setup without configuration --- lua/carbon.lua | 51 ++++++++++++++++++++++---------------- test/specs/carbon_spec.lua | 6 ++--- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index b6f677b..0c2c9ae 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -5,6 +5,10 @@ local view = require('carbon.view') local carbon = {} function carbon.setup(user_settings) + if type(user_settings) ~= 'table' then + user_settings = {} + end + if not vim.g.carbon_initialized then if type(user_settings) == 'function' then user_settings(settings) @@ -254,36 +258,39 @@ function carbon.move() end function carbon.close_parent() - local count = 0 - local lines = { unpack(buffer.lines(), 2) } - local entry = buffer.cursor().line.entry - local line + view.execute(function(ctx) + local count = 0 + local lines = { unpack(ctx.view:current_lines(), 2) } + local entry = ctx.cursor.line.entry + local line + + while count < vim.v.count1 do + line = util.tbl_find(lines, function(current) + return current.entry == entry.parent + or vim.tbl_contains(current.path, entry.parent) + end) + + if line then + count = count + 1 + entry = line.path[1] and line.path[1].parent or line.entry + + ctx.view:set_path_attr(entry.path, 'open', false) + else + break + end + end - while count < vim.v.count1 do line = util.tbl_find(lines, function(current) - return current.entry == entry.parent - or vim.tbl_contains(current.path, entry.parent) + return current.entry == entry or vim.tbl_contains(current.path, entry) end) if line then - count = count + 1 - entry = line.path[1] and line.path[1].parent or line.entry - - entry:set_open(false) - else - break + vim.fn.cursor(line.lnum, (line.depth + 1) * 2 + 1) end - end - line = util.tbl_find(lines, function(current) - return current.entry == entry or vim.tbl_contains(current.path, entry) + ctx.view:update() + ctx.view:render() end) - - if line then - vim.fn.cursor(line.lnum, (line.depth + 1) * 2 + 1) - end - - buffer.render() end return carbon diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index 4f24c70..625412a 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -173,12 +173,12 @@ describe('carbon', function() describe('close_parent', function() it('closes parent of cursor entry and moves cursor', function() - util.cursor(7, 1) + util.cursor(6, 1) carbon.edit() - util.cursor(9, 1) + util.cursor(8, 1) carbon.close_parent() - assert.equal(7, vim.fn.line('.')) + assert.equal(6, vim.fn.line('.')) assert.equal(3, vim.fn.col('.')) end) end) From a3ca231f58e1085ed864182c3b4821aadbbd24e2 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Tue, 4 Apr 2023 23:27:00 +0200 Subject: [PATCH 32/52] Remove `delete_current_buf` option from `view.activate` --- lua/carbon.lua | 6 ++++-- lua/carbon/view.lua | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lua/carbon.lua b/lua/carbon.lua index 2c56114..aa26978 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -91,7 +91,9 @@ function carbon.session_load_post(event) local is_sidebar = window_width == settings.sidebar_width view.activate({ path = event.file }) - vim.cmd.doautocmd({ 'BufWinEnter', event.file }) + view.execute(function(ctx) + ctx.view:show() + end) if is_sidebar then local neighbor = util.tbl_find( @@ -298,7 +300,7 @@ function carbon.explore_buf_dir(params) end if params and params.file and util.is_directory(params.file) then - view.activate({ path = params.file, delete_current_buf = true }) + view.activate({ path = params.file }) view.execute(function(ctx) ctx.view:show() end) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 99e1c14..0da95ee 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -144,10 +144,6 @@ function view.activate(options_param) else vim.api.nvim_win_set_buf(0, current_view:buffer()) end - - if options.delete_current_buf then - pcall(vim.api.nvim_buf_delete, original_buffer, { force = true }) - end end function view.close_sidebar() From 9061b7b6f0601dfdee0160d960d95398d2aebb2f Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Wed, 5 Apr 2023 10:41:48 +0200 Subject: [PATCH 33/52] Cache bust lines when expanding to path --- lua/carbon/view.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 0da95ee..873c2d0 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -98,7 +98,6 @@ end function view.activate(options_param) local options = options_param or {} local original_window = vim.api.nvim_get_current_win() - local original_buffer = vim.api.nvim_get_current_buf() local current_view = (options.path and view.get(options.path)) or view.current() or view.get(vim.loop.cwd()) @@ -250,6 +249,8 @@ function view:expand_to_path(path) if current and current.path == resolved then self.flash = current + self:update() + return true end From 5855ffde11dbbdf1dfdc9ec8b60975e38bec1489 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 9 Apr 2023 17:39:38 +0200 Subject: [PATCH 34/52] Remove unused view methods --- lua/carbon/view.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 873c2d0..c4c6bfa 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -181,10 +181,6 @@ function view.handle_sidebar_or_float() end end -function view.exists(index) - return views[index] and true or false -end - function view.current() local bufnr = vim.api.nvim_get_current_buf() local ref = select(2, pcall(vim.api.nvim_buf_get_var, bufnr, 'carbon')) @@ -200,10 +196,6 @@ function view.execute(callback) end end -function view.list() - return views -end - function view.resync(path) view.resync_paths[path] = true From 17394be16217b5de041a9d54a0cac0889778ebc6 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 9 Apr 2023 17:39:55 +0200 Subject: [PATCH 35/52] Update documentation --- README.md | 8 +- doc/carbon.txt | 734 ++++++++++++++++++++++++++----------------------- 2 files changed, 387 insertions(+), 355 deletions(-) diff --git a/README.md b/README.md index 8a54e3c..ffab5fb 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ customization options. All commands also support bang (`!`) versions which will make Carbon expand the tree to reveal the current buffer path if possible. When successful, the cursor will be moved to the entry and it will be highlighted for a short time as well. -See `:h carbon-buffer-flash-bang` for more information. This behavior can also +See `:h carbon-view-flash-bang` for more information. This behavior can also be enabled by default by setting: `:h carbon-setting-auto-reveal`. ### `:Carbon` / `:Explore` @@ -252,7 +252,7 @@ opened with [`Fcarbon`](#fcarbon) or [`Lcarbon` / `Rcarbon`](#lcarbon--lexplore- Enters an interactive mode in which a path can be entered. When done typing, press enter to confirm or escape to cancel. Prepending a `count` to c will select the `count`_nth_ -directory from the left as base. See `:h carbon-buffer-create` for more details. +directory from the left as base. See `:h carbon-view-create` for more details. ### m Moving files and directories @@ -261,7 +261,7 @@ directory from the left as base. See `:h carbon-buffer-create` for more details. Prompts to enter a new destination of the current entry under the cursor. Will throw an error when the new destination already exists. Prepending a `count` to c will select the `count`_nth_ directory from -the left as base. See `:h carbon-buffer-move` for more details. +the left as base. See `:h carbon-view-move` for more details. ### d Deleting files and directories @@ -270,7 +270,7 @@ the left as base. See `:h carbon-buffer-move` for more details. Prompts confirmation to delete the current entry under the cursor. Press enter to confirm the currently highlighted option, D to confirm deletion directly, or escape to cancel. Prepending a `count` to c will select -the `count`_nth_ directory from the left as base. See `:h carbon-buffer-delete` +the `count`_nth_ directory from the left as base. See `:h carbon-view-delete` for more details. # File icons diff --git a/doc/carbon.txt b/doc/carbon.txt index 2416ba9..509639b 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -18,9 +18,9 @@ TABLE OF CONTENTS *carbon-contents* *carbon- SETUP `................` |carbon-setup| UTIL `.................` |carbon-util| - ENTRY `................` |carbon-entry| PLUGS `................` |carbon-plugs| - BUFFER `...............` |carbon-buffer| + ENTRY `................` |carbon-entry| + VIEW `.................` |carbon-view| CARBON `...............` |carbon-carbon| WATCHER `..............` |carbon-watcher| COMMANDS `.............` |carbon-commands| @@ -97,18 +97,26 @@ CONSTANTS *carbon-constant Usage: `require('carbon.constants').hl_tmp` Value: `carbon:tmp` - Used for temporary highlighting. Used only by |carbon-buffer-focus-flash| + Used for temporary highlighting. Used only by |carbon-view-focus-flash| at the moment. `------------------------------------------------------------------------------` - augroup *carbon-constants-augroup* + augroup *carbon-constants-augroup* Usage: `require('carbon.constants').augroup` Value `carbon:tmp` - Used for temporary highlighting. Used only by |carbon-buffer-focus-flash| + Used for temporary highlighting. Used only by |carbon-view-focus-flash| at the moment. + `------------------------------------------------------------------------------` + directions *carbon-constants-directions* + + Usage: `require('carbon.constants').directions` + Value `{ left = 'h', right = 'l', up = 'k', down = 'j' }` + + Helper to map "human" direction names to "vim" directions. + ================================================================================ COMMANDS *carbon-commands* @@ -124,7 +132,7 @@ COMMANDS *carbon-command Replaces the current buffer with the Carbon buffer. When called with a (`Carbon!`) or |carbon-setting-auto-reveal| enabled, the tree will expand to show the current buffer path and - |carbon-buffer-flash-bang| it if possible. This also works for its alias. + |carbon-view-flash-bang| it if possible. This also works for its alias. `------------------------------------------------------------------------------` Lcarbon *carbon-command-Lcarbon* @@ -141,7 +149,7 @@ COMMANDS *carbon-command When called with a (`Carbon!`) or |carbon-setting-auto-reveal| enabled, the tree will expand to show the current buffer path and - |carbon-buffer-flash-bang| it if possible. This also works for its alias. + |carbon-view-flash-bang| it if possible. This also works for its alias. `------------------------------------------------------------------------------` Rcarbon *carbon-command-Rcarbon* @@ -185,7 +193,7 @@ COMMANDS *carbon-command When called with a (`Carbon!`) or |carbon-setting-auto-reveal| enabled, the tree will expand to show the current buffer path and - |carbon-buffer-flash-bang| it if possible. + |carbon-view-flash-bang| it if possible. See |carbon-setting-float-settings| for more information on how to configure the floating window spawned by `:Fcarbon`. @@ -196,15 +204,15 @@ AUTOCMDS *carbon-autocmd This section documents the default |autocmd| commands that Carbon uses. `------------------------------------------------------------------------------` - BufEnter *carbon-autocmd-bufenter* + BufWinEnter *carbon-autocmd-bufwinenter* Group: |carbon-constants-augroup| - Event: `BufEnter` + Event: `BufWinEnter` Pattern: - Implementation: `require('carbon.buffer').process_enter()` + Implementation: `view:show()` Local to Carbon buffers. - Calls |carbon-buffer-process-enter| when entering a Carbon buffer. + Calls |carbon-view-show| when entering a Carbon buffer. `------------------------------------------------------------------------------` BufHidden *carbon-autocmd-bufhidden* @@ -212,10 +220,10 @@ AUTOCMDS *carbon-autocmd Group: |carbon-constants-augroup| Event: `BufHidden` Pattern: - Implementation: `require('carbon.buffer').process_hidden()` + Implementation: `view:hide()` Local to Carbon buffers. - Calls |carbon-buffer-process-hidden| when the Carbon buffer is hidden. + Calls |carbon-view-hide| when the Carbon buffer is hidden. `------------------------------------------------------------------------------` CursorMovedI *carbon-autocmd-cursormovedi* @@ -226,7 +234,7 @@ AUTOCMDS *carbon-autocmd Implementation: Local to Carbon buffers. Called when moving the cursor in insert mode. - Used during |carbon-buffer-create| to control the line and minimul column + Used during |carbon-view-create| to control the line and minimul column offset of the cursor. `------------------------------------------------------------------------------` @@ -237,7 +245,7 @@ AUTOCMDS *carbon-autocmd Pattern: `global` Implementation: `require('carbon').cd()` - Calls |carbon-buffer-cd| when changing |pwd| using |cd|. + Calls |carbon-view-cd| when changing |pwd| using |cd|. ================================================================================ PLUGS *carbon-plugs* @@ -251,7 +259,7 @@ PLUGS *carbon-plug Implementation: `require('carbon').up()` Mapping: |carbon-setting-actions-up| - Sets |carbon-buffer-data-root| to the parent directory of the current + Sets the root of the current `view` to the parent directory of the current working directory. Accepts a [count], when given, jumps to [count]nth parent directory of the current working directory. @@ -261,10 +269,10 @@ PLUGS *carbon-plug Implementation: `require('carbon').down()` Mapping: |carbon-setting-actions-down| - Sets |carbon-buffer-data-root| to the directory under the cursor. If the entry - under the cursor is a file then the parent directory of the file's path will - be used. Accepts a [count] to allow navigating deeper into compressed paths. - See |carbon-setting-compress| and |carbon-buffer-down| for more information + Sets the root of the current `view` to the directory under the cursor. If the + entry under the cursor is a file then the parent directory of the file's path + will be used. Accepts a [count] to allow navigating deeper into compressed + paths. See |carbon-setting-compress| and |carbon-view-down| for more information about how compressed paths work and how they are handled. `------------------------------------------------------------------------------` @@ -301,7 +309,7 @@ PLUGS *carbon-plug Accepts a [count] to allow left to right selection of the entry to move when on a compressed path (|carbon-setting-compress|). - For more information see |carbon-buffer-move|. + For more information see |carbon-view-move|. `------------------------------------------------------------------------------` (carbon-reset) *carbon-plug-reset* @@ -309,8 +317,7 @@ PLUGS *carbon-plug Implementation: `require('carbon').reset()` Mapping: |carbon-setting-actions-reset| - Sets |carbon-buffer-data-root| to the initial directory that Neovim - was opened with. + Sets root of the current `view` to its initial directory. `------------------------------------------------------------------------------` (carbon-split) *carbon-plug-split* @@ -343,7 +350,7 @@ PLUGS *carbon-plug parent directories will be created using |mkdir|. Pressing without typing anything will not create anything. Press to cancel. - For more information see |carbon-buffer-create|. + For more information see |carbon-view-create|. `------------------------------------------------------------------------------` (carbon-delete) *carbon-plug-delete* @@ -355,7 +362,7 @@ PLUGS *carbon-plug under the cursor. Accepts a [count] to allow deleting directories "from left to right" when the cursor is on a compressed path (|carbon-setting-compress|). - For more information see |carbon-buffer-delete|. + For more information see |carbon-view-delete|. `------------------------------------------------------------------------------` (carbon-close-parent) *carbon-plug-close-parent* @@ -389,19 +396,16 @@ CARBON *carbon-carbo Signature: `require('carbon').setup(`{preferences}`)` + Initializes Carbon. + This method updates Carbon's |carbon-settings| with user {preferences}. The {preferences} argument can be a table which will be deep-merged with |carbon-settings-table| or a callback function which accepts |carbon-settings-table| as argument. The callback can modify the settings freely. See |carbon-setup| for init.vim / init.lua setup call examples. - `------------------------------------------------------------------------------` - initialize *carbon-carbon-initialize* - - Signature: `require('carbon').initialize()` - - Initializes Carbon. This method creates |carbon-commands|, |carbon-plugs|, - |carbon-autocmds|, and highlights. It hijacks NetRW depending on the value of + This method creates |carbon-commands|, |carbon-plugs|, |carbon-autocmds|, and + highlights. It hijacks NetRW depending on the value of |carbon-setting-keep-netrw| and automatically opens the Carbon buffer if Neovim was opened with a directory path depending on |carbon-setting-auto-open|. It also attaches a callback to the watcher to enable auto-refreshing the @@ -410,6 +414,19 @@ CARBON *carbon-carbo This method should be called only once per Neovim instance and must be called before any buffer is created otherwise such buffer will not function properly. + `------------------------------------------------------------------------------` + explore_buf_dir *carbon-carbon-explore-buf-dir* + + Signature: `require('carbon').explore_buf_dir(`[{params}]`)` + + Called on |BufWinEnter| if |carbon-setting-open-on-dir| is enabled. + When the buffer being entered is a directory Carbon will show instead. + + The {params} attribute, when given, must be a Lua table with a `file` key + pointing to the absolute path of the buffer. + + If {params} is not passed or does not contain a `file` key, nothing is executed. + `------------------------------------------------------------------------------` session_load_post *carbon-carbon-session-load-post* @@ -458,12 +475,12 @@ CARBON *carbon-carbo Signature: `require('carbon').explore(`[{options}]`)` - Show Carbon in the current window. Calls |carbon-buffer-show| internally. + Show Carbon in the current window. Calls |carbon-view-show| internally. Used by the |carbon-command-Carbon| command. When {options} is given and `options.bang` is `true` or when |carbon-setting-auto-reveal| is `true` then this calls - |carbon-buffer-expand-to-path| to expand the tree to reveal + |carbon-view-expand-to-path| to expand the tree to reveal the current buffer. `------------------------------------------------------------------------------` @@ -484,7 +501,7 @@ CARBON *carbon-carbo When {options} is given and `options.bang` is `true` or when |carbon-setting-auto-reveal| is `true` then this calls - |carbon-buffer-expand-to-path| to expand the tree to reveal + |carbon-view-expand-to-path| to expand the tree to reveal the current buffer. Subsequent calls to `:Lcarbon` will attempt to navigate to an existing @@ -528,7 +545,7 @@ CARBON *carbon-carbo When {options} is given and `options.bang` is `true` or when |carbon-setting-auto-reveal| is `true` then this calls - |carbon-buffer-expand-to-path| to expand the tree to reveal + |carbon-view-expand-to-path| to expand the tree to reveal the current buffer. `------------------------------------------------------------------------------` @@ -536,7 +553,7 @@ CARBON *carbon-carbo Signature: `require('carbon').up()` - Calls |carbon-buffer-up|. When the |buffer-data-root| is updated + Calls |carbon-view-up|. When the root of the current `view` is updated successfully, this method will move the cursor to the top of the buffer and rerender. @@ -545,7 +562,7 @@ CARBON *carbon-carbo Signature: `require('carbon').reset()` - Calls |carbon-buffer-reset|. When the |carbon-buffer-data-root| is updated + Calls |carbon-view-reset|. When the root of the current `view` is updated successfully, this method will move the cursor to the top of the buffer and rerender. @@ -554,7 +571,7 @@ CARBON *carbon-carbo Signature: `require('carbon').down()` - Calls |carbon-buffer-down|. When the |carbon-buffer-data-root| is updated + Calls |carbon-view-down|. When the root of the current `view` is updated successfully, this method will move the cursor to the top of the buffer and rerender. @@ -563,28 +580,28 @@ CARBON *carbon-carbo Signature: `require('carbon').create()` - Calls |carbon-buffer-create|. + Calls |carbon-view-create|. `------------------------------------------------------------------------------` move *carbon-carbon-move* Signature: `require('carbon').move()` - Calls |carbon-buffer-move|. + Calls |carbon-view-move|. `------------------------------------------------------------------------------` delete *carbon-carbon-delete* Signature: `require('carbon').delete()` - Calls |carbon-buffer-delete|. + Calls |carbon-view-delete|. `------------------------------------------------------------------------------` cd *carbon-carbon-cd* Signature: `require('carbon').cd(`[{path}]`)` - Calls |carbon-buffer-cd| to set |carbon-buffer-data-root| to {path}. If {path} + Calls |carbon-view-cd| to set the root of the current `view` to {path}. If {path} is not supplied then |vim.v| variable `vim.v.event.cwd` will be used. When updated successfully, this method will move the cursor to the top of the buffer and rerender. @@ -736,6 +753,54 @@ UTIL *carbon-uti {group} must be a string containing the group name. {properties} must be a table. + `------------------------------------------------------------------------------` + add_highlight *carbon-util-add-highlight* + + Signature: `require('carbon.util').add_highlight(`{buf}, {...}`)` + + Calls |nvim_buf_add_highlight| like this: + + `vim.api.nvim_buf_add_highlight(`{buf}, , {...}`)` + + will be set to |carbon-constants-hl|. + + `------------------------------------------------------------------------------` + window_neighbors *carbon-util-window-neighbors* + + Signature: `require('carbon.util').window_neighbors(`{win}, {sides}`)` + + Get windows on {sides} of {win} if present. The {sides} argument is a + table of directions. Available directions are keys defined in + |carbon-constants-directions|. + + Returns a table of results: + + `{` + `{` + `origin = `{win}`,` + `position = string,` + `target = number,` + `}`, + `... other sides defined in `{sides} + `}` + + `------------------------------------------------------------------------------` + find_buf_by_name *carbon-util-find-buf-by-name* + + Signature: `require('carbon.util').find_buf_by_name(`{name}`)` + + Returns |bufnr| of buffer with name {name} or `nil` when no buffer with + {name} exists. + + `------------------------------------------------------------------------------` + resolve *carbon-util-resolve* + + Signature: `require('carbon.util').resolve(`{path}`)` + + Returns normalized absolute path of given {path}. Uses |vim.fs.normalize| + followed by |fnamemodify| with `:p` as modifier to get the full path. + Trailing slashes are stripped. + `------------------------------------------------------------------------------` autocmd *carbon-util-autocmd* @@ -774,7 +839,7 @@ UTIL *carbon-uti For example: - `util.set_winhl(`..., {Normal = 'CarbonIndicator', FloatBorder = 'Normal'}`)` + `util.set_winhl(`0, {Normal = 'CarbonIndicator', FloatBorder = 'Normal'}`)` Will result in the following command being executed: @@ -1082,7 +1147,7 @@ ENTRY *carbon-entr Any truthy value will be treated as if the `entry` is compressible, any falsy value will be treated as if the `entry` is not compressible. - This method is used by |carbon-buffer-create| to control the rendered + This method is used by |carbon-view-create| to control the rendered structure while a new path is being created. `------------------------------------------------------------------------------` @@ -1151,196 +1216,157 @@ ENTRY *carbon-entr be excluded from the returned table. ================================================================================ -BUFFER *carbon-buffer* +VIEW *carbon-view* - Usage: `require('carbon.buffer')` + Usage: `require('carbon.view')` This module is one of Carbon's core modules. It provides methods and - utilities to show the directory tree, interact with it, and keep it + utilities to show directory trees, interact with them, and keep them synchronized with changes from the file system. - *carbon-buffer-data-root* - - The following sections may refer to a `data.root` entry object. - This `data.root` variable is local and private to this module. It is set to - a |carbon-entry-new| created with the value of |getcwd()| as only argument. - - This variable is the internal representation of the file tree which methods - such as |carbon-buffer-lines| can use as a data source or methods like - |carbon-buffer-up|, |carbon-buffer-down|, |carbon-buffer-reset|, or - |carbon-buffer-cd| can manipulate to enable navigating up from the current - working directory, down into a child, reset back to the original - directory Neovim was opened with, or set to an arbitrary path. - `------------------------------------------------------------------------------` - sidebar_window_id *carbon-buffer-sidebar-window-id* + get *carbon-view-get* - Signature: `require('carbon.buffer').sidebar_window_id()` + Signature: `view.get(`{path}`)` - Returns the |winid| of the window opened by |carbon-command-Lcarbon|, - |carbon-command-Rcarbon|, or |carbon-command-ToggleSidebarCarbon|. - - Returns `nil` when no such window exists. + Returns an existing `view` for given {path}. Otherwise a new `view` is + created and returned. `------------------------------------------------------------------------------` - focus_flash *carbon-buffer-focus-flash* - - Signature: `require('carbon.buffer').focus_flash(`{duration}, {group}, - {start}, {finish}`)` + activate *carbon-view-activate* - Highlights a region from {start} to {finish} with {group} for {duration} - milliseconds. {start} and {finish} must be values accepted by - |vim.highlight.range|. + Signature: `view.activate(`[{options}]`)` - See |carbon-buffer-flash-bang| for more information about customizing the - highlighting properties when entries are revealed. + Shows a `view` for `path` given in {options} in the current window. If `sidebar` + in {options} is set to `'right'` or `'left'` then a new sidebar explorer is opened + on that side. Otherwise if `float` is truthy then a floating explorer is + opened. If neither is set then an explorer is shown in the current window. `------------------------------------------------------------------------------` - expand_to_path *carbon-buffer-expand-to-path* + current *carbon-view-current* - Signature: `require('carbon.buffer').expand_to_path(`{path}`)` + Signature: `view.current()` - Expands the Carbon buffer to reveal {path} in the tree. If {path} is not - present within |carbon-buffer-data-root| this will result in a no-op. - - When successful this sets `data.flash` to the entry identified by {path}. - This causes the next call to |carbon-buffer-render| to move to and highlight - the entry |carbon-buffer-flash-bang|. - - Given {path} will be converted to an absolute path automatically by means of - calling |fnamemodify| with the `:p` flag. You do not need to pass an - absolute path yourself. - - This method also works when Carbon is not currently visible. It will fail - only if {path} is not a string. - - The buffer is NOT rerendered automatically, this must be done manually by - calling |carbon-buffer-render| afterwards. + Returns the `view` for the current buffer if it is a Carbon explorer. + When the current buffer is not a Carbon explorer `false` is returned. `------------------------------------------------------------------------------` - set_root *carbon-buffer-set-root* + execute *carbon-view-execute* - Signature: `require('carbon.buffer').set_root(`{new_root}`)` + Signature: `view.execute(`{callback}`)` - The {new_root} argument can be an absolute path to a directory or a - |carbon-entry-new| entry. When passed as an absolute path it is converted to - an entry. |carbon-buffer-data-root| is then set to this entry. + Calls {callback} when the current active buffer is a Carbon buffer. Does + nothing otherwise. - When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. + Executes {callback} with a table containing the following keys: - Returns the updated |carbon-buffer-data-root|. + `{` + `cursor =` |carbon-view-cursor|`,` + `view = `|carbon-view-current|`,` + `}` `------------------------------------------------------------------------------` - launch *carbon-buffer-launch* + resync *carbon-view-resync* - Signature: `require('carbon.buffer').launch(`{new_root}`)` + Signature: `view.resync(`{path}`)` - Called during |carbon-carbon-initialize| when |carbon-setting-auto-open| is set - and Neovim is opened with a directory or no argument. Not called when - Neovim is opened with a regular file. + Stores {path} in `view.resync_paths` and defers synchronization by + |carbon-setting-sync-delay| milliseconds. This is done to batch multiple + modifications to directories which were made in quick succession. - Calls |carbon-buffer-set-root| with {new_root} and then calls |carbon-buffer-show|. - Also set the reset directory used by |carbon-plug-reset| to navigate back to. - - Returns the updated |carbon-buffer-data-root|. + Calls |carbon-entry-synchronize| on the root directory of every `view` after + |carbon-setting-sync-delay| amount of time has passed without any modifications. `------------------------------------------------------------------------------` - process_event *carbon-buffer-process-event* + expand_to_path *carbon-view-expand-to-path* - Signature: `require('carbon.buffer').defer_resync()` + Signature: `view:expand_to_path(`{path}`)` - During |carbon-carbon-initialize| this method is attached using - |carbon-watcher-on| to process file change events. + Expands the Carbon buffer to reveal {path} in the tree. If {path} is not + present within the root of the current `view` this will result in a no-op. - When this method is called, it will start a timer for - |carbon-setting-sync-delay| milliseconds after which |carbon-buffer-synchronize| - is called. When called twice within a single |carbon-setting-sync-delay| - the previous timer will be cancelled and a new one will be started. + When successful this sets `data.flash` to the entry identified by {path}. + This causes the next call to |carbon-view-render| to move to and highlight + the entry |carbon-view-flash-bang|. - `------------------------------------------------------------------------------` - process_enter *carbon-buffer-process-enter* + Given {path} will be converted to an absolute path via |carbon-util-resolve|. - Signature: `require('carbon.buffer').process_enter()` - Autocmd: |carbon-autocmd-bufenter| + This method also works when Carbon is not currently visible. It will fail + only if {path} is not a string. - This method sets |fillchars| `eob` to a space to hide the tilde characters after - the end of the buffer and disables |wrap|. + The buffer is NOT rerendered automatically, this must be done manually by + calling |carbon-view-render| afterwards. `------------------------------------------------------------------------------` - process_hidden *carbon-buffer-process-hidden* - - Signature: `require('carbon.buffer').process_hidden()` - Autocmd: |carbon-autocmd-bufhidden| + get_path_attr *carbon-view-get-path-attr* - This method restores |fillchars| and |wrap| back to their original values - before |carbon-buffer-process-enter| was called. + Signature: `view:get_path_attr(`{path}, {attr}`)` - It also `nils` out any `vim.w.carbon_*` window-local variables which are used - internally by some of Carbon's commands. + Get {attr} for {path} in the current `view`. When {attr} is `'compressible'` + and its value is `nil` then `true` is returned. `------------------------------------------------------------------------------` - is_loaded *carbon-buffer-is-loaded* + set_path_attr *carbon-view-set-path-attr* - Signature: `require('carbon.buffer').is_loaded()` + Signature: `view:set_path_attr(`{path}, {attr}`)` - Returns `true` or `false` depending on whether the current Carbon buffer handle is - valid and loaded. Uses |nvim_buf_is_loaded| internally. + Set {attr} for {path} in the current `view`. `------------------------------------------------------------------------------` - is_hidden *carbon-buffer-is-hidden* + buffers *carbon-view-buffers* - Signature: `require('carbon.buffer').is_hidden()` + Signature: `view:buffers()` - Returns `nil` if there is no Carbon buffer handle, `true` if the current buffer - handle is hidden, and `false` if the current buffer handle is not hidden. - A buffer is deemed to be hidden when |carbon-util-bufwinid| returns `nil`. + Returns a list of buffers which belong to the current `view`. This is done using + an internal `view.index` property which is stored in each buffer's `carbon` + local variable which is attached to new Carbon buffers via |carbon-view-buffer|. `------------------------------------------------------------------------------` - handle *carbon-buffer-handle* + buffer *carbon-view-buffer* - Signature: `require('carbon.buffer').handle()` + Signature: `view:buffer()` - Checks the current buffer handle and if valid, returns it. Otherwise creates - and configures a new buffer, updates the current buffer handle and returns - the new buffer handle. + Checks if a buffer for the current `view` already exists in + |carbon-view-buffers| and returns it. Otherwise creates and configures a new + buffer, updates the current buffer handle and returns the new buffer handle. - The buffer name will always be `'carbon'` and the following buffer-local - options are always set: + The buffer name is set to the root directory of the current `view` and the + following buffer-local options are always set: |swapfile| => `false` - |filetype| => `'carbon'` - |bufhidden| => `'hide'` + |filetype| => `'carbon.explorer'` + |bufhidden| => `'wipe'` |buftype| => `'nofile'` |modifiable| => `false` |modified| => `false` - Finally, if there are actions to map in |carbon-setting-actions| then they - will be mapped locally to this buffer to a || mapping with the - same suffix from |carbon-plugs|. + Actions in |carbon-setting-actions| will be mapped locally to this buffer to a + || mapping with the same suffix from |carbon-plugs|. + + Additionally, a buffer-local `carbon` variable is set so that Carbon can + figure out which buffers belong to Carbon. `------------------------------------------------------------------------------` - show *carbon-buffer-show* + update *carbon-view-update* - Signature: `require('carbon.buffer').show()` + Signature: `view:update()` - Show the Carbon buffer in the current window. Triggers a |carbon-buffer-render|. + Clears lines cached via |carbon-view-current-lines|. Does not automatically + rerender the buffer! `------------------------------------------------------------------------------` - render *carbon-buffer-render* + render *carbon-view-render* - Signature: `require('carbon.buffer').render()` + Signature: `view:render()` Renders the Carbon buffer. The contents are determined by calling the - |carbon-buffer-lines| method with the `data.root` entry object as only - argument. See |carbon-buffer-data-root| for more information about `data.root`. - - The Carbon buffer is NOT rendered if |carbon-buffer-is-loaded| returns - `false` or if |carbon-buffer-is-hidden| returns `true`. + |carbon-view-lines| method with the root entry object of the current `view` as + only argument. - *carbon-buffer-flash-bang* + *carbon-view-flash-bang* After rendering, if `data.flash` is set to an entry which is currently visible, - (it is set by |carbon-buffer-expand-to-path|) then the cursor will move to + (it is set by |carbon-view-expand-to-path|) then the cursor will move to that entry and reset `data.flash` to `nil`. Additionally, when this happens, that entry is also highlighted using @@ -1348,46 +1374,9 @@ BUFFER *carbon-buffe highlight are controlled by |carbon-setting-flash|. `------------------------------------------------------------------------------` - cursor *carbon-buffer-cursor* - - Signature: `require('carbon.buffer').cursor(`[{opts}]`)` - - Returns a table with the following shape: -> - { - line = , - target = , - target_line = , - } -< - Property: `line` - - An item at index `line('.')` from |carbon-buffer-lines|. - - Property: `target` - - Equal to `line.entry` by default but when called during a mapping, will respect - |v:count| where possible. This is used for example to start deleting from - "left to right" on compressed paths by prefixing a mapping with a [count]. - - When {opts}`.target_directory_only` is truthy and `target.is_directory` is `false` - then `target` will be set to `line.path[#line.path] or target.parent` BEFORE - any [count] logic is applied. - - One way to describe the difference between `line.entry` and `target` is that - `line.entry` will always point to the last path component entry on the current - line whereas `target` points to the entry targetted by the user. - - Property: `target_line` - - Like `line` but `target_line` refers to the line which `target` is located on. - - For more information about compressed paths, see: |carbon-setting-compress|. - - `------------------------------------------------------------------------------` - lines *carbon-buffer-lines* + lines *carbon-view-lines* - Signature: `require('carbon.buffer').lines(`{entry}[, {lines}[, {depth}]]`)` + Signature: `view:lines(`{target}[, {lines}[, {depth}]]`)` The {entry} argument must be an entry object as returned by |carbon-entry-new|. The {lines} argument is optional. Defaults to an empty table `{}`. When {lines} @@ -1418,17 +1407,17 @@ BUFFER *carbon-buffe Property: `lnum` - The line number of the entry. Used by |carbon-buffer-create| to determine + The line number of the entry. Used by |carbon-view-create| to determine the line number to move to for editing. Property: `depth` - The indent depth of the entry. Used by |carbon-buffer-create| to determine + The indent depth of the entry. Used by |carbon-view-create| to determine the start column to move to for editing. Property: `icon_width` - Width of file icons shown before a path. Used by |carbon-buffer-create| to + Width of file icons shown before a path. Used by |carbon-view-create| to determine the start column to move to for editing. Property: `highlights` @@ -1442,176 +1431,98 @@ BUFFER *carbon-buffe , } < - |carbon-buffer-render| uses |nvim_buf_add_highlight| to set the highlights. + |carbon-view-render| uses |nvim_buf_add_highlight| to set the highlights. It is called the following way: -> - vim.api.nvim_buf_add_highlight( - buffer.handle(), - require('util.constants').hl, - highlight[1], - lnum - 1, - highlight[2], - highlight[3], - ) -< + + `vim.api.nvim_buf_add_highlight(` + |carbon-view-buffer|, + |carbon-constants-hl|, + `highlight[1],` + `lnum - 1,` + `highlight[2],` + `highlight[3],` + `)` + Property: `path` When |carbon-setting-compress| is enabled, this table is filled with the compressed parent entry objects of `entry`. `------------------------------------------------------------------------------` - synchronize *carbon-buffer-synchronize* - - Signature: `require('carbon.buffer').synchronize()` - - Calls |carbon-entry-synchronize| on |carbon-buffer-data-root| and calls - |carbon-buffer-render| afterwards. - - `------------------------------------------------------------------------------` - up *carbon-buffer-up* - - Signature: `require('carbon.buffer').up(`[{count}]`)` - - Set |carbon-buffer-data-root| to its {count}th parent directory. When {count} - is not supplied, it will use `vim.v.count1` which defaults to `1` or [count] if - specified while executing this method in a mapping. - - This method moves the root up one level at a time. It queries the children - of the parent and replaces the child with the same path as the current - `data.root` with `data.root`. It also sets `data.root` to an open state so - when "zooming out" you still see what you had open. - - After doing all this, the `data.root` is finally set to the actual parent. - This routine then is repeated {count} `- 1` more times. - - When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. - - Returns `true` if the root is replaced at least once. `nil` otherwise. This - can happen when you are at your OS root directory and try to go up more. - - `------------------------------------------------------------------------------` - down *carbon-buffer-down* - - Signature: `require('carbon.buffer').down(`[{count}]`)` - - Set |carbon-buffer-data-root| to its {count}th child directory below the - cursor. When {count} is not supplied, it will use `vim.v.count1` which defaults - to `1` or [count] if specified while executing this method in a mapping. - - Specifying a {count} is only useful when the cursor is on a compressed path. - The |carbon-setting-compress| section explains how compressed paths work. - - With the cursor on this path `a/b/c/d/e.txt`, calling this method without - specifying a {count} will move the root to `/a`, you will now see the path - `b/c/d/e.txt`. If you call the method again, but this time you supply `2` as - {count} you will navigate "past" `c/` directly into `d/`. You will now only - see `e.txt`. - - When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. - - Returns `true` when the root is replaced, `nil` when the `new_root` path is the - same as the current `data.root` path in which case the root is not replaced. - - `------------------------------------------------------------------------------` - cd *carbon-buffer-cd* + current_lines *carbon-view-current-lines* - Signature: `require('carbon.buffer').cd(`{path}`)` + Signature: `view:current_lines()` - Set |carbon-buffer-data-root| to a |carbon-entry-new| created using {path}. - This method remembers the state of all child directories when navigating up - directories. When navigating to a child of the current working directory - Carbon tries look for any children in its own state to jump to without - having to perform additional file system reads. In any other case, Carbon - navigates to the {path} entry object. - - When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. - - `------------------------------------------------------------------------------` - reset *carbon-buffer-reset* - - Signature: `require('carbon.buffer').reset()` - - Set |carbon-buffer-data-root| to the original |pwd| using |carbon-buffer-cd|. - When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. + Returns cached lines generated from |carbon-view-lines| if present. + Otherwise |carbon-view-lines| is called and its results are cached. `------------------------------------------------------------------------------` - set_lines *carbon-buffer-set-lines* - - Signature: `require('carbon.buffer').set_lines(`{from}, {to}, {lines}`)` + cursor *carbon-view-cursor* - Makes the Carbon buffer |modifiable| and calls |nvim_buf_set_lines| like this: + Signature: `view:cursor(`[{opts}]`)` - `vim.api.nvim_buf_set_lines(`, {from}, {to}, 1, {lines}`)` - - After lines have been inserted Carbon sets |modified| to `false`. - Will also set |nomodifiable| to `true` except when called in |insert-mode|. - - will be set to the |bufnr| of the Carbon buffer. - - `------------------------------------------------------------------------------` - add_highlight *carbon-buffer-add-highlight* - - Signature: `require('carbon.buffer').add_highlight(`{hl}, {lnum}, {from}, {to}`)` + Returns a table with the following shape: +> + { + line = , + target = , + target_line = , + } +< + Property: `line` - Calls |nvim_buf_add_highlight| like this: + An item at index `line('.')` from |carbon-view-lines|. - `vim.api.nvim_buf_add_highlight(`, , {hl}, {lnum}, {from}, {to}`)` + Property: `target` - will be set to the |bufnr| of the Carbon buffer. - will be set to the namespace stored internally by Carbon. + Equal to `line.entry` by default but when called during a mapping, will respect + |v:count| where possible. This is used for example to start deleting from + "left to right" on compressed paths by prefixing a mapping with a [count]. - `------------------------------------------------------------------------------` - clear_namespace *carbon-buffer-clear-namespace* + When {opts}`.target_directory_only` is truthy and `target.is_directory` is `false` + then `target` will be set to `line.path[#line.path] or target.parent` BEFORE + any [count] logic is applied. - Signature: `require('carbon.buffer').clear_namespace(`{from}, {to}`)` + One way to describe the difference between `line.entry` and `target` is that + `line.entry` will always point to the last path component entry on the current + line whereas `target` points to the entry targetted by the user. - Calls |nvim_buf_clear_namespace| like this: + Property: `target_line` - `vim.api.nvim_buf_clear_namespace(`, , {from}, {to}`)` + Like `line` but `target_line` refers to the line which `target` is located on. - will be set to the |bufnr| of the Carbon buffer. - will be set to the namespace stored internally by Carbon. + For more information about compressed paths, see: |carbon-setting-compress|. `------------------------------------------------------------------------------` - clear_extmarks *carbon-buffer-clear-extmarks* - - Signature: `require('carbon.buffer').clear_extmarks(`{from}, {to}, {opts}`)` + show *carbon-view-show* - Wraps |nvim_buf_get_extmarks| and |nvim_buf_del_extmark|. This function - first calls |nvim_buf_get_extmarks| like this: + Signature: `view:show()` - `vim.api.nvim_buf_get_extmarks(`, , {from}, {to}, {opts}`)` + Sets the following local options: - The resulting list of |extmarks| will all be cleared by calling - |nvim_buf_del_extmark| like this: + - |wrap| to `false` + - |spell| to `false` + - |fillchars| to `{ eob = ' ' }` - `vim.api.nvim_buf_del_extmark(`, , `)` - - will be set to the |bufnr| of the Carbon buffer. - will be set to the namespace stored internally by Carbon. + Show the Carbon buffer in the current window. Triggers a |carbon-view-render|. `------------------------------------------------------------------------------` - entry_line *carbon-buffer-entry-line* - - Signature: `require('carbon.buffer').entry_line(`{entry}`)` + hide *carbon-view-hide* - Returns line information for given {entry}. The format is the same as a - single line result from |carbon-buffer-lines|. When {entry} is not visible - in the buffer this function returns `nil`. + Signature: `view:hide()` - An entry is visible when it is shown in the buffer. This is also true for - compressed path entries. While they are not displayed on their "own" line - they are visible "somewhere" in the buffer. + Resets the following local options to their global defaults: - In those cases, Carbon returns the line info of the rendered entry which - will then have {entry} in its `line.path`. + - |wrap| + - |spell| + - |fillchars| - See |carbon-setting-compress| for more information about compressed paths. + Clears sidebar and float flags. `------------------------------------------------------------------------------` - create *carbon-buffer-create* + create *carbon-view-create* - Signature: `require('carbon.buffer').create()` + Signature: `view:create()` When called, puts the Carbon buffer in a context-aware insert mode which allows a path to be typed. Pressing will confirm creation and pressing @@ -1722,9 +1633,9 @@ BUFFER *carbon-buffe behavior as if no count was supplied will be executed. `------------------------------------------------------------------------------` - delete *carbon-buffer-delete* + delete *carbon-view-delete* - Signature: `require('carbon.buffer').delete()` + Signature: `view:delete()` Prompts confirmation to delete the current entry under the cursor. When the current entry is a regular file or executable the file itself will be @@ -1783,9 +1694,9 @@ BUFFER *carbon-buffe then it will select the last component (the actual entry of that line). `------------------------------------------------------------------------------` - move *carbon-buffer-move* + move *carbon-view-move* - Signature: `require('carbon.buffer').move()` + Signature: `view:move()` Prompts the user for a new destination for the entry below the cursor. A [count] can be supplied to move starting from left to right on compressed @@ -1794,6 +1705,114 @@ BUFFER *carbon-buffe Intermediate directories will be created if they do not exist. + `------------------------------------------------------------------------------` + focus_flash *carbon-view-focus-flash* + + Signature: `view:focus_flash(`{duration}, {group}, {start}, {finish}`)` + + Highlights a region from {start} to {finish} with {group} for {duration} + milliseconds. {start} and {finish} must be values accepted by + |vim.highlight.range|. + + See |carbon-view-flash-bang| for more information about customizing the + highlighting properties when entries are revealed. + + `------------------------------------------------------------------------------` + close_sidebar *carbon-view-close-sidebar* + + Signature: `view.close_sidebar()` + + Closes a sidebar Carbon explorer window. + + `------------------------------------------------------------------------------` + close_float *carbon-view-close-float* + + Signature: `view.close_float()` + + Closes a floating Carbon explorer window. + + `------------------------------------------------------------------------------` + handle_sidebar_or_float *carbon-view-handle-sidebar-or-float* + + Signature: `view.handle_sidebar_or_float()` + + Executed before opening new files to handle sidebar/float mechanics i.e. + when |carbon-command-ToggleSidebarCarbon| is used to open a sidebar explorer and + a file is edited from there this function ensures that the buffer to the side + of the explorer is used to show the file. If such buffer does not exist it + creates it. + + `------------------------------------------------------------------------------` + set_root *carbon-view-set-root* + + Signature: `view:set_root(`{target}`)` + + The {new_root} argument can be an absolute path to a directory or a + |carbon-entry-new| entry. When passed as an absolute path it is converted to + an entry. The root of the current `view` is then set to this entry. + + When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. + This is only done when the original root directory matches the current + working directory. + + `------------------------------------------------------------------------------` + cd *carbon-view-cd* + + Signature: `view:cd(`{path}`)` + + Set the root of the current `view` to specified {path}. + When {path} is a parent of the current root directory of `view` then + |carbon-view-up| is used. Otherwise the root directory is set directly via + |carbon-view-set-root|. + + `------------------------------------------------------------------------------` + reset *carbon-view-reset* + + Signature: `view:reset()` + + Reset the root of the current `view` to the directory it originally showed. + + `------------------------------------------------------------------------------` + up *carbon-view-up* + + Signature: `view:up(`[{count}]`)` + + Set the root of the current `view` to its {count}th parent directory. When + {count} is not supplied, it will use `vim.v.count1` which defaults to `1` or + [count] if specified while executing this method in a mapping. + + When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. + This is only done when the original root directory matches the current + working directory. + + Returns `true` if the root is replaced at least once. `nil` otherwise. This + can happen when you are at your OS root directory and try to go up more. + + `------------------------------------------------------------------------------` + down *carbon-view-down* + + Signature: `view:down(`{count}`)` + + Set the root of the current `view` to its {count}th child directory below the + cursor. When {count} is not supplied, it will use `vim.v.count1` which defaults + to `1` or [count] if specified while executing this method in a mapping. + + Specifying a {count} is only useful when the cursor is on a compressed path. + The |carbon-setting-compress| section explains how compressed paths work. + + With the cursor on this path `a/b/c/d/e.txt`, calling this method without + specifying a {count} will move the root to `/a`, you will now see the path + `b/c/d/e.txt`. If you call the method again, but this time you supply `2` as + {count} you will navigate "past" `c/` directly into `d/`. You will now only + see `e.txt`. + + When |carbon-setting-sync-pwd| is enabled Neovim's |pwd| is updated automatically. + This is only done when the original root directory matches the current + working directory. + + Returns `true` when the root is replaced, `nil` when the `new_root` path is the + same as the current `data.root` path in which case the root is not replaced. + ================================================================================ WATCHER *carbon-watcher* @@ -1957,6 +1976,7 @@ SETTINGS *carbon-setting ` sidebar_toggle_focus = `|carbon-setting-sidebar-toggle-focus|`,` ` sidebar_position = `|carbon-setting-sidebar-position|`,` ` auto_reveal = `|carbon-setting-auto-reveal|`,` + ` open_on_dir = `|carbon-setting-open-on-dir|`,` ` exclude = `|carbon-setting-exclude|`,` ` indicators = {` @@ -2096,7 +2116,8 @@ SETTINGS *carbon-setting Disabled by default when |autochdir| is set to `1`, enabled by default when |autochdir| set to `0`. When enabled, Carbon listens for `:cd` commands. Once a `:cd` command is executed Carbon will automatically update - |carbon-buffer-data-root| to that path. + the root of a `view` pointing to the original current working directory + to the `:cd` path. `------------------------------------------------------------------------------` sync_delay *carbon-setting-sync-delay* @@ -2137,6 +2158,13 @@ SETTINGS *carbon-setting the current buffer. Enabling this will cause all versions of these commands to reveal the current buffer. + `------------------------------------------------------------------------------` + open_on_dir *carbon-setting-open-on-dir* + + Default: `true` + + Shows a Carbon buffer when opening directories. + `------------------------------------------------------------------------------` exclude *carbon-setting-exclude* @@ -2185,7 +2213,7 @@ SETTINGS *carbon-setting `}` Controls highlight {delay} and {duration} for entries revealed by - |carbon-buffer-expand-to-path|. Setting it to `nil` will disable + |carbon-view-expand-to-path|. Setting it to `nil` will disable highlighting of revealed entries. Uses |carbon-setting-highlights-CarbonFlash| to highlight the revealed entry. @@ -2197,13 +2225,13 @@ SETTINGS *carbon-setting function() local columns = vim.opt.columns:get() local rows = vim.opt.lines:get() - local width = math.min(50, columns * 0.8) - local height = math.min(20, rows * 0.8) + local width = math.min(40, columns * 0.9) + local height = math.min(20, rows * 0.9) return { relative = 'editor', style = 'minimal', - border = 'single', + border = 'rounded', width = width, height = height, col = math.floor(columns / 2 - width / 2), @@ -2294,6 +2322,10 @@ SETTINGS *carbon-setting ` ctermfg = 'DarkGray',` ` bold = true,` ` },` + ` CarbonFloat = {` *carbon-setting-highlights-CarbonFloat* + ` bg = '#111111',` + ` ctermbg = 'Black',` + ` },` ` CarbonDanger = {` *carbon-setting-highlights-CarbonDanger* ` link = 'Error',` ` },` From b134d5f74f2f7468e6a9d152b3ca51fdf223fa1f Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 9 Apr 2023 17:42:07 +0200 Subject: [PATCH 36/52] Use singular forms of carbon-constant-{subject} --- doc/carbon.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index 509639b..f0e083a 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -84,7 +84,7 @@ CONSTANTS *carbon-constant Exposes a table containing all the constants which Carbon uses. `------------------------------------------------------------------------------` - hl *carbon-constants-hl* + hl *carbon-constant-hl* Usage: `require('carbon.constants').hl` Value: `carbon` @@ -92,7 +92,7 @@ CONSTANTS *carbon-constant Used for all default highlighting. `------------------------------------------------------------------------------` - hl_tmp *carbon-constants-hl-tmp* + hl_tmp *carbon-constant-hl-tmp* Usage: `require('carbon.constants').hl_tmp` Value: `carbon:tmp` @@ -101,7 +101,7 @@ CONSTANTS *carbon-constant at the moment. `------------------------------------------------------------------------------` - augroup *carbon-constants-augroup* + augroup *carbon-constant-augroup* Usage: `require('carbon.constants').augroup` Value `carbon:tmp` @@ -110,7 +110,7 @@ CONSTANTS *carbon-constant at the moment. `------------------------------------------------------------------------------` - directions *carbon-constants-directions* + directions *carbon-constant-directions* Usage: `require('carbon.constants').directions` Value `{ left = 'h', right = 'l', up = 'k', down = 'j' }` @@ -206,7 +206,7 @@ AUTOCMDS *carbon-autocmd `------------------------------------------------------------------------------` BufWinEnter *carbon-autocmd-bufwinenter* - Group: |carbon-constants-augroup| + Group: |carbon-constant-augroup| Event: `BufWinEnter` Pattern: Implementation: `view:show()` @@ -217,7 +217,7 @@ AUTOCMDS *carbon-autocmd `------------------------------------------------------------------------------` BufHidden *carbon-autocmd-bufhidden* - Group: |carbon-constants-augroup| + Group: |carbon-constant-augroup| Event: `BufHidden` Pattern: Implementation: `view:hide()` @@ -228,7 +228,7 @@ AUTOCMDS *carbon-autocmd `------------------------------------------------------------------------------` CursorMovedI *carbon-autocmd-cursormovedi* - Group: |carbon-constants-augroup| + Group: |carbon-constant-augroup| Event: `CursorMovedI` Pattern: Implementation: @@ -240,7 +240,7 @@ AUTOCMDS *carbon-autocmd `------------------------------------------------------------------------------` DirChanged *carbon-autocmd-dirchanged* - Group: |carbon-constants-augroup| + Group: |carbon-constant-augroup| Event: `DirChanged` Pattern: `global` Implementation: `require('carbon').cd()` @@ -762,7 +762,7 @@ UTIL *carbon-uti `vim.api.nvim_buf_add_highlight(`{buf}, , {...}`)` - will be set to |carbon-constants-hl|. + will be set to |carbon-constant-hl|. `------------------------------------------------------------------------------` window_neighbors *carbon-util-window-neighbors* @@ -771,7 +771,7 @@ UTIL *carbon-uti Get windows on {sides} of {win} if present. The {sides} argument is a table of directions. Available directions are keys defined in - |carbon-constants-directions|. + |carbon-constant-directions|. Returns a table of results: @@ -1436,7 +1436,7 @@ VIEW *carbon-vie `vim.api.nvim_buf_add_highlight(` |carbon-view-buffer|, - |carbon-constants-hl|, + |carbon-constant-hl|, `highlight[1],` `lnum - 1,` `highlight[2],` From 976275b667b73ee803cdaf95e98eff7cd9109b26 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Mon, 10 Apr 2023 14:54:06 +0200 Subject: [PATCH 37/52] Tweak view documentation --- doc/carbon.txt | 144 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 40 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index f0e083a..faa23d2 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -778,8 +778,8 @@ UTIL *carbon-uti `{` `{` `origin = `{win}`,` - `position = string,` - `target = number,` + `position = ,` + `target = ,` `}`, `... other sides defined in `{sides} `}` @@ -1227,15 +1227,37 @@ VIEW *carbon-vie `------------------------------------------------------------------------------` get *carbon-view-get* - Signature: `view.get(`{path}`)` + Signature: `require('carbon.view').get(`{path}`)` - Returns an existing `view` for given {path}. Otherwise a new `view` is + Returns an existing `view` for given {path}. Otherwise a new `view` instance is created and returned. + *carbon-view-instance* + + A view instance includes information about a specific root directory. It + holds various state attributes such as which directories have been expanded + and which entries are compressible. + + Views instances exist to allow multiple Carbon buffers to exist which can + show different directories. They are stored in a list local to this module + to allow them being referenced by index. + + This is done to prevent confusion when moving the root directory of one view + instance to one which is already present in another view. + + A view instance includes the following data structure: + + `{` + `index = ,` + `initial = ,` + `states = >>,` + `root = ` + `}` + `------------------------------------------------------------------------------` activate *carbon-view-activate* - Signature: `view.activate(`[{options}]`)` + Signature: `require('carbon.view').activate(`[{options}]`)` Shows a `view` for `path` given in {options} in the current window. If `sidebar` in {options} is set to `'right'` or `'left'` then a new sidebar explorer is opened @@ -1245,15 +1267,15 @@ VIEW *carbon-vie `------------------------------------------------------------------------------` current *carbon-view-current* - Signature: `view.current()` + Signature: `require('carbon.view').current()` - Returns the `view` for the current buffer if it is a Carbon explorer. - When the current buffer is not a Carbon explorer `false` is returned. + Returns the |carbon-view-instance| for the current buffer if it is a Carbon + explorer. When the current buffer is not a Carbon explorer `false` is returned. `------------------------------------------------------------------------------` execute *carbon-view-execute* - Signature: `view.execute(`{callback}`)` + Signature: `require('carbon.view').execute(`{callback}`)` Calls {callback} when the current active buffer is a Carbon buffer. Does nothing otherwise. @@ -1262,13 +1284,13 @@ VIEW *carbon-vie `{` `cursor =` |carbon-view-cursor|`,` - `view = `|carbon-view-current|`,` + `view = `|carbon-view-instance|`,` `}` `------------------------------------------------------------------------------` resync *carbon-view-resync* - Signature: `view.resync(`{path}`)` + Signature: `require('carbon.view').resync(`{path}`)` Stores {path} in `view.resync_paths` and defers synchronization by |carbon-setting-sync-delay| milliseconds. This is done to batch multiple @@ -1277,11 +1299,38 @@ VIEW *carbon-vie Calls |carbon-entry-synchronize| on the root directory of every `view` after |carbon-setting-sync-delay| amount of time has passed without any modifications. + `------------------------------------------------------------------------------` + close_sidebar *carbon-view-close-sidebar* + + Signature: `require('carbon.view').close_sidebar()` + + Closes a sidebar Carbon explorer window. + + `------------------------------------------------------------------------------` + close_float *carbon-view-close-float* + + Signature: `require('carbon.view').close_float()` + + Closes a floating Carbon explorer window. + + `------------------------------------------------------------------------------` + handle_sidebar_or_float *carbon-view-handle-sidebar-or-float* + + Signature: `require('carbon.view').handle_sidebar_or_float()` + + Executed before opening new files to handle sidebar/float mechanics i.e. + when |carbon-command-ToggleSidebarCarbon| is used to open a sidebar explorer and + a file is edited from there this function ensures that the buffer to the side + of the explorer is used to show the file. If such buffer does not exist it + creates it. + `------------------------------------------------------------------------------` expand_to_path *carbon-view-expand-to-path* Signature: `view:expand_to_path(`{path}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Expands the Carbon buffer to reveal {path} in the tree. If {path} is not present within the root of the current `view` this will result in a no-op. @@ -1302,6 +1351,8 @@ VIEW *carbon-vie Signature: `view:get_path_attr(`{path}, {attr}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Get {attr} for {path} in the current `view`. When {attr} is `'compressible'` and its value is `nil` then `true` is returned. @@ -1310,6 +1361,8 @@ VIEW *carbon-vie Signature: `view:set_path_attr(`{path}, {attr}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Set {attr} for {path} in the current `view`. `------------------------------------------------------------------------------` @@ -1317,6 +1370,8 @@ VIEW *carbon-vie Signature: `view:buffers()` + The variable `view` in this section refers to a |carbon-view-instance|. + Returns a list of buffers which belong to the current `view`. This is done using an internal `view.index` property which is stored in each buffer's `carbon` local variable which is attached to new Carbon buffers via |carbon-view-buffer|. @@ -1326,6 +1381,8 @@ VIEW *carbon-vie Signature: `view:buffer()` + The variable `view` in this section refers to a |carbon-view-instance|. + Checks if a buffer for the current `view` already exists in |carbon-view-buffers| and returns it. Otherwise creates and configures a new buffer, updates the current buffer handle and returns the new buffer handle. @@ -1351,14 +1408,18 @@ VIEW *carbon-vie Signature: `view:update()` - Clears lines cached via |carbon-view-current-lines|. Does not automatically - rerender the buffer! + The variable `view` in this section refers to a |carbon-view-instance|. + + Clears lines of the current `view` cached via |carbon-view-current-lines|. + Does not automatically rerender the buffer! `------------------------------------------------------------------------------` render *carbon-view-render* Signature: `view:render()` + The variable `view` in this section refers to a |carbon-view-instance|. + Renders the Carbon buffer. The contents are determined by calling the |carbon-view-lines| method with the root entry object of the current `view` as only argument. @@ -1378,6 +1439,8 @@ VIEW *carbon-vie Signature: `view:lines(`{target}[, {lines}[, {depth}]]`)` + The variable `view` in this section refers to a |carbon-view-instance|. + The {entry} argument must be an entry object as returned by |carbon-entry-new|. The {lines} argument is optional. Defaults to an empty table `{}`. When {lines} is set to a table, this table will be used instead as accumulator value. @@ -1453,14 +1516,18 @@ VIEW *carbon-vie Signature: `view:current_lines()` - Returns cached lines generated from |carbon-view-lines| if present. - Otherwise |carbon-view-lines| is called and its results are cached. + The variable `view` in this section refers to a |carbon-view-instance|. + + Returns cached lines of the current `view` generated from |carbon-view-lines| if + present. Otherwise |carbon-view-lines| is called and its results are cached. `------------------------------------------------------------------------------` cursor *carbon-view-cursor* Signature: `view:cursor(`[{opts}]`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Returns a table with the following shape: > { @@ -1498,6 +1565,8 @@ VIEW *carbon-vie Signature: `view:show()` + The variable `view` in this section refers to a |carbon-view-instance|. + Sets the following local options: - |wrap| to `false` @@ -1511,6 +1580,8 @@ VIEW *carbon-vie Signature: `view:hide()` + The variable `view` in this section refers to a |carbon-view-instance|. + Resets the following local options to their global defaults: - |wrap| @@ -1524,6 +1595,8 @@ VIEW *carbon-vie Signature: `view:create()` + The variable `view` in this section refers to a |carbon-view-instance|. + When called, puts the Carbon buffer in a context-aware insert mode which allows a path to be typed. Pressing will confirm creation and pressing will cancel creation. @@ -1637,6 +1710,8 @@ VIEW *carbon-vie Signature: `view:delete()` + The variable `view` in this section refers to a |carbon-view-instance|. + Prompts confirmation to delete the current entry under the cursor. When the current entry is a regular file or executable the file itself will be removed. When it is a directory, the directory and all its contents will be @@ -1698,6 +1773,8 @@ VIEW *carbon-vie Signature: `view:move()` + The variable `view` in this section refers to a |carbon-view-instance|. + Prompts the user for a new destination for the entry below the cursor. A [count] can be supplied to move starting from left to right on compressed paths. The [count] is clamped. If it exceeds the amount of "components" in the @@ -1710,6 +1787,8 @@ VIEW *carbon-vie Signature: `view:focus_flash(`{duration}, {group}, {start}, {finish}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Highlights a region from {start} to {finish} with {group} for {duration} milliseconds. {start} and {finish} must be values accepted by |vim.highlight.range|. @@ -1717,36 +1796,13 @@ VIEW *carbon-vie See |carbon-view-flash-bang| for more information about customizing the highlighting properties when entries are revealed. - `------------------------------------------------------------------------------` - close_sidebar *carbon-view-close-sidebar* - - Signature: `view.close_sidebar()` - - Closes a sidebar Carbon explorer window. - - `------------------------------------------------------------------------------` - close_float *carbon-view-close-float* - - Signature: `view.close_float()` - - Closes a floating Carbon explorer window. - - `------------------------------------------------------------------------------` - handle_sidebar_or_float *carbon-view-handle-sidebar-or-float* - - Signature: `view.handle_sidebar_or_float()` - - Executed before opening new files to handle sidebar/float mechanics i.e. - when |carbon-command-ToggleSidebarCarbon| is used to open a sidebar explorer and - a file is edited from there this function ensures that the buffer to the side - of the explorer is used to show the file. If such buffer does not exist it - creates it. - `------------------------------------------------------------------------------` set_root *carbon-view-set-root* Signature: `view:set_root(`{target}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + The {new_root} argument can be an absolute path to a directory or a |carbon-entry-new| entry. When passed as an absolute path it is converted to an entry. The root of the current `view` is then set to this entry. @@ -1760,6 +1816,8 @@ VIEW *carbon-vie Signature: `view:cd(`{path}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Set the root of the current `view` to specified {path}. When {path} is a parent of the current root directory of `view` then |carbon-view-up| is used. Otherwise the root directory is set directly via @@ -1770,6 +1828,8 @@ VIEW *carbon-vie Signature: `view:reset()` + The variable `view` in this section refers to a |carbon-view-instance|. + Reset the root of the current `view` to the directory it originally showed. `------------------------------------------------------------------------------` @@ -1777,6 +1837,8 @@ VIEW *carbon-vie Signature: `view:up(`[{count}]`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Set the root of the current `view` to its {count}th parent directory. When {count} is not supplied, it will use `vim.v.count1` which defaults to `1` or [count] if specified while executing this method in a mapping. @@ -1793,6 +1855,8 @@ VIEW *carbon-vie Signature: `view:down(`{count}`)` + The variable `view` in this section refers to a |carbon-view-instance|. + Set the root of the current `view` to its {count}th child directory below the cursor. When {count} is not supplied, it will use `vim.v.count1` which defaults to `1` or [count] if specified while executing this method in a mapping. From 3543e46a5a96a1af1cb1acf43e2708333dbe8465 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Mon, 10 Apr 2023 16:24:33 +0200 Subject: [PATCH 38/52] Improve sidebar handling --- doc/carbon.txt | 8 ++++++++ lua/carbon.lua | 11 +++++++++++ lua/carbon/util.lua | 12 +++++++----- lua/carbon/view.lua | 30 +++++++++++++++++++++--------- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index faa23d2..6207dbf 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -427,6 +427,14 @@ CARBON *carbon-carbo If {params} is not passed or does not contain a `file` key, nothing is executed. + `------------------------------------------------------------------------------` + win_resized *carbon-carbon-win-resized* + + Signature: `require('carbon').win_resized()` + + Resizes the sidebar to |carbon-setting-sidebar-width| after a |WinResized| + event has occurred. When no sidebar is present this method does nothing. + `------------------------------------------------------------------------------` session_load_post *carbon-carbon-session-load-post* diff --git a/lua/carbon.lua b/lua/carbon.lua index aa26978..b317c93 100644 --- a/lua/carbon.lua +++ b/lua/carbon.lua @@ -40,6 +40,7 @@ function carbon.setup(user_settings) util.command('ToggleSidebarCarbon', carbon.toggle_sidebar, command_opts) 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 = '*' }) @@ -84,6 +85,16 @@ function carbon.setup(user_settings) end end +function carbon.win_resized() + if vim.api.nvim_win_is_valid(view.sidebar.origin) then + local window_width = vim.api.nvim_win_get_width(view.sidebar.origin) + + if window_width ~= settings.sidebar_width then + vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + end + end +end + function carbon.session_load_post(event) if util.is_directory(event.file) then local window_id = util.bufwinid(event.buf) diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index b08f9b6..b4a655d 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -278,11 +278,13 @@ function util.window_neighbors(window_id, sides) local side_id = vim.api.nvim_get_current_win() local result_id = window_id ~= side_id and side_id or nil - result[#result + 1] = { - origin = window_id, - position = side, - target = result_id, - } + if result_id then + result[#result + 1] = { + origin = window_id, + position = side, + target = result_id, + } + end end vim.api.nvim_set_current_win(original_window) diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index c4c6bfa..bf54c4f 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -110,14 +110,19 @@ function view.activate(options_param) if vim.api.nvim_win_is_valid(view.sidebar.origin) then vim.api.nvim_set_current_win(view.sidebar.origin) else - local split = options.sidebar == 'right' and 'rightbelow' or 'leftabove' + local split = options.sidebar == 'right' and 'botright' or 'topleft' + local target_side = options.sidebar == 'right' and 'left' or 'right' vim.cmd.split({ mods = { vertical = true, split = split } }) + local origin_id = vim.api.nvim_get_current_win() + local neighbor = util.window_neighbors(origin_id, { target_side })[1] + local target = neighbor and neighbor.target or original_window + view.sidebar = { position = options.sidebar, - origin = vim.api.nvim_get_current_win(), - target = original_window, + origin = origin_id, + target = target, } end @@ -168,13 +173,20 @@ function view.handle_sidebar_or_float() if vim.api.nvim_win_is_valid(view.sidebar.target) then vim.api.nvim_set_current_win(view.sidebar.target) else - local split = view.sidebar.position == 'right' and 'aboveleft' - or 'belowright' - - vim.cmd.split({ mods = { vertical = true, split = split } }) + local split = view.sidebar.position == 'right' and 'topleft' or 'botright' + local target_side = view.sidebar.position == 'right' and 'left' or 'right' + local neighbor = + util.window_neighbors(view.sidebar.origin, { target_side })[1] + + if neighbor then + view.sidebar.target = neighbor.target + vim.api.nvim_set_current_win(neighbor.target) + else + vim.cmd.split({ mods = { vertical = true, split = split } }) - view.sidebar.target = vim.api.nvim_get_current_win() - vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + view.sidebar.target = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_width(view.sidebar.origin, settings.sidebar_width) + end end elseif current_window == view.float.origin then view.close_float() From 8e0c7b4dde0951a6cdae5bdfc35ec1b6d3e8c532 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Mon, 10 Apr 2023 23:44:55 +0200 Subject: [PATCH 39/52] wip --- dev/init.lua | 2 +- .../{constants.lua => constants/init.lua} | 0 lua/carbon/{entry.lua => entry/init.lua} | 38 ++++++++---- lua/{carbon.lua => carbon/init.lua} | 0 .../{settings.lua => settings/init.lua} | 6 +- lua/carbon/{util.lua => util/init.lua} | 20 +++++-- lua/carbon/{view.lua => view/init.lua} | 55 +++++++++-------- lua/carbon/watcher/health.lua | 59 +++++++++++++++++++ lua/carbon/{watcher.lua => watcher/init.lua} | 45 +++++++------- test/specs/carbon_spec.lua | 16 +---- 10 files changed, 164 insertions(+), 77 deletions(-) rename lua/carbon/{constants.lua => constants/init.lua} (100%) rename lua/carbon/{entry.lua => entry/init.lua} (78%) rename lua/{carbon.lua => carbon/init.lua} (100%) rename lua/carbon/{settings.lua => settings/init.lua} (92%) rename lua/carbon/{util.lua => util/init.lua} (95%) rename lua/carbon/{view.lua => view/init.lua} (94%) create mode 100644 lua/carbon/watcher/health.lua rename lua/carbon/{watcher.lua => watcher/init.lua} (51%) diff --git a/dev/init.lua b/dev/init.lua index e39f98a..c47c42b 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -6,4 +6,4 @@ vim.opt.runtimepath:append({ vim.env.HOME .. '/.local/share/nvim/site/pack/packer/start/nvim-web-devicons', }) -require('carbon').setup({ file_icons = false }) +require('carbon').setup({ file_icons = false, sync_pwd = true }) diff --git a/lua/carbon/constants.lua b/lua/carbon/constants/init.lua similarity index 100% rename from lua/carbon/constants.lua rename to lua/carbon/constants/init.lua diff --git a/lua/carbon/entry.lua b/lua/carbon/entry/init.lua similarity index 78% rename from lua/carbon/entry.lua rename to lua/carbon/entry/init.lua index 82282b8..b7a6b10 100644 --- a/lua/carbon/entry.lua +++ b/lua/carbon/entry/init.lua @@ -1,8 +1,8 @@ local util = require('carbon.util') local watcher = require('carbon.watcher') local entry = {} -local data = { children = {} } +entry.items = {} entry.__index = entry entry.__lt = function(a, b) if a.is_directory and b.is_directory then @@ -17,14 +17,15 @@ entry.__lt = function(a, b) end function entry.new(path, parent) - local clean = string.gsub(path, '/+$', '') - local lstat = select(2, pcall(vim.loop.fs_lstat, clean)) or {} + local raw_path = path == '' and '/' or path + local clean = string.gsub(raw_path, '/+$', '') + local lstat = select(2, pcall(vim.loop.fs_lstat, raw_path)) or {} local is_executable = lstat.mode == 33261 local is_directory = lstat.type == 'directory' local is_symlink = lstat.type == 'link' and 1 if is_symlink then - local stat = select(2, pcall(vim.loop.fs_stat, clean)) + local stat = select(2, pcall(vim.loop.fs_stat, raw_path)) if stat then is_executable = lstat.mode == 33261 @@ -36,6 +37,7 @@ function entry.new(path, parent) end return setmetatable({ + raw_path = raw_path, path = clean, name = vim.fn.fnamemodify(clean, ':t'), parent = parent, @@ -46,7 +48,7 @@ function entry.new(path, parent) end function entry.find(path) - for _, children in pairs(data.children) do + for _, children in pairs(entry.items) do for _, child in ipairs(children) do if child.path == path then return child @@ -68,7 +70,7 @@ function entry:synchronize(paths) local all_paths = {} local current_paths = {} local previous_paths = {} - local previous_children = data.children[self.path] or {} + local previous_children = entry.items[self.path] or {} self:set_children(nil) @@ -117,7 +119,7 @@ function entry:terminate() if self.parent and self.parent:has_children() then self.parent:set_children(vim.tbl_filter(function(sibling) return sibling.path ~= self.path - end, data.children[self.parent.path])) + end, entry.items[self.parent.path])) end end @@ -126,20 +128,20 @@ function entry:children() self:set_children(self:get_children()) end - return data.children[self.path] or {} + return entry.items[self.path] or {} end function entry:has_children() - return data.children[self.path] and true or false + return entry.items[self.path] and true or false end function entry:set_children(children) - data.children[self.path] = children + entry.items[self.path] = children end function entry:get_children() local entries = {} - local handle = vim.loop.fs_scandir(self.path) + local handle = vim.loop.fs_scandir(self.raw_path) if type(handle) == 'userdata' then local function iterator() @@ -161,4 +163,18 @@ function entry:get_children() return entries end +function entry:highlight_group() + if self.is_symlink == 1 then + return 'CarbonSymlink' + elseif self.is_symlink == 2 then + return 'CarbonBrokenSymlink' + elseif self.is_directory then + return 'CarbonDir' + elseif self.is_executable then + return 'CarbonExe' + else + return 'CarbonFile' + end +end + return entry diff --git a/lua/carbon.lua b/lua/carbon/init.lua similarity index 100% rename from lua/carbon.lua rename to lua/carbon/init.lua diff --git a/lua/carbon/settings.lua b/lua/carbon/settings/init.lua similarity index 92% rename from lua/carbon/settings.lua rename to lua/carbon/settings/init.lua index 24c0da7..10d37ac 100644 --- a/lua/carbon/settings.lua +++ b/lua/carbon/settings/init.lua @@ -67,9 +67,9 @@ local defaults = { highlights = { CarbonDir = { link = 'Directory' }, CarbonFile = { link = 'Text' }, - CarbonExe = { link = 'NetrwExe' }, - CarbonSymlink = { link = 'NetrwSymLink' }, - CarbonBrokenSymlink = { link = 'ErrorMsg' }, + CarbonExe = { link = '@function.builtin' }, + CarbonSymlink = { link = '@include' }, + CarbonBrokenSymlink = { link = 'DiagnosticError' }, CarbonIndicator = { fg = 'Gray', ctermfg = 'DarkGray', bold = true }, CarbonFloat = { bg = '#111111', ctermbg = 'black' }, CarbonDanger = { link = 'Error' }, diff --git a/lua/carbon/util.lua b/lua/carbon/util/init.lua similarity index 95% rename from lua/carbon/util.lua rename to lua/carbon/util/init.lua index b4a655d..5d43fc8 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util/init.lua @@ -3,9 +3,11 @@ local settings = require('carbon.settings') local util = {} function util.resolve(path) - local normalized = vim.fs.normalize(path) - - return string.gsub(vim.fn.fnamemodify(normalized, ':p'), '/+$', '') + return string.gsub( + vim.fn.fnamemodify(vim.fs.normalize(path), ':p'), + '/+$', + '' + ) end function util.is_excluded(path) @@ -40,6 +42,16 @@ function util.tbl_key(tbl, item) end end +function util.tbl_some(tbl, callback) + for key, value in pairs(tbl) do + if callback(value, key) then + return true + end + end + + return false +end + function util.tbl_find(tbl, callback) for key, value in pairs(tbl) do if callback(value, key) then @@ -205,7 +217,7 @@ function util.create_scratch_buf(options) }, util.tbl_except(options, { 'name', 'lines', 'mappings', 'autocmds' })) if options.name then - vim.api.nvim_buf_set_name(buf, options.name) + vim.api.nvim_buf_set_name(buf, options.name == '' and '/' or options.name) end if options.lines then diff --git a/lua/carbon/view.lua b/lua/carbon/view/init.lua similarity index 94% rename from lua/carbon/view.lua rename to lua/carbon/view/init.lua index bf54c4f..89f880c 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view/init.lua @@ -4,11 +4,11 @@ local watcher = require('carbon.watcher') local settings = require('carbon.settings') local constants = require('carbon.constants') local view = {} -local views = {} view.__index = view view.sidebar = { origin = -1, target = -1 } view.float = { origin = -1, target = -1 } +view.items = {} view.resync_paths = {} local function create_leave(ctx) @@ -74,7 +74,7 @@ end function view.get(path) local resolved = util.resolve(path) - local found_view = util.tbl_find(views, function(target_view) + local found_view = util.tbl_find(view.items, function(target_view) return target_view.root.path == resolved end) @@ -82,7 +82,7 @@ function view.get(path) return found_view end - local index = #views + 1 + local index = #view.items + 1 local instance = setmetatable({ index = index, initial = resolved, @@ -90,7 +90,7 @@ function view.get(path) root = entry.new(resolved), }, view) - views[index] = instance + view.items[index] = instance return instance end @@ -197,7 +197,7 @@ function view.current() local bufnr = vim.api.nvim_get_current_buf() local ref = select(2, pcall(vim.api.nvim_buf_get_var, bufnr, 'carbon')) - return ref and views[ref.index] or false + return ref and view.items[ref.index] or false end function view.execute(callback) @@ -216,7 +216,7 @@ function view.resync(path) end view.resync_timer = vim.defer_fn(function() - for _, current_view in ipairs(views) do + for _, current_view in ipairs(view.items) do current_view.root:synchronize(view.resync_paths) current_view:update() current_view:render() @@ -507,6 +507,7 @@ function view:set_root(target) end self.root = target + vim.api.nvim_buf_set_name(self:buffer(), self.root.raw_path) vim.api.nvim_buf_set_var( self:buffer(), 'carbon', @@ -514,7 +515,9 @@ function view:set_root(target) ) watcher.keep(function(path) - return vim.startswith(path, self.root.path) + return util.tbl_some(view.items, function(current_view) + return vim.startswith(path, current_view.root.path) + end) end) if settings.sync_pwd and is_cwd then @@ -592,6 +595,8 @@ function view:lines(input_target, lines, depth) indicator = collapse_indicator elseif not is_empty then indicator = expand_indicator + else + indent = indent .. ' ' end else indent = indent .. ' ' @@ -613,7 +618,6 @@ function view:lines(input_target, lines, depth) icon_highlight = info[2] end - local link_group local full_path = tmp.name .. path_suffix local indent_end = #indent local icon_width = #icon ~= 0 and #icon + 1 or 0 @@ -630,14 +634,6 @@ function view:lines(input_target, lines, depth) full_path = dir_path .. '/' .. full_path end - if tmp.is_symlink == 1 then - link_group = 'CarbonSymlink' - elseif tmp.is_symlink == 2 then - link_group = 'CarbonBrokenSymlink' - elseif tmp.is_executable then - link_group = 'CarbonExe' - end - if indicator_width ~= 0 and not is_empty then hls[#hls + 1] = { 'CarbonIndicator', indent_end, indent_end + indicator_width } @@ -648,15 +644,26 @@ function view:lines(input_target, lines, depth) { icon_highlight, indent_end + indicator_width, path_start - 1 } end - if tmp.is_directory then - hls[#hls + 1] = { link_group or 'CarbonDir', path_start, -1 } - elseif path[1] then - local dir_end = path_start + #dir_path + 1 + local entries = { unpack(path) } + entries[#entries + 1] = tmp + + for _, current_entry in ipairs(entries) do + local part = current_entry.name .. '/' + local path_end = path_start + #part + local highlight_group = 'CarbonFile' + + if current_entry.is_symlink == 1 then + highlight_group = 'CarbonSymlink' + elseif current_entry.is_symlink == 2 then + highlight_group = 'CarbonBrokenSymlink' + elseif current_entry.is_directory then + highlight_group = 'CarbonDir' + elseif current_entry.is_executable then + highlight_group = 'CarbonExe' + end - hls[#hls + 1] = { link_group or 'CarbonDir', path_start, dir_end } - hls[#hls + 1] = { link_group or 'CarbonFile', dir_end, -1 } - else - hls[#hls + 1] = { link_group or 'CarbonFile', path_start, -1 } + hls[#hls + 1] = { highlight_group, path_start, path_end } + path_start = path_end end local line_prefix = indent diff --git a/lua/carbon/watcher/health.lua b/lua/carbon/watcher/health.lua new file mode 100644 index 0000000..2109ea5 --- /dev/null +++ b/lua/carbon/watcher/health.lua @@ -0,0 +1,59 @@ +local util = require('carbon.util') +local watcher = require('carbon.watcher') +local health = {} + +function health.check() + health.report_listeners() + health.report_events() +end + +function health.report_events() + vim.health.report_start('events') + + local names = vim.tbl_keys(watcher.events) + + table.sort(names, function(a, b) + return string.lower(a) < string.lower(b) + end) + + for _, name in ipairs(names) do + local callback_count = #vim.tbl_keys(watcher.events[name] or {}) + local reporter = callback_count == 0 and 'report_warn' or 'report_ok' + + vim.health[reporter]( + string.format( + '%d %s attached to %s', + callback_count, + callback_count == 1 and 'handler' or 'handlers', + name + ) + ) + end +end + +function health.report_listeners() + vim.health.report_start('listeners') + + local paths = vim.tbl_keys(watcher.listeners) + + table.sort(paths, function(a, b) + local a_is_directory = util.is_directory(a) + local b_is_directory = util.is_directory(b) + + if a_is_directory and b_is_directory then + return string.lower(a) < string.lower(b) + elseif a_is_directory then + return true + elseif b_is_directory then + return false + end + + return string.lower(a) < string.lower(b) + end) + + for _, path in ipairs(paths) do + vim.health.report_ok(path) + end +end + +return health diff --git a/lua/carbon/watcher.lua b/lua/carbon/watcher/init.lua similarity index 51% rename from lua/carbon/watcher.lua rename to lua/carbon/watcher/init.lua index 9a96c0d..c46c73c 100644 --- a/lua/carbon/watcher.lua +++ b/lua/carbon/watcher/init.lua @@ -1,9 +1,11 @@ local util = require('carbon.util') local watcher = {} -local data = { listeners = {}, events = {} } + +watcher.listeners = {} +watcher.events = {} function watcher.keep(callback) - for path in pairs(data.listeners) do + for path in pairs(watcher.listeners) do if not callback(path) then watcher.release(path) end @@ -12,21 +14,21 @@ end function watcher.release(path) if not path then - for listener_path in pairs(data.listeners) do + for listener_path in pairs(watcher.listeners) do watcher.release(listener_path) end - elseif data.listeners[path] then - data.listeners[path]:stop() + elseif watcher.listeners[path] then + watcher.listeners[path]:stop() - data.listeners[path] = nil + watcher.listeners[path] = nil end end function watcher.register(path) - if not data.listeners[path] and not util.is_excluded(path) then - data.listeners[path] = vim.loop.new_fs_event() + if not watcher.listeners[path] and not util.is_excluded(path) then + watcher.listeners[path] = vim.loop.new_fs_event() - data.listeners[path]:start( + watcher.listeners[path]:start( path, {}, vim.schedule_wrap(function(error, filename) @@ -37,11 +39,11 @@ function watcher.register(path) end function watcher.emit(event, ...) - for callback in pairs(data.events[event] or {}) do + for callback in pairs(watcher.events[event] or {}) do callback(event, ...) end - for callback in pairs(data.events['*'] or {}) do + for callback in pairs(watcher.events['*'] or {}) do callback(event, ...) end end @@ -52,33 +54,34 @@ function watcher.on(event, callback) watcher.on(key, callback) end elseif event then - data.events[event] = data.events[event] or {} - data.events[event][callback] = callback + watcher.events[event] = watcher.events[event] or {} + watcher.events[event][callback] = callback end end function watcher.off(event, callback) if not event then - data.events = {} + watcher.events = {} elseif type(event) == 'table' then for _, key in ipairs(event) do watcher.off(key, callback) end - elseif data.events[event] and callback then - data.events[event][callback] = nil - elseif event then - data.events[event] = {} + elseif watcher.events[event] and callback then + watcher.events[event][callback] = nil + elseif watcher.events[event] then + watcher.events[event] = {} else - data.events[event] = nil + watcher.events[event] = nil end end function watcher.has(event, callback) - return data.events[event] and data.events[event][callback] and true or false + return watcher.events[event] and watcher.events[event][callback] and true + or false end function watcher.registered() - return vim.tbl_keys(data.listeners) + return vim.tbl_keys(watcher.listeners) end return watcher diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index ac2b949..f29590f 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -12,7 +12,7 @@ describe('carbon', function() before_each(function() carbon.explore() util.cursor(1, 1) - vim.cmd.only() + vim.cmd.only({ mods = { silent = true } }) end) describe('autocommands', function() @@ -166,7 +166,7 @@ describe('carbon', function() carbon.toggle_recursive() assert.is_false(helpers.is_open(assets_entry.path)) assert.same( - { '+ doc/', '+ lua/' }, + { '+ doc/', '+ lua/carbon/' }, vim.api.nvim_buf_get_lines(0, 3, 5, true) ) end) @@ -322,19 +322,9 @@ describe('carbon', function() assert.equal(vim.loop.cwd(), string.format('%s/.github', original_cwd)) carbon.reset() + assert.equal(vim.loop.cwd(), original_cwd) settings.sync_pwd = settings.defaults.sync_pwd end) - - it('releases registered listeners not in new cwd', function() - local original_listeners = watcher.registered() - - util.cursor(2, 1) - carbon.down() - - assert.not_same(original_listeners, watcher.registered()) - - carbon.reset() - end) end) describe('cd', function() From 1e44f718877d66804d8c374628eb884389e09e32 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 15 Apr 2023 21:51:20 +0200 Subject: [PATCH 40/52] Use single health.lua for all checks --- lua/carbon/health.lua | 79 +++++++++++++++++++++++++++++++++++ lua/carbon/watcher/health.lua | 59 -------------------------- 2 files changed, 79 insertions(+), 59 deletions(-) create mode 100644 lua/carbon/health.lua delete mode 100644 lua/carbon/watcher/health.lua diff --git a/lua/carbon/health.lua b/lua/carbon/health.lua new file mode 100644 index 0000000..8977d5a --- /dev/null +++ b/lua/carbon/health.lua @@ -0,0 +1,79 @@ +local util = require('carbon.util') +local view = require('carbon.view') +local watcher = require('carbon.watcher') +local health = {} + +local function sort_names(a, b) + return string.lower(a) < string.lower(b) +end + +local function sort_paths(a, b) + local a_is_directory = util.is_directory(a) + local b_is_directory = util.is_directory(b) + + if a_is_directory and b_is_directory then + return sort_names(a, b) + elseif a_is_directory then + return true + elseif b_is_directory then + return false + end + + return sort_names(a, b) +end + +function health.check() + health.report_views() + health.report_listeners() + health.report_events() +end + +function health.report_views() + vim.health.report_start('view::active') + + local view_roots = vim.tbl_map(function(item) + return item.root + end, view.items) + + table.sort(view_roots) + + for _, root in ipairs(view_roots) do + vim.health.report_info(root.path) + end +end + +function health.report_events() + vim.health.report_start('watcher::events') + + local names = vim.tbl_keys(watcher.events) + + table.sort(names, sort_names) + + for _, name in ipairs(names) do + local callback_count = #vim.tbl_keys(watcher.events[name] or {}) + local reporter = callback_count == 0 and 'report_warn' or 'report_info' + + vim.health[reporter]( + string.format( + '%d %s attached to %s', + callback_count, + callback_count == 1 and 'handler' or 'handlers', + name + ) + ) + end +end + +function health.report_listeners() + vim.health.report_start('watcher::listeners') + + local paths = vim.tbl_keys(watcher.listeners) + + table.sort(paths, sort_paths) + + for _, path in ipairs(paths) do + vim.health.report_info(path) + end +end + +return health diff --git a/lua/carbon/watcher/health.lua b/lua/carbon/watcher/health.lua deleted file mode 100644 index 2109ea5..0000000 --- a/lua/carbon/watcher/health.lua +++ /dev/null @@ -1,59 +0,0 @@ -local util = require('carbon.util') -local watcher = require('carbon.watcher') -local health = {} - -function health.check() - health.report_listeners() - health.report_events() -end - -function health.report_events() - vim.health.report_start('events') - - local names = vim.tbl_keys(watcher.events) - - table.sort(names, function(a, b) - return string.lower(a) < string.lower(b) - end) - - for _, name in ipairs(names) do - local callback_count = #vim.tbl_keys(watcher.events[name] or {}) - local reporter = callback_count == 0 and 'report_warn' or 'report_ok' - - vim.health[reporter]( - string.format( - '%d %s attached to %s', - callback_count, - callback_count == 1 and 'handler' or 'handlers', - name - ) - ) - end -end - -function health.report_listeners() - vim.health.report_start('listeners') - - local paths = vim.tbl_keys(watcher.listeners) - - table.sort(paths, function(a, b) - local a_is_directory = util.is_directory(a) - local b_is_directory = util.is_directory(b) - - if a_is_directory and b_is_directory then - return string.lower(a) < string.lower(b) - elseif a_is_directory then - return true - elseif b_is_directory then - return false - end - - return string.lower(a) < string.lower(b) - end) - - for _, path in ipairs(paths) do - vim.health.report_ok(path) - end -end - -return health From f702c52ba8bfa7b4f0fa5e411b20475bb74097b6 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 15 Apr 2023 21:53:38 +0200 Subject: [PATCH 41/52] Un-nest plugin submodules --- lua/carbon/{constants/init.lua => constants.lua} | 0 lua/carbon/{entry/init.lua => entry.lua} | 0 lua/carbon/{settings/init.lua => settings.lua} | 0 lua/carbon/{util/init.lua => util.lua} | 0 lua/carbon/{view/init.lua => view.lua} | 0 lua/carbon/{watcher/init.lua => watcher.lua} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename lua/carbon/{constants/init.lua => constants.lua} (100%) rename lua/carbon/{entry/init.lua => entry.lua} (100%) rename lua/carbon/{settings/init.lua => settings.lua} (100%) rename lua/carbon/{util/init.lua => util.lua} (100%) rename lua/carbon/{view/init.lua => view.lua} (100%) rename lua/carbon/{watcher/init.lua => watcher.lua} (100%) diff --git a/lua/carbon/constants/init.lua b/lua/carbon/constants.lua similarity index 100% rename from lua/carbon/constants/init.lua rename to lua/carbon/constants.lua diff --git a/lua/carbon/entry/init.lua b/lua/carbon/entry.lua similarity index 100% rename from lua/carbon/entry/init.lua rename to lua/carbon/entry.lua diff --git a/lua/carbon/settings/init.lua b/lua/carbon/settings.lua similarity index 100% rename from lua/carbon/settings/init.lua rename to lua/carbon/settings.lua diff --git a/lua/carbon/util/init.lua b/lua/carbon/util.lua similarity index 100% rename from lua/carbon/util/init.lua rename to lua/carbon/util.lua diff --git a/lua/carbon/view/init.lua b/lua/carbon/view.lua similarity index 100% rename from lua/carbon/view/init.lua rename to lua/carbon/view.lua diff --git a/lua/carbon/watcher/init.lua b/lua/carbon/watcher.lua similarity index 100% rename from lua/carbon/watcher/init.lua rename to lua/carbon/watcher.lua From 9ac0bd4863ec7d59301046f0173fa68848111b6b Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 15 Apr 2023 22:27:44 +0200 Subject: [PATCH 42/52] Clarify requirement to call `setup` manually in `$MYVIMRC` files. --- README.md | 43 ++++++++++--------------------------------- doc/carbon.txt | 17 +++++++++++++---- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index ffab5fb..b471152 100644 --- a/README.md +++ b/README.md @@ -44,47 +44,30 @@ Install on Nightly Neovim (0.8.0+) using your favorite plugin manager: | **[dein.vim](https://github.com/Shougo/dein.vim)** | `call dein#add('SidOfc/carbon.nvim')` | | **[minpac](https://github.com/k-takata/minpac)** | `call minpac#add('SidOfc/carbon.nvim')` | | **[packer.nvim](https://github.com/wbthomason/packer.nvim)** | `use 'SidOfc/carbon.nvim'` | -| **[paq-nvim](https://github.com/savq/paq-nvim)** | `{ 'SidOfc/carbon.nvim', }` | +| **[paq-nvim](https://github.com/savq/paq-nvim)** | `{ 'SidOfc/carbon.nvim' }` | | **[lazy.nvim](https://github.com/folke/lazy.nvim)** | `{ 'SidOfc/carbon.nvim' }` | -# Configuration +# Usage and configuration -Configuration can be supplied like this: - -**init.vim** +Depending on whether you use native vim packages or a plugin manager +the way Carbon is set up will be slightly different. The most important +part is that Carbon's `setup` method **must** be called somewhere in your +`init.lua` / `init.vim` to initialize and use Carbon. It can be called like this: -```viml -lua << EOF - require('carbon').setup({ - setting = 'value', - }) -EOF +```lua +require('carbon').setup() ``` -**init.lua** +Configuration can be supplied like this: ```lua -require('carbon').setup({ - setting = 'value', -}) +require('carbon').setup({ setting = 'value' }) ``` These settings will be deep merged with the default settings. See `:h carbon-settings-table` for a list of available settings. An alternative option of calling this method also exists: -**init.vim** - -```viml -lua << EOF - require('carbon').setup(function(settings) - settings.setting = 'value' - end) -EOF -``` - -**init.lua** - ```lua require('carbon').setup(function(settings) settings.setting = 'value' @@ -97,12 +80,6 @@ You are free to modify them as you wish, no merging will occur. See `:h carbon-setup` for a more detailed explanation on configuration. See `:h carbon-carbon-setup` for documentation about the `.setup` method. -# Usage - -After installation, Carbon will launch automatically and disable NetRW. -These behaviors and many others can be customized, see `:h carbon-settings` for -more information about customization or `:h carbon-toc` for a table of contents. - Carbon comes with a few commands and mappings out of the box, each is described below: ## Commands diff --git a/doc/carbon.txt b/doc/carbon.txt index 6207dbf..f9bca5f 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -30,7 +30,17 @@ TABLE OF CONTENTS *carbon-contents* *carbon- ================================================================================ USAGE *carbon-usage* - Carbon automatically replaces |netrw| and remaps NetRW's |Explore| and |Lexplore| + In your |$MYVIMRC| file, run `require('carbon').setup()` to initialize Carbon. + + Depending on your plugin manager you may need to call this `setup` function + in a specific place or in a specific way. If you use any package manager + please consult its documentation to find out where `setup` functions may be run. + + When using regular Vim |packages| this `setup` function can simply be run + directly anywhere in your |$MYVIMRC| file since such packages are + automatically loaded. + + Carbon replaces |netrw| by default and remaps NetRW's |Explore| and |Lexplore| commands to Carbon's |carbon-command-Carbon| and |carbon-command-Lcarbon| commands respectively. For more specific usage and configuration information, see: @@ -42,7 +52,7 @@ USAGE *carbon-usag ================================================================================ SETUP *carbon-setup* - The behavior of this plugin can be customized by calling + The behavior of this plugin can be customized by providing settings to `require('carbon').setup` in your |$MYVIMRC| like this: init.lua: > @@ -411,8 +421,7 @@ CARBON *carbon-carbo It also attaches a callback to the watcher to enable auto-refreshing the buffer when file system events occur. - This method should be called only once per Neovim instance and must be called - before any buffer is created otherwise such buffer will not function properly. + This method must be called within your init.vim / init.lua configuration. `------------------------------------------------------------------------------` explore_buf_dir *carbon-carbon-explore-buf-dir* From 58fb6616ff3b40459dfce7b4ab5eea71845d83b6 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 17:21:10 +0200 Subject: [PATCH 43/52] Convert paths passed to explore to absolute paths --- doc/carbon.txt | 18 ++++++++++++++++++ lua/carbon/init.lua | 21 ++++++--------------- lua/carbon/util.lua | 22 ++++++++++++++++++++++ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index f9bca5f..e952fe4 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -665,6 +665,24 @@ UTIL *carbon-uti methods must always be considered unstable and should not be used in code external to Carbon. + `------------------------------------------------------------------------------` + explore_path *carbon-util-explore-path* + + Signature: `require('carbon.util').explore_path(`{path}[, {view}]`)` + + Converts {path} to an absolute path. When {view} (|carbon-view-instance|) is + given its `root.path` will be used as a base instead of |uv.cwd()| when {path} is + relative. + + If the current view or current working directory is `/example/directory` + and this function is called with `../` then the result will be `/example`. + For `../other/directory` the result will be `/example/other/directory`. + + When {path} starts with a `/` an absolute path is assumed. In this case no + base path will be prepended. + + When {path} is an empty string, it will default to |uv.cwd()|. + `------------------------------------------------------------------------------` is_excluded *carbon-util-is-excluded* diff --git a/lua/carbon/init.lua b/lua/carbon/init.lua index b317c93..04f0bc9 100644 --- a/lua/carbon/init.lua +++ b/lua/carbon/init.lua @@ -233,11 +233,8 @@ end function carbon.explore(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') - - if path == '' then - path = vim.loop.cwd() - end + local path = + util.explore_path(options.fargs and options.fargs[1] or '', view.current()) view.activate({ path = path, reveal = options.bang }) end @@ -264,12 +261,9 @@ end function carbon.explore_sidebar(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') local sidebar = options.sidebar or settings.sidebar_position - - if path == '' then - path = vim.loop.cwd() - end + local path = + util.explore_path(options.fargs and options.fargs[1] or '', view.current()) view.activate({ path = path, reveal = options.bang, sidebar = sidebar }) end @@ -296,11 +290,8 @@ end function carbon.explore_float(options_param) local options = options_param or {} - local path = options.fargs and string.gsub(options.fargs[1] or '', '%s', '') - - if path == '' then - path = vim.loop.cwd() - end + local path = + util.explore_path(options.fargs and options.fargs[1] or '', view.current()) view.activate({ path = path, reveal = options.bang, float = true }) end diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index 5d43fc8..87ee42a 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -2,6 +2,28 @@ local constants = require('carbon.constants') local settings = require('carbon.settings') local util = {} +function util.explore_path(path, current_view) + path = string.gsub(path, '%s', '') + + if path == '' then + path = vim.loop.cwd() + end + + if not vim.startswith(path, '/') then + local base_path = current_view and current_view.root.path or vim.loop.cwd() + + path = string.format('%s/%s', base_path, path) + end + + path = string.gsub( + vim.fn.fnamemodify(string.gsub(path, '/+$', '') .. '/', ':p'), + '/+$', + '' + ) + + return path +end + function util.resolve(path) return string.gsub( vim.fn.fnamemodify(vim.fs.normalize(path), ':p'), From 51cd31775c87b7c4cb7e2fe8a97453213e714098 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 17:21:48 +0200 Subject: [PATCH 44/52] Change some test names referring to buffer module to refer to view module --- test/specs/carbon_spec.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index f29590f..011936a 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -353,7 +353,7 @@ describe('carbon', function() end) describe('create', function() - it('calls buffer.create', function() + it('calls view.create', function() local view_create = spy.on(view, 'create') carbon.create() @@ -364,7 +364,7 @@ describe('carbon', function() end) describe('delete', function() - it('calls buffer.delete', function() + it('calls view.delete', function() local view_delete = spy.on(view, 'delete') carbon.delete() @@ -375,7 +375,7 @@ describe('carbon', function() end) describe('move', function() - it('calls buffer.move', function() + it('calls view.move', function() local view_move = spy.on(view, 'move') carbon.move() From a5f17177aa1613a2754f735b1226780071162a16 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 17:25:02 +0200 Subject: [PATCH 45/52] Implement `view.find` --- doc/carbon.txt | 12 ++++++++++-- lua/carbon/view.lua | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index e952fe4..1ab3ef7 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1259,13 +1259,21 @@ VIEW *carbon-vie utilities to show directory trees, interact with them, and keep them synchronized with changes from the file system. + `------------------------------------------------------------------------------` + find *carbon-view-find* + + Signature: `require('carbon.view').find(`{path}`)` + + Returns an existing `view` (|carbon-view-instance|) for given {path} or `nil` if no + instance for {path} exists. + `------------------------------------------------------------------------------` get *carbon-view-get* Signature: `require('carbon.view').get(`{path}`)` - Returns an existing `view` for given {path}. Otherwise a new `view` instance is - created and returned. + Returns an existing `view` (|carbon-view-instance|) for given {path}. Otherwise a + new `view` instance is created and returned. *carbon-view-instance* diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 89f880c..8fcc292 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -72,17 +72,23 @@ function view.file_icons() end end -function view.get(path) +function view.find(path) local resolved = util.resolve(path) - local found_view = util.tbl_find(view.items, function(target_view) + + return util.tbl_find(view.items, function(target_view) return target_view.root.path == resolved end) +end + +function view.get(path) + local found_view = view.find(path) if found_view then return found_view end local index = #view.items + 1 + local resolved = util.resolve(path) local instance = setmetatable({ index = index, initial = resolved, From d39ab7a3c842801a4405195427071ee0ba2825e8 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 17:28:59 +0200 Subject: [PATCH 46/52] Remove test which is no longer strictly true in all cases --- doc/carbon.txt | 18 ++++++++++++++++++ test/specs/carbon_spec.lua | 15 --------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index 1ab3ef7..cdd2d25 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1893,6 +1893,24 @@ VIEW *carbon-vie Returns `true` if the root is replaced at least once. `nil` otherwise. This can happen when you are at your OS root directory and try to go up more. + This method also tries to open the intermediate parent directories while + navigating to the desired location. + + This is not always possible to enfore because Carbon can have multiple views + representing different directories and the name of Carbon `view` buffers will + be the absolute path to these directories. + + This causes issues when trying to navigate to a path for which a `view` + already exists, because Carbon will rename the buffer to match its current + root path which will yield |E95|. + + To solve this Carbon checks if a `view` for the desired destination exists. + If this is the case, then Carbon skips traversing and opening parent + directories and will instead show the existing view directly. + + In doing this, Carbon will no longer expand intermediate parents between the + original `view` root and the destination `view` root. + `------------------------------------------------------------------------------` down *carbon-view-down* diff --git a/test/specs/carbon_spec.lua b/test/specs/carbon_spec.lua index 011936a..dbe74a9 100644 --- a/test/specs/carbon_spec.lua +++ b/test/specs/carbon_spec.lua @@ -281,21 +281,6 @@ describe('carbon', function() assert.not_same(original_listeners, watcher.registered()) end) - - it('automatically opens previous cwd', function() - util.cursor(1, 1) - - assert.is_equal('carbon.explorer', vim.bo.filetype) - - view.execute(function(ctx) - local root = ctx.view.root - - carbon.up() - assert.is_true(ctx.view:get_path_attr(root.path, 'open')) - end) - - carbon.reset() - end) end) describe('reset', function() From 9eb0597b14bdf1274982c6ad1675cb98da4fc2d5 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 17:47:57 +0200 Subject: [PATCH 47/52] Add parameter to skip renaming buffer to new root directory in `view:set_root` --- doc/carbon.txt | 7 +++++-- lua/carbon/view.lua | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index cdd2d25..00c384e 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1842,11 +1842,11 @@ VIEW *carbon-vie `------------------------------------------------------------------------------` set_root *carbon-view-set-root* - Signature: `view:set_root(`{target}`)` + Signature: `view:set_root(`{target}[, {opts}]`)` The variable `view` in this section refers to a |carbon-view-instance|. - The {new_root} argument can be an absolute path to a directory or a + The {target} argument can be an absolute path to a directory or a |carbon-entry-new| entry. When passed as an absolute path it is converted to an entry. The root of the current `view` is then set to this entry. @@ -1854,6 +1854,9 @@ VIEW *carbon-vie This is only done when the original root directory matches the current working directory. + The |carbon-view-buffer| name will be set to the new root of the `view` + unless {opts} is given and `opts.rename` is set to `false`. + `------------------------------------------------------------------------------` cd *carbon-view-cd* diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index 8fcc292..cb245d2 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -501,7 +501,8 @@ function view:down(count) end end -function view:set_root(target) +function view:set_root(target, options_param) + local options = options_param or {} local is_cwd = self.root.path == vim.loop.cwd() if type(target) == 'string' then @@ -513,7 +514,11 @@ function view:set_root(target) end self.root = target - vim.api.nvim_buf_set_name(self:buffer(), self.root.raw_path) + + if options.rename ~= false then + vim.api.nvim_buf_set_name(self:buffer(), self.root.raw_path) + end + vim.api.nvim_buf_set_var( self:buffer(), 'carbon', From f573abf42ce19f284c1ebf4ef06b6a4725a2428e Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 17:55:41 +0200 Subject: [PATCH 48/52] Simplify `view:up`, implement `view:parents` --- doc/carbon.txt | 23 +++++++++++++++++++++++ lua/carbon/view.lua | 42 +++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index 00c384e..2cc5501 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1941,6 +1941,29 @@ VIEW *carbon-vie Returns `true` when the root is replaced, `nil` when the `new_root` path is the same as the current `data.root` path in which case the root is not replaced. + `------------------------------------------------------------------------------` + parents *carbon-view-parents* + + Signature: `view:parents(`[{count}]`)` + + The variable `view` in this section refers to a |carbon-view-instance|. + + Returns a table of parent entries (|carbon-entry-new|) sorted from nearest to + farthest i.e. given a view whose root is `/a/b/c/d` and {count} set to `4` this + function returns: + + `{` + `,` + `,` + `,` + `,` + `}` + + An empty table is returned when there are no parents. If {count} is not + supplied it will default to `1` and result in either a table with a single + parent entry, or an empty table if there are no parents (i.e. the root + directory of the operating system). + ================================================================================ WATCHER *carbon-watcher* diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index cb245d2..e3d63ca 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -442,29 +442,15 @@ function view:show() end function view:up(count) - local rerender = false - local remaining = count or vim.v.count1 + local parents = self:parents(count) - while remaining > 0 do - remaining = remaining - 1 - local new_root = entry.new(vim.fn.fnamemodify(self.root.path, ':h')) - if new_root.path ~= self.root.path then - rerender = true - - new_root:set_children(vim.tbl_map(function(child) - if child.path == self.root.path then - self:set_path_attr(child.path, 'open', true) - end - - return child - end, new_root:get_children())) - - self:set_root(new_root) - end + for idx, parent_entry in ipairs(parents) do + self:set_path_attr(self.root.path, 'open', true) + self:set_root(parent_entry, { rename = idx == #parents }) end - return rerender + return #parents ~= 0 end function view:reset() @@ -912,4 +898,22 @@ function view:move() end end +function view:parents(count) + local path = self.root.path + local parents = {} + + if path ~= '' then + for _ = count or vim.v.count1, 1, -1 do + path = vim.fn.fnamemodify(path, ':h') + parents[#parents + 1] = entry.new(path) + + if path == '/' then + break + end + end + end + + return parents +end + return view From 9b11fc220b87ea309e94e8bb60b6b78bac9be780 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 18:03:12 +0200 Subject: [PATCH 49/52] Mark argument as optional --- doc/carbon.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index 2cc5501..f6a1c65 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1917,7 +1917,7 @@ VIEW *carbon-vie `------------------------------------------------------------------------------` down *carbon-view-down* - Signature: `view:down(`{count}`)` + Signature: `view:down(`[{count}]`)` The variable `view` in this section refers to a |carbon-view-instance|. From 6c6a9548f6cb19f2d736f640a6693fc8517069c5 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 18:07:40 +0200 Subject: [PATCH 50/52] Implement `view:switch_to_existing_view` --- doc/carbon.txt | 14 ++++++++++++++ lua/carbon/view.lua | 28 +++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index f6a1c65..10838cc 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1964,6 +1964,20 @@ VIEW *carbon-vie parent entry, or an empty table if there are no parents (i.e. the root directory of the operating system). + `------------------------------------------------------------------------------` + switch_to_existing_view *carbon-view-switch-to-existing-view* + + Signature: `view:switch_to_existing_view(`{path}`)` + + The variable `view` in this section refers to a |carbon-view-instance|. + + If an existing `view` instance exists which has its root set to {path} the + current window will have its current buffer replaced for the buffer + belonging to that existing view. + + Additionally, if the current `view` (before switching) root equals |uv.cwd()| and + |carbon-setting-sync-pwd| is set then the cwd of Neovim is updated as well. + ================================================================================ WATCHER *carbon-watcher* diff --git a/lua/carbon/view.lua b/lua/carbon/view.lua index e3d63ca..cb30582 100644 --- a/lua/carbon/view.lua +++ b/lua/carbon/view.lua @@ -443,7 +443,11 @@ end function view:up(count) local parents = self:parents(count) + local destination = parents[#parents] + if destination and self:switch_to_existing_view(destination.path) then + return true + end for idx, parent_entry in ipairs(parents) do self:set_path_attr(self.root.path, 'open', true) @@ -467,6 +471,8 @@ function view:cd(path) if current_depth - new_depth > 0 then return self:up(current_depth - new_depth) end + elseif self:switch_to_existing_view(path) then + return true else return self:set_root(entry.find(path) or entry.new(path)) end @@ -480,7 +486,13 @@ function view:down(count) new_root = new_root.parent end - if new_root.path ~= self.root.path then + if not new_root or new_root.path == self.root.path then + return false + end + + if self:switch_to_existing_view(new_root.path) then + return true + else self:set_path_attr(self.root.path, 'open', true) return self:set_root(new_root) @@ -916,4 +928,18 @@ function view:parents(count) return parents end +function view:switch_to_existing_view(path) + local destination_view = view.find(path) + + if destination_view then + vim.api.nvim_win_set_buf(0, destination_view:buffer()) + + if settings.sync_pwd and self.root.path == vim.loop.cwd() then + vim.api.nvim_set_current_dir(destination_view.root.path) + end + + return true + end +end + return view From fa29945bb8ae016959693eb53c5e257dad0cba67 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sun, 16 Apr 2023 18:26:40 +0200 Subject: [PATCH 51/52] Add `util.explore_path` tests --- lua/carbon/util.lua | 8 +------- test/specs/util_spec.lua | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lua/carbon/util.lua b/lua/carbon/util.lua index 87ee42a..a04ac38 100644 --- a/lua/carbon/util.lua +++ b/lua/carbon/util.lua @@ -15,13 +15,7 @@ function util.explore_path(path, current_view) path = string.format('%s/%s', base_path, path) end - path = string.gsub( - vim.fn.fnamemodify(string.gsub(path, '/+$', '') .. '/', ':p'), - '/+$', - '' - ) - - return path + return string.gsub(vim.fn.simplify(path), '/+$', '') end function util.resolve(path) diff --git a/test/specs/util_spec.lua b/test/specs/util_spec.lua index 5730289..7a50d12 100644 --- a/test/specs/util_spec.lua +++ b/test/specs/util_spec.lua @@ -1,10 +1,29 @@ require('test.config.assertions') local spy = require('luassert.spy') +local view = require('carbon.view') local util = require('carbon.util') local helpers = require('test.config.helpers') describe('carbon.util', function() + describe('explore_path', function() + it('{path} is expanded to an absolute path', function() + local cwd = vim.loop.cwd() + local parent = vim.fn.fnamemodify(cwd, ':h') + + assert.equal(parent, util.explore_path('../')) + assert.equal(parent, util.explore_path('..')) + end) + + it('{path} is expanded relative to {current_view}', function() + local current_view = view.get(vim.fn.tempname()) + local parent = vim.fn.fnamemodify(current_view.root.path, ':h') + + assert.equal(parent, util.explore_path('../', current_view)) + assert.equal(parent, util.explore_path('..', current_view)) + end) + end) + describe('cursor', function() it('{lnum} and {col} are both 1-based', function() util.cursor(2, 2) From 707819dc32e087826f94000df898b4c5aec2c019 Mon Sep 17 00:00:00 2001 From: Sidney Liebrand Date: Sat, 22 Apr 2023 16:04:42 +0200 Subject: [PATCH 52/52] Version bump --- doc/carbon.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/carbon.txt b/doc/carbon.txt index 10838cc..24c95c7 100644 --- a/doc/carbon.txt +++ b/doc/carbon.txt @@ -1,7 +1,7 @@ *carbon.nvim.txt* The simple directory tree viewer for Neovim written in Lua. *carbon.txt* - `Version: 0.18.3` + `Version: 0.19.0` `Licence: MIT` `Source: https://github.com/SidOfc/carbon.nvim` `Author: Sidney Liebrand `