├── .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 |
--------------------------------------------------------------------------------