diff --git a/lua/lib/conf.lua b/lua/lib/conf.lua new file mode 100644 index 00000000..f9635640 --- /dev/null +++ b/lua/lib/conf.lua @@ -0,0 +1,23 @@ +local BUF_NAME = 'LuaTree' +local function get_cwd() return vim.loop.cwd() end +local ROOT_PATH = get_cwd() .. '/' + +local function get_buf_name() + return BUF_NAME +end + +local function get_root_path() + return ROOT_PATH +end + +local function set_root_path(path) + ROOT_PATH = path +end + +return { + get_buf_name = get_buf_name; + get_root_path = get_root_path; + set_root_path = set_root_path; + get_cwd = get_cwd; +} + diff --git a/lua/lib/file.lua b/lua/lib/file.lua index 4bb0fb26..109957e1 100644 --- a/lua/lib/file.lua +++ b/lua/lib/file.lua @@ -1,6 +1,8 @@ local api = vim.api local buf, win local system = function(v) api.nvim_call_function('system', { v }) end +-- local update_tree_view = require '' +local update_tree_state = require 'lib/state'.update_tree local EDIT_FILE = nil @@ -109,6 +111,8 @@ local function add_file(path) system('touch ' .. path) end api.nvim_command("q!") + update_tree_state() + update_tree_view(true) end local function remove_file(confirmation) @@ -117,12 +121,16 @@ local function remove_file(confirmation) end EDIT_FILE = nil api.nvim_command("q!") + update_tree_state() + update_tree_view(true) end local function rename_file(path) system('mv '..EDIT_FILE..' '..path) EDIT_FILE = nil api.nvim_command("q!") + update_tree_state() + update_tree_view(true) end return { diff --git a/lua/lib/state.lua b/lua/lib/state.lua new file mode 100644 index 00000000..1878ea43 --- /dev/null +++ b/lua/lib/state.lua @@ -0,0 +1,134 @@ +local api = vim.api +local function syslist(v) return api.nvim_call_function('systemlist', { v }) end + +local Tree = {} + +local function is_dir(path) + local stat = vim.loop.fs_stat(path) + return stat and stat.type == 'directory' or false +end + +local function check_dir_access(path) + return vim.loop.fs_access(path, 'R') == true +end + +local function list_dirs(path) + local ls_cmd = 'ls -A --ignore=.git ' + if path == nil then + return syslist(ls_cmd) + elseif check_dir_access(path) == false then + -- TODO: display an error here (permission denied) + return {} + else + return syslist(ls_cmd .. path) + end +end + +local function sort_dirs(dirs) + local sorted_tree = {} + for _, node in pairs(dirs) do + if node.dir == true then + table.insert(sorted_tree, 1, node) + else + table.insert(sorted_tree, node) + end + end + + return sorted_tree +end + +local function create_nodes(path, depth, dirs) + local tree = {} + + if not string.find(path, '^.*/$') then path = path .. '/' end + + for i, name in pairs(dirs) do + tree[i] = { + path = path, + name = name, + depth = depth, + dir = is_dir(path .. name), + open = false, -- only relevant when its a dir + icon = true + } + end + + return sort_dirs(tree) +end + +local function init_tree(ROOT_PATH) + Tree = create_nodes(ROOT_PATH, 0, list_dirs()) + if ROOT_PATH ~= '/' then + table.insert(Tree, 1, { + path = ROOT_PATH, + name = '..', + depth = 0, + dir = true, + open = false, + icon = false + }) + end +end + +local function refresh_tree() + local cache = {} + + for _, v in pairs(Tree) do + if v.dir == true and v.open == true then + table.insert(cache, v.path .. v.name) + end + end + + init_tree() + + for i, node in pairs(Tree) do + if node.dir == true then + for _, path in pairs(cache) do + if node.path .. node.name == path then + node.open = true + local dirs = list_dirs(path) + for j, n in pairs(create_nodes(path, node.depth + 1, dirs)) do + table.insert(Tree, i + j, n) + end + -- TODO: maybe extract this function to make a recursive call to reexplore the list + -- when adding a new list of dirs to make sure we go to the bottom of the opened structure + -- otherwise it might close opened directories + -- im not sure how lua behaves in this matter + end + end + end + end +end + +local function open_dir(tree_index) + local node = Tree[tree_index]; + node.open = not node.open + + if node.open == false then + local next_index = tree_index + 1; + local next_node = Tree[next_index] + + while next_node ~= nil and next_node.depth ~= node.depth do + table.remove(Tree, next_index) + next_node = Tree[next_index] + end + else + local dirlist = list_dirs(node.path .. node.name) + local child_dirs = create_nodes(node.path .. node.name .. '/', node.depth + 1, dirlist) + + for i, n in pairs(child_dirs) do + table.insert(Tree, tree_index + i, n) + end + end +end + +local function get_tree() + return Tree +end + +return { + init_tree = init_tree; + get_tree = get_tree; + refresh_tree = refresh_tree; + open_dir = open_dir; +} diff --git a/lua/lib/winutils.lua b/lua/lib/winutils.lua new file mode 100644 index 00000000..490801e6 --- /dev/null +++ b/lua/lib/winutils.lua @@ -0,0 +1,129 @@ +local api = vim.api + +local libformat = require 'lib/format' +local format = libformat.format_tree +local highlight = libformat.highlight_buffer + +local stateutils = require 'lib/state' +local get_tree = stateutils.get_tree + +local confutils = require 'lib/conf' +local get_buf_name = confutils.get_buf_name +local get_root_path = confutils.get_root_path + +local function get_buf() + local BUF_NAME = get_buf_name() + local regex = '.*'..BUF_NAME..'$'; + + for _, win in pairs(api.nvim_list_wins()) do + local buf = api.nvim_win_get_buf(win) + local buf_name = api.nvim_buf_get_name(buf) + + if string.match(buf_name, regex) ~= nil then return buf end + end + + return nil +end + +local function get_win() + local BUF_NAME = get_buf_name() + local regex = '.*'..BUF_NAME..'$'; + + for _, win in pairs(api.nvim_list_wins()) do + local buf_name = api.nvim_buf_get_name(api.nvim_win_get_buf(win)) + if string.match(buf_name, regex) ~= nil then return win end + end + + return nil +end + +local function buf_setup() + api.nvim_command('setlocal nonumber norelativenumber winfixwidth winfixheight') + api.nvim_command('setlocal winhighlight=EndOfBuffer:LuaTreeEndOfBuffer') +end + +local function open() + local BUF_NAME = get_buf_name() + local ROOT_PATH = get_root_path() + local win_width = 30 + local options = { + bufhidden = 'wipe'; + buftype = 'nowrite'; + modifiable = false; + } + + local buf = api.nvim_create_buf(false, true) + api.nvim_buf_set_name(buf, BUF_NAME) + + for opt, val in pairs(options) do + api.nvim_buf_set_option(buf, opt, val) + end + + api.nvim_command('topleft '..win_width..'vnew') + api.nvim_win_set_buf(0, buf) + buf_setup() + api.nvim_command('echo "'..ROOT_PATH..'"') +end + +local function close() + local BUF_NAME = get_buf_name() + local win = get_win(BUF_NAME) + if not win then return end + + api.nvim_win_close(win, true) +end + +local function update_view(update_cursor) + local BUF_NAME = get_buf_name() + local buf = get_buf(BUF_NAME); + if not buf then return end + + local cursor = api.nvim_win_get_cursor(0) + local tree = get_tree() + + api.nvim_buf_set_option(buf, 'modifiable', true) + api.nvim_buf_set_lines(buf, 0, -1, false, format(tree)) + highlight(buf, tree) + api.nvim_buf_set_option(buf, 'modifiable', false) + + if update_cursor == true then + api.nvim_win_set_cursor(0, cursor) + end +end + + +local function is_win_open() + return get_buf() ~= nil +end + +local function set_mappings() + local buf = get_buf() + if not buf then return end + + local mappings = { + [''] = 'open_file("edit")'; + [''] = 'open_file("vsplit")'; + [''] = 'open_file("split")'; + [''] = 'open_file("chdir")'; + a = 'edit_file("add")'; + d = 'edit_file("delete")'; + r = 'edit_file("rename")'; + f = 'find_file()'; + } + + for k,v in pairs(mappings) do + api.nvim_buf_set_keymap(buf, 'n', k, ':lua require"tree".'..v..'', { + nowait = true, noremap = true, silent = true + }) + end +end + +return { + open = open; + close = close; + is_win_open = is_win_open; + update_view = update_view; + get_buf = get_buf; + get_win = get_win; + set_mappings = set_mappings; +} diff --git a/lua/tree.lua b/lua/tree.lua index bae20e66..3ba2cbfc 100644 --- a/lua/tree.lua +++ b/lua/tree.lua @@ -1,251 +1,29 @@ -local lib_file = require 'lib/file' -local format = require 'lib/format'.format_tree -local highlight = require 'lib/format'.highlight_buffer - local api = vim.api -local function syslist(v) return api.nvim_call_function('systemlist', { v }) end -local ROOT_PATH = vim.loop.cwd() .. '/' -local Tree = {} -local BUF_NAME = '_LuaTree_' +local lib_file = require 'lib/file' +local edit_add = lib_file.edit_add +local edit_remove = lib_file.edit_remove +local edit_rename = lib_file.edit_rename -local function is_dir(path) - local stat = vim.loop.fs_stat(path) - return stat and stat.type == 'directory' or false -end +local stateutils = require 'lib/state' +local get_tree = stateutils.get_tree +local init_tree = stateutils.init_tree +local open_dir = stateutils.open_dir +local check_dir_access = stateutils.check_dir_access -local function check_dir_access(path) - return vim.loop.fs_access(path, 'R') == true -end +local winutils = require 'lib/winutils' +local update_view = winutils.update_view +local is_win_open = winutils.is_win_open +local close = winutils.close +local open = winutils.open +local set_mappings = winutils.set_mappings -local function list_dirs(path) - local ls_cmd = 'ls -A --ignore=.git ' - if path == nil then - return syslist(ls_cmd) - elseif check_dir_access(path) == false then - -- TODO: display an error here (permission denied) - return {} - else - return syslist(ls_cmd .. path) - end -end +local conf = require 'lib/conf' +local set_root_path = conf.set_root_path +local get_root_path = conf.get_root_path +local get_cwd = conf.get_cwd -local function sort_dirs(dirs) - local sorted_tree = {} - for _, node in pairs(dirs) do - if node.dir == true then - table.insert(sorted_tree, 1, node) - else - table.insert(sorted_tree, node) - end - end - - return sorted_tree -end - -local function create_nodes(path, depth, dirs) - local tree = {} - - if not string.find(path, '^.*/$') then path = path .. '/' end - - for i, name in pairs(dirs) do - tree[i] = { - path = path, - name = name, - depth = depth, - dir = is_dir(path .. name), - open = false, -- only relevant when its a dir - icon = true - } - end - - return sort_dirs(tree) -end - -local function init_tree() - Tree = create_nodes(ROOT_PATH, 0, list_dirs()) - if ROOT_PATH ~= '/' then - table.insert(Tree, 1, { - path = ROOT_PATH, - name = '..', - depth = 0, - dir = true, - open = false, - icon = false - }) - end -end - -init_tree() - -local function get_buf() - local regex = '.*'..BUF_NAME..'$'; - - for _, win in pairs(api.nvim_list_wins()) do - local buf = api.nvim_win_get_buf(win) - local buf_name = api.nvim_buf_get_name(buf) - - if string.match(buf_name, regex) ~= nil then return buf end - end - - return nil -end - -local function get_win() - local regex = '.*'..BUF_NAME..'$'; - - for _, win in pairs(api.nvim_list_wins()) do - local buf_name = api.nvim_buf_get_name(api.nvim_win_get_buf(win)) - if string.match(buf_name, regex) ~= nil then return win end - end - - return nil -end - -local function buf_setup() - api.nvim_command('setlocal nonumber norelativenumber winfixwidth winfixheight') - api.nvim_command('setlocal winhighlight=EndOfBuffer:LuaTreeEndOfBuffer') -end - -local function open() - local win_width = 30 - local options = { - bufhidden = 'wipe'; - buftype = 'nowrite'; - modifiable = false; - } - - local buf = api.nvim_create_buf(false, true) - api.nvim_buf_set_name(buf, BUF_NAME) - - for opt, val in pairs(options) do - api.nvim_buf_set_option(buf, opt, val) - end - - api.nvim_command('topleft '..win_width..'vnew') - api.nvim_win_set_buf(0, buf) - buf_setup() - api.nvim_command('echo "'..ROOT_PATH..'"') -end - -local function close() - local win = get_win() - if not win then return end - - api.nvim_win_close(win, true) -end - -local function update_view(update_cursor) - local buf = get_buf(); - if not buf then return end - - local cursor = api.nvim_win_get_cursor(0) - - api.nvim_buf_set_option(buf, 'modifiable', true) - api.nvim_buf_set_lines(buf, 0, -1, false, format(Tree)) - highlight(buf, Tree) - api.nvim_buf_set_option(buf, 'modifiable', false) - - if update_cursor == true then - api.nvim_win_set_cursor(0, cursor) - end -end - -local function refresh_tree() - local cache = {} - - for _, v in pairs(Tree) do - if v.dir == true and v.open == true then - table.insert(cache, v.path .. v.name) - end - end - - init_tree() - - for i, node in pairs(Tree) do - if node.dir == true then - for _, path in pairs(cache) do - if node.path .. node.name == path then - node.open = true - local dirs = list_dirs(path) - for j, n in pairs(create_nodes(path, node.depth + 1, dirs)) do - table.insert(Tree, i + j, n) - end - end - end - end - end - - update_view() -end - -local function is_win_open() - return get_buf() ~= nil -end - -local function open_file(open_type) - local tree_index = api.nvim_win_get_cursor(0)[1] - local node = Tree[tree_index] - - if node.name == '..' then - api.nvim_command('cd ..') - if vim.loop.cwd() == '/' then - ROOT_PATH = '/' - else - ROOT_PATH = vim.loop.cwd() .. '/' - end - init_tree() - update_view() - elseif open_type == 'chdir' then - if node.dir == false or check_dir_access(node.path .. node.name) == false then return end - api.nvim_command('cd ' .. node.path .. node.name) - ROOT_PATH = vim.loop.cwd() .. '/' - init_tree() - update_view() - elseif node.dir == true then - local index = tree_index + 1; - node.open = not node.open - local next_node = Tree[index] - if next_node ~= nil and next_node.depth > node.depth then - while next_node ~= nil and next_node.depth ~= node.depth do - table.remove(Tree, index) - next_node = Tree[index] - end - else - local dirlist = list_dirs(node.path .. node.name) - local child_dirs = create_nodes(node.path .. node.name .. '/', node.depth + 1, dirlist) - for i, n in pairs(child_dirs) do - table.insert(Tree, tree_index + i, n) - end - end - - update_view(true) - else - api.nvim_command('wincmd l | '..open_type..' '.. node.path .. node.name) - end -end - -local function set_mappings() - local buf = get_buf() - if not buf then return end - - local mappings = { - [''] = 'open_file("edit")'; - [''] = 'open_file("vsplit")'; - [''] = 'open_file("split")'; - [''] = 'open_file("chdir")'; - a = 'edit_file("add")'; - d = 'edit_file("delete")'; - r = 'edit_file("rename")'; - f = 'find_file()'; - } - - for k,v in pairs(mappings) do - api.nvim_buf_set_keymap(buf, 'n', k, ':lua require"tree".'..v..'', { - nowait = true, noremap = true, silent = true - }) - end -end +init_tree(get_root_path()) local function toggle() if is_win_open() == true then @@ -257,29 +35,61 @@ local function toggle() end end -local function edit_file(edit_type) +local function open_file(open_type) local tree_index = api.nvim_win_get_cursor(0)[1] - local node = Tree[tree_index] + local tree = get_tree() + local node = tree[tree_index] + + if node.name == '..' then + api.nvim_command('cd ..') + + local new_path + if get_cwd() == '/' then + new_path = '/' + else + new_path = get_cwd() .. '/' + end + + set_root_path(new_path) + init_tree(new_path) + update_view() + elseif open_type == 'chdir' then + if node.dir == false or check_dir_access(node.path .. node.name) == false then return end + + api.nvim_command('cd ' .. node.path .. node.name) + local new_path = get_cwd() .. '/' + set_root_path(new_path) + init_tree(new_path) + update_view() + elseif node.dir == true then + open_dir(tree_index) + update_view(true) + else + api.nvim_command('wincmd l | '..open_type..' '.. node.path .. node.name) + end +end + +local function edit_file(edit_type) + local tree = get_tree() + local tree_index = api.nvim_win_get_cursor(0)[1] + local node = tree[tree_index] if edit_type == 'add' then if node.dir == true then - lib_file.edit_add(node.path .. node.name .. '/') + edit_add(node.path .. node.name .. '/') else - lib_file.edit_add(node.path) + edit_add(node.path) end elseif edit_type == 'delete' then - lib_file.edit_remove(node.name, node.path, node.dir) + edit_remove(node.name, node.path, node.dir) elseif edit_type == 'rename' then - lib_file.edit_rename(node.name, node.path, node.dir) + edit_rename(node.name, node.path, node.dir) end - - refresh_tree() end return { toggle = toggle; open_file = open_file; edit_file = edit_file; - refresh = refresh_tree; }