├── .gitignore ├── LICENSE ├── README.md ├── lua └── LightningFileExplorer │ └── lua.lua └── plugin └── LightningFileExplorer.vim /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zoxves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚡ Lightning
    ⚡ File
        ⚡ Explorer 2 | Lightning-fast, lightweight, Lua-powered native file explorer for Neovim. 3 | The plugin is very basic and displays local files in lightning-like structure (i.e. tree). 4 | 5 | ## Installation 6 | ``` 7 | Plug 'zoxves/LightningFileExplorer' 8 | ``` 9 | ## Usage 10 | Basic: 11 | `:LiFE` or `:LiFE /Path/To/` or `:lua LiFE.open('/Path/To/')` 12 | 13 | Additional configuration for NERDTree-like panel on `Alt+\`: 14 | ``` 15 | augroup LiFE_mapping 16 | autocmd! 17 | autocmd filetype LightningFileExplorer call LiFEMapping() 18 | augroup END 19 | 20 | function! LiFEMapping() 21 | setlocal statusline=%{getcwd()}\ 22 | lua LiFE.click_on_file = function(entry) vim.api.nvim_command('wincmd l | edit ' .. entry.path) end 23 | nmap <2-LeftMouse> 24 | endfunction 25 | 26 | function! LLiFE() 27 | 40vsplit 28 | \| LiFE 29 | endfunction 30 | 31 | noremap :call LLiFE() 32 | ``` 33 | As you can see in example above, you can add/replace functions of the plugin. Take a look at the code. Don't be afraid, it's just around 200 lines of Lua! 34 | -------------------------------------------------------------------------------- /lua/LightningFileExplorer/lua.lua: -------------------------------------------------------------------------------- 1 | LiFE = { 2 | entries = {}, 3 | path = {}, 4 | branch_line = '|- ', 5 | trunk_line = '| ', 6 | filetype = 'LightningFileExplorer', 7 | 8 | clean = function() 9 | local buf_list = vim.api.nvim_list_bufs 10 | for buf,_ in pairs(LiFE.entries) do 11 | if not(vim.api.nvim_buf_is_loaded(buf)) or not(vim.api.nvim_buf_is_valid(buf)) or 12 | vim.api.nvim_buf_get_option(buf, 'filetype') ~= LiFE.filetype then 13 | table.remove(LiFE.entries, buf) 14 | table.remove(LiFE.path, buf) 15 | end 16 | end 17 | end, 18 | 19 | get_list_lines_offset = function(buf) 20 | return 2 21 | end, 22 | 23 | --TODO 24 | get_header = function(buf) 25 | return { 26 | LiFE.path[buf], 27 | "../", 28 | } 29 | end, 30 | 31 | open = function(path) 32 | if path == '' then 33 | path = '.' 34 | end 35 | path = vim.loop.fs_realpath(path) 36 | path = string.gsub(path, '\\', '/') 37 | local dir = vim.loop.fs_opendir(path) 38 | if dir == nil then 39 | print('LiFE: incorrect path: ' .. path) 40 | else 41 | vim.loop.fs_closedir(dir) 42 | end 43 | 44 | if string.sub(path, -1) ~= '/' then 45 | path = path .. '/' 46 | end 47 | 48 | local buf = vim.api.nvim_create_buf(true, true) 49 | vim.api.nvim_set_current_buf(buf) 50 | vim.api.nvim_buf_set_option(buf, 'bufhidden', 'delete') 51 | vim.api.nvim_buf_set_option(buf, 'buflisted', false) 52 | vim.api.nvim_buf_set_option(buf, 'filetype', LiFE.filetype) 53 | 54 | LiFE.entries[buf] = {} 55 | LiFE.path[buf] = path 56 | 57 | LiFE.render_header(buf) 58 | LiFE.scan(buf, path, LiFE.entries[buf], 1, 0) 59 | 60 | vim.api.nvim_buf_set_keymap(buf, 'n', '', ':lua LiFE.click()', {['noremap']= true, ['silent']= true}) 61 | vim.api.nvim_buf_set_keymap(buf, 'n', 'R', ':lua LiFE.render(vim.api.nvim_get_current_buf())', {['noremap']= true, ['silent']= true}) 62 | --vim.api.nvim_buf_set_option(buf, 'ro', true) 63 | end, 64 | 65 | render_header = function(buf) 66 | local offset = LiFE.get_list_lines_offset(buf) 67 | vim.api.nvim_buf_set_lines(buf, 0, offset, false, LiFE.get_header(buf)) 68 | vim.api.nvim_buf_add_highlight(buf, -1, "Directory", offset - 1, 0, 2) 69 | end, 70 | 71 | highlight = function(position, entry) 72 | if entry.type == 'directory' then 73 | local line_end = string.len(vim.api.nvim_buf_get_lines(buf, position, position + 1, false)[1]) 74 | vim.api.nvim_buf_add_highlight(buf, -1, "Directory", position, line_end - string.len(entry.name) - 1, line_end - 1) 75 | end 76 | end, 77 | 78 | insert_line = function(buf, entries, position, entry) 79 | table.insert(entries, position, entry) 80 | local line = entry.name 81 | local prefix = '' 82 | for i=1,entry.level - 1,1 do 83 | prefix = prefix .. LiFE.trunk_line 84 | end 85 | if entry.level ~= 0 then prefix = prefix .. LiFE.branch_line end 86 | line = prefix .. line 87 | if entry.type == 'directory' then 88 | line = line .. '/' 89 | end 90 | local offset_position = LiFE.get_list_lines_offset(buf) + position - 1 91 | vim.api.nvim_buf_set_lines(buf, offset_position, offset_position, false, {line}) 92 | LiFE.highlight(offset_position, entry) 93 | end, 94 | 95 | remove_line = function(buf, entries, position) 96 | table.remove(entries, position) 97 | local offset_position = LiFE.get_list_lines_offset(buf) + position - 1 98 | vim.api.nvim_buf_set_lines(buf, offset_position, offset_position + 1, false, {}) 99 | end, 100 | 101 | level_up_line = function(buf, position, entry) 102 | entry.level = entry.level + 1 103 | local offset_position = LiFE.get_list_lines_offset(buf) + position - 1 104 | local line = vim.api.nvim_buf_get_lines(buf, offset_position, offset_position + 1, false)[1] 105 | if entry.level == 1 then 106 | line = LiFE.branch_line .. line 107 | else 108 | line = LiFE.trunk_line .. line 109 | end 110 | vim.api.nvim_buf_set_lines(buf, offset_position, offset_position + 1, false, {line}) 111 | LiFE.highlight(offset_position, entry) 112 | end, 113 | 114 | 115 | scan = function(buf, path, entries, position, level) 116 | local count = 0 117 | local dir_count = 0 118 | local dir = vim.loop.fs_opendir(path) 119 | while true do 120 | local raw_entry = vim.loop.fs_readdir(dir) 121 | if raw_entry == nil then 122 | break 123 | else 124 | local entry = raw_entry[1] 125 | entry.path = path..entry.name 126 | entry.level = level 127 | local curr_position = position + count 128 | if entry.type == 'directory' then 129 | entry.path = entry.path .. '/' 130 | curr_position = position + dir_count 131 | dir_count = dir_count + 1 132 | end 133 | LiFE.insert_line(buf, entries, curr_position, entry) 134 | count = count + 1 135 | end 136 | end 137 | vim.loop.fs_closedir(dir) 138 | return count 139 | end, 140 | 141 | expand = function(buf, entries, line) 142 | local root_entry = entries[line] 143 | local count = LiFE.scan(buf, root_entry.path, entries, line + 1, root_entry.level + 1) 144 | root_entry.expanded = true; 145 | end, 146 | 147 | collapse = function(buf, entries, line) 148 | local root_entry = entries[line] 149 | local root_level = root_entry.level 150 | local curr_line = line + 1 151 | while true do 152 | if entries[curr_line] ~= nil and entries[curr_line].level > root_level then 153 | LiFE.remove_line(buf, entries, curr_line) 154 | else break end 155 | end 156 | root_entry.expanded = false 157 | end, 158 | 159 | above = function(buf, old_path, entries) 160 | local sub_path_index = string.find(old_path, "/[^/]*/$") 161 | local path = string.sub(old_path, 0, sub_path_index) 162 | local old_name = string.sub(old_path, sub_path_index + 1, string.len(old_path) - 1) 163 | local dir = vim.loop.fs_opendir(path) 164 | if dir == nil then return end 165 | local old_count = 1 166 | while true do 167 | local old_entry = entries[old_count] 168 | if old_entry == nil then break end 169 | local curr_old_position = old_count 170 | LiFE.level_up_line(buf, curr_old_position, old_entry) 171 | old_count = old_count + 1 172 | end 173 | local count = 1 174 | local dir_count = 1 175 | while true do 176 | local raw_entry = vim.loop.fs_readdir(dir) 177 | if raw_entry == nil then 178 | break 179 | else 180 | local entry = raw_entry[1] 181 | entry.path = path..entry.name 182 | entry.level = 0 183 | if entry.type == "directory" then 184 | entry.path = entry.path .. '/' 185 | 186 | LiFE.insert_line(buf, entries, dir_count, entry) 187 | 188 | if entry.name == old_name then 189 | entry.expanded = true 190 | --count = count + old_count - 1 191 | dir_count = dir_count + old_count - 1 192 | end 193 | dir_count = dir_count + 1 194 | else 195 | LiFE.insert_line(buf, entries, count + old_count - 1, entry) 196 | end 197 | count = count + 1 198 | end 199 | end 200 | vim.loop.fs_closedir(dir) 201 | LiFE.path[buf] = path 202 | LiFE.render_header(buf) 203 | end, 204 | 205 | click = function() 206 | local cursor = vim.api.nvim_win_get_cursor(0) 207 | local line = vim.api.nvim_win_get_cursor(0)[1] - LiFE.get_list_lines_offset() 208 | local buf = vim.api.nvim_get_current_buf() 209 | local line_entry = LiFE.entries[buf][line] 210 | if line == 0 then 211 | LiFE.above(buf, LiFE.path[buf], LiFE.entries[buf]) 212 | return 213 | end 214 | if line_entry.type == "directory" then 215 | if line_entry.expanded then 216 | LiFE.collapse(buf, LiFE.entries[buf], line) 217 | else 218 | LiFE.expand(buf, LiFE.entries[buf], line) 219 | end 220 | vim.api.nvim_win_set_cursor(0, cursor) 221 | else 222 | LiFE.click_on_file(line_entry) 223 | end 224 | end, 225 | 226 | click_on_file = function(entry) 227 | vim.api.nvim_command('edit ' .. entry.path) 228 | end, 229 | 230 | } 231 | -------------------------------------------------------------------------------- /plugin/LightningFileExplorer.vim: -------------------------------------------------------------------------------- 1 | lua require('LightningFileExplorer.lua') 2 | 3 | augroup LightningFileExplorer 4 | au! 5 | au BufDelete * lua LiFE.clean() 6 | augroup END 7 | 8 | command! -nargs=* LiFE lua LiFE.open('') 9 | --------------------------------------------------------------------------------