Embed the Carbon.
This commit is contained in:
parent
98587f4144
commit
6d861ae5f2
9 changed files with 2081 additions and 2 deletions
|
@ -15,8 +15,7 @@ require("maps")
|
||||||
|
|
||||||
require("bootstrap")
|
require("bootstrap")
|
||||||
require("dep") {
|
require("dep") {
|
||||||
require("plugin.carbon")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require("carbon").setup()
|
require("carbon").setup({})
|
||||||
|
|
||||||
|
|
6
nvim/lua/carbon/constants.lua
Normal file
6
nvim/lua/carbon/constants.lua
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
return {
|
||||||
|
hl = vim.api.nvim_create_namespace('carbon'),
|
||||||
|
hl_tmp = vim.api.nvim_create_namespace('carbon:tmp'),
|
||||||
|
augroup = vim.api.nvim_create_augroup('carbon', { clear = false }),
|
||||||
|
directions = { left = 'h', right = 'l', up = 'k', down = 'j' },
|
||||||
|
}
|
180
nvim/lua/carbon/entry.lua
Normal file
180
nvim/lua/carbon/entry.lua
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
local util = require('carbon.util')
|
||||||
|
local watcher = require('carbon.watcher')
|
||||||
|
local entry = {}
|
||||||
|
|
||||||
|
entry.items = {}
|
||||||
|
entry.__index = entry
|
||||||
|
entry.__lt = function(a, b)
|
||||||
|
if a.is_directory and b.is_directory then
|
||||||
|
return string.lower(a.name) < string.lower(b.name)
|
||||||
|
elseif a.is_directory then
|
||||||
|
return true
|
||||||
|
elseif b.is_directory then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.lower(a.name) < string.lower(b.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry.new(path, parent)
|
||||||
|
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, raw_path))
|
||||||
|
|
||||||
|
if stat then
|
||||||
|
is_executable = lstat.mode == 33261
|
||||||
|
is_directory = stat.type == 'directory'
|
||||||
|
is_symlink = 1
|
||||||
|
else
|
||||||
|
is_symlink = 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
raw_path = raw_path,
|
||||||
|
path = clean,
|
||||||
|
name = vim.fn.fnamemodify(clean, ':t'),
|
||||||
|
parent = parent,
|
||||||
|
is_directory = is_directory,
|
||||||
|
is_executable = is_executable,
|
||||||
|
is_symlink = is_symlink,
|
||||||
|
}, entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry.find(path)
|
||||||
|
for _, children in pairs(entry.items) do
|
||||||
|
for _, child in ipairs(children) do
|
||||||
|
if child.path == path then
|
||||||
|
return child
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry:synchronize(paths)
|
||||||
|
if not self.is_directory then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
paths = paths or {}
|
||||||
|
|
||||||
|
if paths[self.path] then
|
||||||
|
paths[self.path] = nil
|
||||||
|
|
||||||
|
local all_paths = {}
|
||||||
|
local current_paths = {}
|
||||||
|
local previous_paths = {}
|
||||||
|
local previous_children = entry.items[self.path] or {}
|
||||||
|
|
||||||
|
self:set_children(nil)
|
||||||
|
|
||||||
|
for _, previous in ipairs(previous_children) do
|
||||||
|
all_paths[previous.path] = true
|
||||||
|
previous_paths[previous.path] = previous
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, current in ipairs(self:children()) do
|
||||||
|
all_paths[current.path] = true
|
||||||
|
current_paths[current.path] = current
|
||||||
|
end
|
||||||
|
|
||||||
|
for path in pairs(all_paths) do
|
||||||
|
local current = current_paths[path]
|
||||||
|
local previous = previous_paths[path]
|
||||||
|
|
||||||
|
if previous and current then
|
||||||
|
if current.is_directory then
|
||||||
|
current:synchronize(paths)
|
||||||
|
end
|
||||||
|
elseif previous then
|
||||||
|
previous:terminate()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif self:has_children() then
|
||||||
|
for _, child in ipairs(self:children()) do
|
||||||
|
if child.is_directory then
|
||||||
|
child:synchronize(paths)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry:terminate()
|
||||||
|
watcher.release(self.path)
|
||||||
|
|
||||||
|
if self:has_children() then
|
||||||
|
for _, child in ipairs(self:children()) do
|
||||||
|
child:terminate()
|
||||||
|
end
|
||||||
|
|
||||||
|
self:set_children(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.parent and self.parent:has_children() then
|
||||||
|
self.parent:set_children(vim.tbl_filter(function(sibling)
|
||||||
|
return sibling.path ~= self.path
|
||||||
|
end, entry.items[self.parent.path]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry:children()
|
||||||
|
if self.is_directory and not self:has_children() then
|
||||||
|
self:set_children(self:get_children())
|
||||||
|
end
|
||||||
|
|
||||||
|
return entry.items[self.path] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry:has_children()
|
||||||
|
return entry.items[self.path] and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry:set_children(children)
|
||||||
|
entry.items[self.path] = children
|
||||||
|
end
|
||||||
|
|
||||||
|
function entry:get_children()
|
||||||
|
local entries = {}
|
||||||
|
local handle = vim.loop.fs_scandir(self.raw_path)
|
||||||
|
|
||||||
|
if type(handle) == 'userdata' then
|
||||||
|
local function iterator()
|
||||||
|
return vim.loop.fs_scandir_next(handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
for name in iterator do
|
||||||
|
local absolute_path = self.path .. '/' .. name
|
||||||
|
local relative_path = vim.fn.fnamemodify(absolute_path, ':.')
|
||||||
|
|
||||||
|
if not util.is_excluded(relative_path) then
|
||||||
|
entries[#entries + 1] = entry.new(absolute_path, self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(entries)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
79
nvim/lua/carbon/health.lua
Normal file
79
nvim/lua/carbon/health.lua
Normal file
|
@ -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
|
378
nvim/lua/carbon/init.lua
Normal file
378
nvim/lua/carbon/init.lua
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
local util = require('carbon.util')
|
||||||
|
local watcher = require('carbon.watcher')
|
||||||
|
local settings = require('carbon.settings')
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
if type(user_settings.highlights) == 'table' then
|
||||||
|
settings.highlights =
|
||||||
|
vim.tbl_extend('force', settings.highlights, user_settings.highlights)
|
||||||
|
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', function(_, path)
|
||||||
|
view.resync(path)
|
||||||
|
end)
|
||||||
|
|
||||||
|
util.command('Carbon', carbon.explore, command_opts)
|
||||||
|
util.command('Rcarbon', carbon.explore_right, command_opts)
|
||||||
|
util.command('Lcarbon', carbon.explore_left, command_opts)
|
||||||
|
util.command('Fcarbon', carbon.explore_float, command_opts)
|
||||||
|
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 = '*' })
|
||||||
|
end
|
||||||
|
|
||||||
|
--if settings.sync_on_cd then
|
||||||
|
util.autocmd('DirChanged', carbon.cd, { pattern = 'global' })
|
||||||
|
--end
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
util.command('Explore', carbon.explore, command_opts)
|
||||||
|
util.command('Rexplore', carbon.explore_right, command_opts)
|
||||||
|
util.command('Lexplore', carbon.explore_left, command_opts)
|
||||||
|
util.command('ToggleSidebarExplore', carbon.toggle_sidebar, command_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
for action in pairs(settings.defaults.actions) do
|
||||||
|
vim.keymap.set('', util.plug(action), carbon[action])
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(settings.highlights) == 'table' then
|
||||||
|
for group, properties in pairs(settings.highlights) do
|
||||||
|
util.highlight(group, properties)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
if
|
||||||
|
vim.fn.has('vim_starting')
|
||||||
|
and settings.auto_open
|
||||||
|
and util.is_directory(open)
|
||||||
|
then
|
||||||
|
view.activate({ path = open })
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
vim.g.carbon_initialized = true
|
||||||
|
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)
|
||||||
|
local window_width = vim.api.nvim_win_get_width(window_id)
|
||||||
|
local is_sidebar = window_width == settings.sidebar_width
|
||||||
|
|
||||||
|
view.activate({ path = event.file })
|
||||||
|
view.execute(function(ctx)
|
||||||
|
ctx.view:show()
|
||||||
|
end)
|
||||||
|
|
||||||
|
if is_sidebar then
|
||||||
|
local neighbor = util.tbl_find(
|
||||||
|
util.window_neighbors(window_id, { 'left', 'right' }),
|
||||||
|
function(neighbor)
|
||||||
|
return neighbor.target
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
if neighbor then
|
||||||
|
view.sidebar = neighbor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.toggle_recursive()
|
||||||
|
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(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({
|
||||||
|
ctx.cursor.line.entry.path,
|
||||||
|
mods = { keepalt = #vim.fn.getreg('#') ~= 0 },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.split()
|
||||||
|
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
|
||||||
|
|
||||||
|
view.handle_sidebar_or_float()
|
||||||
|
vim.cmd.split(ctx.cursor.line.entry.path)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.vsplit()
|
||||||
|
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
|
||||||
|
|
||||||
|
view.handle_sidebar_or_float()
|
||||||
|
vim.cmd.vsplit(ctx.cursor.line.entry.path)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.up()
|
||||||
|
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(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(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(ctx)
|
||||||
|
local destination = path and path.file or path or vim.v.event.cwd
|
||||||
|
|
||||||
|
if ctx.view:cd(destination) then
|
||||||
|
ctx.view:update()
|
||||||
|
ctx.view:render()
|
||||||
|
util.cursor(1, 1)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.explore(options_param)
|
||||||
|
local options = options_param or {}
|
||||||
|
local path =
|
||||||
|
util.explore_path(options.fargs and options.fargs[1] or '', view.current())
|
||||||
|
|
||||||
|
view.activate({ path = path, reveal = options.bang })
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.toggle_sidebar(options)
|
||||||
|
local current_win = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
|
if vim.api.nvim_win_is_valid(view.sidebar.origin) then
|
||||||
|
vim.api.nvim_win_close(view.sidebar.origin, 1)
|
||||||
|
else
|
||||||
|
local explore_options = vim.tbl_extend(
|
||||||
|
'force',
|
||||||
|
options or {},
|
||||||
|
{ sidebar = settings.sidebar_position }
|
||||||
|
)
|
||||||
|
|
||||||
|
carbon.explore_sidebar(explore_options)
|
||||||
|
|
||||||
|
if not settings.sidebar_toggle_focus then
|
||||||
|
vim.api.nvim_set_current_win(current_win)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.explore_sidebar(options_param)
|
||||||
|
local options = options_param or {}
|
||||||
|
local sidebar = options.sidebar or settings.sidebar_position
|
||||||
|
local path =
|
||||||
|
util.explore_path(options.fargs and options.fargs[1] or '', view.current())
|
||||||
|
|
||||||
|
view.activate({ path = path, reveal = options.bang, sidebar = sidebar })
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.explore_left(options_param)
|
||||||
|
if view.sidebar.position ~= 'left' then
|
||||||
|
view.close_sidebar()
|
||||||
|
end
|
||||||
|
|
||||||
|
carbon.explore_sidebar(
|
||||||
|
vim.tbl_extend('force', options_param or {}, { sidebar = 'left' })
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.explore_right(options_param)
|
||||||
|
if view.sidebar.position ~= 'right' then
|
||||||
|
view.close_sidebar()
|
||||||
|
end
|
||||||
|
|
||||||
|
carbon.explore_sidebar(
|
||||||
|
vim.tbl_extend('force', options_param or {}, { sidebar = 'right' })
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.explore_float(options_param)
|
||||||
|
local options = options_param or {}
|
||||||
|
local path =
|
||||||
|
util.explore_path(options.fargs and options.fargs[1] or '', view.current())
|
||||||
|
|
||||||
|
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 })
|
||||||
|
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)
|
||||||
|
elseif #vim.api.nvim_list_bufs() > 1 then
|
||||||
|
pcall(vim.cmd.bprevious)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.create()
|
||||||
|
view.execute(function(ctx)
|
||||||
|
ctx.view:create()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.delete()
|
||||||
|
view.execute(function(ctx)
|
||||||
|
ctx.view:delete()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.move()
|
||||||
|
view.execute(function(ctx)
|
||||||
|
ctx.view:move()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function carbon.close_parent()
|
||||||
|
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
|
||||||
|
|
||||||
|
line = util.tbl_find(lines, function(current)
|
||||||
|
return current.entry == entry or vim.tbl_contains(current.path, entry)
|
||||||
|
end)
|
||||||
|
|
||||||
|
if line then
|
||||||
|
vim.fn.cursor(line.lnum, (line.depth + 1) * 2 + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx.view:update()
|
||||||
|
ctx.view:render()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return carbon
|
82
nvim/lua/carbon/settings.lua
Normal file
82
nvim/lua/carbon/settings.lua
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
local defaults = {
|
||||||
|
sync_pwd = false,
|
||||||
|
compress = true,
|
||||||
|
auto_open = true,
|
||||||
|
keep_netrw = false,
|
||||||
|
file_icons = pcall(require, 'nvim-web-devicons'),
|
||||||
|
sync_on_cd = not vim.opt.autochdir:get(),
|
||||||
|
sync_delay = 20,
|
||||||
|
open_on_dir = true,
|
||||||
|
auto_reveal = false,
|
||||||
|
sidebar_width = 30,
|
||||||
|
sidebar_toggle_focus = true,
|
||||||
|
sidebar_position = 'left',
|
||||||
|
exclude = {
|
||||||
|
'~$',
|
||||||
|
'#$',
|
||||||
|
'%.git$',
|
||||||
|
'%.bak$',
|
||||||
|
'%.rbc$',
|
||||||
|
'%.class$',
|
||||||
|
'%.sw[a-p]$',
|
||||||
|
'%.py[cod]$',
|
||||||
|
'%.Trashes$',
|
||||||
|
'%.DS_Store$',
|
||||||
|
'Thumbs%.db$',
|
||||||
|
'__pycache__',
|
||||||
|
'node_modules',
|
||||||
|
},
|
||||||
|
indicators = {
|
||||||
|
expand = '+',
|
||||||
|
collapse = '-',
|
||||||
|
},
|
||||||
|
flash = {
|
||||||
|
delay = 50,
|
||||||
|
duration = 500,
|
||||||
|
},
|
||||||
|
float_settings = function()
|
||||||
|
local columns = vim.opt.columns:get()
|
||||||
|
local rows = vim.opt.lines:get()
|
||||||
|
local width = math.min(40, math.floor(columns * 0.9))
|
||||||
|
local height = math.min(20, math.floor(rows * 0.9))
|
||||||
|
|
||||||
|
return {
|
||||||
|
relative = 'editor',
|
||||||
|
style = 'minimal',
|
||||||
|
border = 'rounded',
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
col = math.floor(columns / 2 - width / 2),
|
||||||
|
row = math.floor(rows / 2 - height / 2 - 2),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
actions = {
|
||||||
|
up = '[',
|
||||||
|
down = ']',
|
||||||
|
quit = 'q',
|
||||||
|
edit = '<cr>',
|
||||||
|
move = 'm',
|
||||||
|
reset = 'u',
|
||||||
|
split = { '<c-x>', '<c-s>' },
|
||||||
|
vsplit = '<c-v>',
|
||||||
|
create = { 'c', '%' },
|
||||||
|
delete = 'd',
|
||||||
|
close_parent = '-',
|
||||||
|
toggle_recursive = '!',
|
||||||
|
},
|
||||||
|
highlights = {
|
||||||
|
CarbonDir = { link = 'Directory' },
|
||||||
|
CarbonFile = { link = 'Text' },
|
||||||
|
CarbonExe = { link = '@function.builtin' },
|
||||||
|
CarbonSymlink = { link = '@include' },
|
||||||
|
CarbonBrokenSymlink = { link = 'DiagnosticError' },
|
||||||
|
CarbonIndicator = { fg = 'Gray', ctermfg = 'DarkGray', bold = true },
|
||||||
|
CarbonFloat = { bg = '#111111', ctermbg = 'black' },
|
||||||
|
CarbonFloatBorder = { link = 'CarbonFloat' },
|
||||||
|
CarbonDanger = { link = 'Error' },
|
||||||
|
CarbonPending = { link = 'Search' },
|
||||||
|
CarbonFlash = { link = 'Visual' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return vim.tbl_extend('force', vim.deepcopy(defaults), { defaults = defaults })
|
323
nvim/lua/carbon/util.lua
Normal file
323
nvim/lua/carbon/util.lua
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
return string.gsub(vim.fn.simplify(path), '/+$', '')
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.resolve(path)
|
||||||
|
return string.gsub(
|
||||||
|
vim.fn.fnamemodify(vim.fs.normalize(path), ':p'),
|
||||||
|
'/+$',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.is_excluded(path)
|
||||||
|
if settings.exclude then
|
||||||
|
for _, pattern in ipairs(settings.exclude) do
|
||||||
|
if string.find(path, pattern) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.cursor(row, col)
|
||||||
|
return vim.api.nvim_win_set_cursor(0, { row, col - 1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.is_directory(path)
|
||||||
|
return (vim.loop.fs_stat(path) or {}).type == 'directory'
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.plug(name)
|
||||||
|
return string.format('<plug>(carbon-%s)', string.gsub(name, '_', '-'))
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.tbl_key(tbl, item)
|
||||||
|
for key, tbl_item in pairs(tbl) do
|
||||||
|
if tbl_item == item then
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
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
|
||||||
|
return value, key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.tbl_except(tbl, keys)
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
for key, value in pairs(tbl) do
|
||||||
|
if not vim.tbl_contains(keys, key) then
|
||||||
|
result[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.autocmd(event, cmd_or_callback, opts)
|
||||||
|
return vim.api.nvim_create_autocmd(
|
||||||
|
event,
|
||||||
|
vim.tbl_extend('force', {
|
||||||
|
group = constants.augroup,
|
||||||
|
callback = cmd_or_callback,
|
||||||
|
}, opts or {})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.clear_autocmd(event, opts)
|
||||||
|
return vim.api.nvim_clear_autocmds(vim.tbl_extend('force', {
|
||||||
|
group = constants.augroup,
|
||||||
|
event = event,
|
||||||
|
}, opts or {}))
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.command(lhs, rhs, options)
|
||||||
|
return vim.api.nvim_create_user_command(lhs, rhs, options or {})
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.highlight(group, opts)
|
||||||
|
local merged = vim.tbl_extend('force', { default = true }, opts or {})
|
||||||
|
|
||||||
|
vim.api.nvim_set_hl(0, group, merged)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.confirm(options)
|
||||||
|
local finished = false
|
||||||
|
local actions = {}
|
||||||
|
local mappings = {}
|
||||||
|
local lines = {}
|
||||||
|
|
||||||
|
local function finish(label, immediate)
|
||||||
|
local function handler()
|
||||||
|
if finished then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
finished = true
|
||||||
|
local callback = actions[label] and actions[label].callback
|
||||||
|
|
||||||
|
if type(callback) == 'function' then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.cmd.close()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not immediate then
|
||||||
|
return handler
|
||||||
|
end
|
||||||
|
|
||||||
|
handler()
|
||||||
|
end
|
||||||
|
|
||||||
|
for ascii = 32, 127 do
|
||||||
|
if
|
||||||
|
ascii < 48
|
||||||
|
and ascii > 57
|
||||||
|
and not vim.tbl_contains({ 38, 40, 74, 75, 106, 107 }, ascii)
|
||||||
|
then
|
||||||
|
mappings[#mappings + 1] = { 'n', string.char(ascii), '<nop>' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, action in ipairs(options.actions) do
|
||||||
|
actions[action.label] = action
|
||||||
|
|
||||||
|
if action.shortcut then
|
||||||
|
mappings[#mappings + 1] = { 'n', action.shortcut, finish(action.label) }
|
||||||
|
lines[#lines + 1] = ' [' .. action.shortcut .. '] ' .. action.label
|
||||||
|
else
|
||||||
|
lines[#lines + 1] = ' ' .. action.label
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mappings[#mappings + 1] = { 'n', '<esc>', finish('cancel') }
|
||||||
|
mappings[#mappings + 1] = {
|
||||||
|
'n',
|
||||||
|
'<cr>',
|
||||||
|
function()
|
||||||
|
finish(string.sub(vim.fn.getline('.'), 6), true)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local buf = util.create_scratch_buf({
|
||||||
|
modifiable = false,
|
||||||
|
lines = lines,
|
||||||
|
mappings = mappings,
|
||||||
|
autocmds = {
|
||||||
|
BufLeave = finish('cancel'),
|
||||||
|
CursorMoved = function()
|
||||||
|
util.cursor(vim.fn.line('.'), 3)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local win = vim.api.nvim_open_win(buf, true, {
|
||||||
|
relative = 'win',
|
||||||
|
anchor = 'NW',
|
||||||
|
border = 'single',
|
||||||
|
style = 'minimal',
|
||||||
|
row = options.row or vim.fn.line('.'),
|
||||||
|
col = options.col or vim.fn.col('.'),
|
||||||
|
height = #lines,
|
||||||
|
width = 1 + math.max(unpack(vim.tbl_map(function(line)
|
||||||
|
return #line
|
||||||
|
end, lines))),
|
||||||
|
})
|
||||||
|
|
||||||
|
util.cursor(1, 3)
|
||||||
|
vim.api.nvim_win_set_option(win, 'cursorline', true)
|
||||||
|
util.set_winhl(win, {
|
||||||
|
Normal = options.highlight or 'CarbonIndicator',
|
||||||
|
FloatBorder = options.highlight or 'Normal',
|
||||||
|
CursorLine = options.highlight or 'Normal',
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.bufwinid(buf)
|
||||||
|
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||||
|
if vim.api.nvim_win_get_buf(win) == buf then
|
||||||
|
return win
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.find_buf_by_name(name)
|
||||||
|
return util.tbl_find(vim.api.nvim_list_bufs(), function(bufnr)
|
||||||
|
return name == vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.create_scratch_buf(options)
|
||||||
|
options = options or {}
|
||||||
|
local found = util.find_buf_by_name(options.name)
|
||||||
|
local buf = found or vim.api.nvim_create_buf(false, true)
|
||||||
|
local buffer_options = vim.tbl_extend('force', {
|
||||||
|
bufhidden = 'wipe',
|
||||||
|
buftype = 'nofile',
|
||||||
|
swapfile = false,
|
||||||
|
}, util.tbl_except(options, { 'name', 'lines', 'mappings', 'autocmds' }))
|
||||||
|
|
||||||
|
if options.name then
|
||||||
|
vim.api.nvim_buf_set_name(buf, options.name == '' and '/' or options.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.lines then
|
||||||
|
vim.api.nvim_buf_set_lines(buf, 0, -1, 1, options.lines)
|
||||||
|
vim.api.nvim_buf_set_option(buf, 'modified', false)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.mappings then
|
||||||
|
util.set_buf_mappings(buf, options.mappings)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.autocmds then
|
||||||
|
util.set_buf_autocmds(buf, options.autocmds)
|
||||||
|
end
|
||||||
|
|
||||||
|
for option, value in pairs(buffer_options) do
|
||||||
|
vim.api.nvim_buf_set_option(buf, option, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.set_buf_mappings(buf, mappings)
|
||||||
|
for _, mapping in ipairs(mappings) do
|
||||||
|
vim.keymap.set(
|
||||||
|
mapping[1],
|
||||||
|
mapping[2],
|
||||||
|
mapping[3],
|
||||||
|
vim.tbl_extend('force', mapping[4] or {}, { buffer = buf })
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.set_buf_autocmds(buf, autocmds)
|
||||||
|
for autocmd, rhs in pairs(autocmds) do
|
||||||
|
util.autocmd(autocmd, rhs, { buffer = buf })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.set_winhl(win, highlights)
|
||||||
|
local winhls = {}
|
||||||
|
|
||||||
|
for source, target in pairs(highlights) do
|
||||||
|
winhls[#winhls + 1] = source .. ':' .. target
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function util.window_neighbors(window_id, sides)
|
||||||
|
local original_window = vim.api.nvim_get_current_win()
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
for _, side in ipairs(sides or {}) do
|
||||||
|
vim.api.nvim_set_current_win(window_id)
|
||||||
|
vim.cmd.wincmd(constants.directions[side])
|
||||||
|
|
||||||
|
local side_id = vim.api.nvim_get_current_win()
|
||||||
|
local result_id = window_id ~= side_id and side_id or nil
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
return util
|
945
nvim/lua/carbon/view.lua
Normal file
945
nvim/lua/carbon/view.lua
Normal file
|
@ -0,0 +1,945 @@
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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', '<cr>', { buffer = 0 })
|
||||||
|
vim.keymap.del('i', '<esc>', { 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.view:set_path_attr(ctx.target.path, '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.file_icons()
|
||||||
|
if settings.file_icons then
|
||||||
|
local ok, module = pcall(require, 'nvim-web-devicons')
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
return module
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function view.find(path)
|
||||||
|
local resolved = util.resolve(path)
|
||||||
|
|
||||||
|
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,
|
||||||
|
states = {},
|
||||||
|
root = entry.new(resolved),
|
||||||
|
}, view)
|
||||||
|
|
||||||
|
view.items[index] = instance
|
||||||
|
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
|
||||||
|
function view.activate(options_param)
|
||||||
|
local options = options_param or {}
|
||||||
|
local original_window = vim.api.nvim_get_current_win()
|
||||||
|
local current_view = (options.path and view.get(options.path))
|
||||||
|
or view.current()
|
||||||
|
or view.get(vim.loop.cwd())
|
||||||
|
|
||||||
|
if options.reveal or settings.auto_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
|
||||||
|
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 = origin_id,
|
||||||
|
target = target,
|
||||||
|
}
|
||||||
|
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:CarbonFloatBorder,Normal:CarbonFloat'
|
||||||
|
)
|
||||||
|
else
|
||||||
|
vim.api.nvim_win_set_buf(0, current_view:buffer())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function view.close_sidebar()
|
||||||
|
if vim.api.nvim_win_is_valid(view.sidebar.origin) then
|
||||||
|
vim.api.nvim_win_close(view.sidebar.origin, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
view.sidebar = { origin = -1, target = -1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
function view.close_float()
|
||||||
|
if vim.api.nvim_win_is_valid(view.float.origin) then
|
||||||
|
vim.api.nvim_win_close(view.float.origin, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
view.float = { origin = -1, target = -1 }
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif current_window == view.float.origin then
|
||||||
|
view.close_float()
|
||||||
|
end
|
||||||
|
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 view.items[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.resync(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(view.items) 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: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
|
||||||
|
|
||||||
|
self:update()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function view:get_path_attr(path, attr)
|
||||||
|
local state = self.states[path]
|
||||||
|
local value = 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)
|
||||||
|
if not self.states[path] then
|
||||||
|
self.states[path] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
self.states[path][attr] = value
|
||||||
|
|
||||||
|
return value
|
||||||
|
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:update()
|
||||||
|
self.cached_lines = nil
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
local buf = self:buffer()
|
||||||
|
|
||||||
|
vim.highlight.range(buf, constants.hl_tmp, group, start, finish, {})
|
||||||
|
|
||||||
|
vim.defer_fn(function()
|
||||||
|
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
|
||||||
|
|
||||||
|
function view:buffer()
|
||||||
|
local buffers = self:buffers()
|
||||||
|
|
||||||
|
if buffers[1] then
|
||||||
|
return buffers[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local mappings = {
|
||||||
|
{ 'n', 'i', '<nop>' },
|
||||||
|
{ 'n', 'I', '<nop>' },
|
||||||
|
{ 'n', 'o', '<nop>' },
|
||||||
|
{ 'n', 'O', '<nop>' },
|
||||||
|
}
|
||||||
|
|
||||||
|
for action, mapping in pairs(settings.actions or {}) do
|
||||||
|
if type(mapping) == 'string' then
|
||||||
|
mapping = { mapping }
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(mapping) == 'table' then
|
||||||
|
for _, key in ipairs(mapping) do
|
||||||
|
mappings[#mappings + 1] =
|
||||||
|
{ 'n', key, util.plug(action), { nowait = true } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local buffer = util.create_scratch_buf({
|
||||||
|
name = self.root.path,
|
||||||
|
filetype = 'carbon.explorer',
|
||||||
|
modifiable = false,
|
||||||
|
modified = false,
|
||||||
|
bufhidden = 'wipe',
|
||||||
|
mappings = mappings,
|
||||||
|
autocmds = {
|
||||||
|
BufHidden = function()
|
||||||
|
self:hide()
|
||||||
|
end,
|
||||||
|
BufWinEnter = function()
|
||||||
|
self:show()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_var(
|
||||||
|
buffer,
|
||||||
|
'carbon',
|
||||||
|
{ index = self.index, path = self.root.path }
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
self:set_root(parent_entry, { rename = idx == #parents })
|
||||||
|
end
|
||||||
|
|
||||||
|
return #parents ~= 0
|
||||||
|
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
|
||||||
|
elseif self:switch_to_existing_view(path) then
|
||||||
|
return true
|
||||||
|
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 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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
target = entry.new(target)
|
||||||
|
end
|
||||||
|
|
||||||
|
if target.path == self.root.path then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
self.root = target
|
||||||
|
|
||||||
|
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',
|
||||||
|
{ index = self.index, path = self.root.path }
|
||||||
|
)
|
||||||
|
|
||||||
|
watcher.keep(function(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
|
||||||
|
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 = ' '
|
||||||
|
local file_icons = view.file_icons()
|
||||||
|
|
||||||
|
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 } },
|
||||||
|
icon_width = 0,
|
||||||
|
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 self:get_path_attr(tmp.path, '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 self:get_path_attr(tmp.path, 'open') then
|
||||||
|
indicator = collapse_indicator
|
||||||
|
elseif not is_empty then
|
||||||
|
indicator = expand_indicator
|
||||||
|
else
|
||||||
|
indent = indent .. ' '
|
||||||
|
end
|
||||||
|
else
|
||||||
|
indent = indent .. ' '
|
||||||
|
end
|
||||||
|
|
||||||
|
local icon = ''
|
||||||
|
local icon_highlight
|
||||||
|
|
||||||
|
if file_icons and settings.file_icons and not tmp.is_directory then
|
||||||
|
local info = {
|
||||||
|
file_icons.get_icon(
|
||||||
|
tmp.name .. path_suffix,
|
||||||
|
vim.fn.fnamemodify(tmp.name, ':e'),
|
||||||
|
{ default = true }
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
icon = info[1] or ' '
|
||||||
|
icon_highlight = info[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
local full_path = tmp.name .. path_suffix
|
||||||
|
local indent_end = #indent
|
||||||
|
local icon_width = #icon ~= 0 and #icon + 1 or 0
|
||||||
|
local indicator_width = #indicator ~= 0 and #indicator + 1 or 0
|
||||||
|
local path_start = indent_end + icon_width + indicator_width
|
||||||
|
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 indicator_width ~= 0 and not is_empty then
|
||||||
|
hls[#hls + 1] =
|
||||||
|
{ 'CarbonIndicator', indent_end, indent_end + indicator_width }
|
||||||
|
end
|
||||||
|
|
||||||
|
if icon and icon_highlight then
|
||||||
|
hls[#hls + 1] =
|
||||||
|
{ icon_highlight, indent_end + indicator_width, path_start - 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
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] = { highlight_group, path_start, path_end }
|
||||||
|
path_start = path_end
|
||||||
|
end
|
||||||
|
|
||||||
|
local line_prefix = indent
|
||||||
|
|
||||||
|
if indicator_width ~= 0 then
|
||||||
|
line_prefix = line_prefix .. indicator .. ' '
|
||||||
|
end
|
||||||
|
|
||||||
|
if icon_width ~= 0 then
|
||||||
|
line_prefix = line_prefix .. icon .. ' '
|
||||||
|
end
|
||||||
|
|
||||||
|
lines[#lines + 1] = {
|
||||||
|
lnum = lnum,
|
||||||
|
depth = depth,
|
||||||
|
entry = tmp,
|
||||||
|
line = line_prefix .. full_path,
|
||||||
|
icon_width = icon_width,
|
||||||
|
highlights = hls,
|
||||||
|
path = path,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.is_directory and self:get_path_attr(tmp.path, '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 = self:get_path_attr(ctx.target.path, 'open')
|
||||||
|
ctx.prev_compressible = self:get_path_attr(ctx.target.path, 'compressible')
|
||||||
|
|
||||||
|
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
|
||||||
|
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', '<cr>', create_confirm(ctx), { buffer = 0 })
|
||||||
|
vim.keymap.set('i', '<esc>', 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',
|
||||||
|
cursor.line.depth * 2 + 2 + cursor.line.icon_width,
|
||||||
|
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 + target_line.icon_width
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
87
nvim/lua/carbon/watcher.lua
Normal file
87
nvim/lua/carbon/watcher.lua
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
local util = require('carbon.util')
|
||||||
|
local watcher = {}
|
||||||
|
|
||||||
|
watcher.listeners = {}
|
||||||
|
watcher.events = {}
|
||||||
|
|
||||||
|
function watcher.keep(callback)
|
||||||
|
for path in pairs(watcher.listeners) do
|
||||||
|
if not callback(path) then
|
||||||
|
watcher.release(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.release(path)
|
||||||
|
if not path then
|
||||||
|
for listener_path in pairs(watcher.listeners) do
|
||||||
|
watcher.release(listener_path)
|
||||||
|
end
|
||||||
|
elseif watcher.listeners[path] then
|
||||||
|
watcher.listeners[path]:stop()
|
||||||
|
|
||||||
|
watcher.listeners[path] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.register(path)
|
||||||
|
if not watcher.listeners[path] and not util.is_excluded(path) then
|
||||||
|
watcher.listeners[path] = vim.loop.new_fs_event()
|
||||||
|
|
||||||
|
watcher.listeners[path]:start(
|
||||||
|
path,
|
||||||
|
{},
|
||||||
|
vim.schedule_wrap(function(error, filename)
|
||||||
|
watcher.emit('carbon:synchronize', path, filename, error)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.emit(event, ...)
|
||||||
|
for callback in pairs(watcher.events[event] or {}) do
|
||||||
|
callback(event, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
for callback in pairs(watcher.events['*'] or {}) do
|
||||||
|
callback(event, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.on(event, callback)
|
||||||
|
if type(event) == 'table' then
|
||||||
|
for _, key in ipairs(event) do
|
||||||
|
watcher.on(key, callback)
|
||||||
|
end
|
||||||
|
elseif event then
|
||||||
|
watcher.events[event] = watcher.events[event] or {}
|
||||||
|
watcher.events[event][callback] = callback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.off(event, callback)
|
||||||
|
if not event then
|
||||||
|
watcher.events = {}
|
||||||
|
elseif type(event) == 'table' then
|
||||||
|
for _, key in ipairs(event) do
|
||||||
|
watcher.off(key, callback)
|
||||||
|
end
|
||||||
|
elseif watcher.events[event] and callback then
|
||||||
|
watcher.events[event][callback] = nil
|
||||||
|
elseif watcher.events[event] then
|
||||||
|
watcher.events[event] = {}
|
||||||
|
else
|
||||||
|
watcher.events[event] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.has(event, callback)
|
||||||
|
return watcher.events[event] and watcher.events[event][callback] and true
|
||||||
|
or false
|
||||||
|
end
|
||||||
|
|
||||||
|
function watcher.registered()
|
||||||
|
return vim.tbl_keys(watcher.listeners)
|
||||||
|
end
|
||||||
|
|
||||||
|
return watcher
|
Loading…
Reference in a new issue