├── README.md
├── UNLICENSE
├── filetree.kak
└── filetree.perl
/README.md:
--------------------------------------------------------------------------------
1 | # kakoune-filetree
2 |
3 | [Kakoune](http://kakoune.org) plugin to view and navigate files, using `tree`.
4 |
5 | [](https://asciinema.org/a/568907)
6 |
7 | ## Setup
8 |
9 | Add `filetree.kak` and `filetree.perl` to your `autoload` directory: `~/.config/kak/autoload/`, or source the kakoune file manually.
10 | The two files must be in the same directory for the plugin to function.
11 |
12 | The plugin has a dependency on `tree` as well as `perl`.
13 |
14 | ## Basic usage
15 |
16 | Simply call `:filetree`. A new buffer will be open with all files found below the specified directory (Kakoune's current directory by default), presented in a tree-like structure.
17 |
18 | From the `*filetree*` buffer, you can open files by pressing Return (calling the command `filetree-open-selected-files`).
19 |
20 | The files that are open in buffers are highlighted with a special face.
21 |
22 | ## Navigation
23 |
24 | The `filetree-edit` command can be used for opening files, using the fuzzy matching engine of Kakoune. The completions are generated using the content of the `*filetree*` buffer: any file in the tree is proposed as a possible completion. This command can be used from any context, as long as the `*filetree*` buffer exists.
25 |
26 | The `filetree-goto` command can be used for selecting entries in the filetree, using the same fuzzy completion as with `filetree-edit`.
27 |
28 | The following commands can be used to navigate the files and directories depending on their relationship:
29 | * `filetree-select-prev-sibling`: select next entry in the same directory as the selected entry (``)
30 | * `filetree-select-next-sibling`: select previous entry in the same directory as the selected entry (``)
31 | * `filetree-select-parent-directory`: select parent directory of the selected entry (``)
32 | * `filetree-select-first-child`: select first entry in the selected directory (``)
33 | * `filetree-select-direct-children`: select all entries directly in the selected directory
34 | * `filetree-select-all-children`: select all entries in all subdirectories of the selected directory
35 |
36 | The `filetree-select-open-files` command can be used to select all files currently opened in buffers.
37 |
38 | ## Manipulation
39 |
40 | The `*filetree*` buffer can be modified by hand as long as the general tree-structure is preserved (for example, certain irrelevant files or directories can be filtered out by deleting the corresponding lines).
41 |
42 | The `*filetree*` buffer supports limited manipulation of the filesystem with the commands `filetree-create-child` and `filetree-create-sibling`.
43 | Note that the two commands do not create the files themselves, they only adjust the buffer to keep the tree structure valid. The desired filename can then be typed out, and then opened with `filetree-open-selected-files -create`.
44 |
45 | It is currently not possible (nor planned) to rename, move or delete files using this plugin.
46 |
47 | ## Customization
48 |
49 | The `filetree` command offers various switches to control which files are shown in the tree and in which order.
50 | * `-files-first`: for each level, show files before directories
51 | * `-dirs-first`: for each level, show directories before files
52 | * `-consider-gitignore`: do not show any entries matched by gitignore rules
53 | * `-no-empty-dirs`: do not show empty directories
54 | * `-show-hidden`: show hidden files and directories
55 | * `-depth `: only traverse the root directory up to directories deep (unlimited by default)
56 | * `-only-dirs`: only show directories, not files
57 |
58 | In addition, the following options and faces can be changed to affect the style of the tree:
59 | * option `filetree_indentation_level`: number of padding characters for each depth level (>=0, default 3)
60 | * face `FileTreePipes`: used for the indentation lines of the tree (`rgb:606060,default`)
61 | * face `FileTreeDirName`: used for directories (`blue,default+b`)
62 | * face `FileTreeFileName`: used for files (`default,default`)
63 | * face `FileTreeOpenFiles`: used for files that have an open buffer (`black,yellow`)
64 | * face `FileTreeEmptyName`: used for highlighting empty (and therefore invalid) entries, such as when creating them (`default,red`)
65 |
66 | ## License
67 |
68 | [Unlicense](http://unlicense.org)
69 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/filetree.kak:
--------------------------------------------------------------------------------
1 | declare-option -hidden str filetree_script_path %val{source}
2 |
3 | provide-module filetree %{
4 |
5 | declare-option -docstring "Name of the client in which all source code jumps will be executed" str jumpclient
6 | declare-option -docstring "name of the client in which utilities display information" str toolsclient
7 |
8 | declare-option -hidden bool filetree_highlight_dirty
9 | declare-option -hidden str filetree_root_directory
10 | declare-option -hidden range-specs filetree_open_files
11 |
12 | declare-option int filetree_indentation_level 3
13 |
14 | face global FileTreeOpenFiles black,yellow
15 | face global FileTreePipesColor rgb:606060,default
16 | face global FileTreeDirColor blue,default+b
17 | face global FileTreeFileName default,default
18 | face global FileTreeEmptyName black,red
19 |
20 | define-command filetree-switch-or-start -params .. -docstring '
21 | filetree-switch-or-start: switch to the *filetree* buffer.
22 | If the buffer does not exist, or the current kakoune directory has changed, it is generated from
23 | scratch. In this case, all arguments are forwarded to the ''filetree'' command.
24 | ' -shell-script-candidates %{
25 | printf '%s\n' -files-first -dirs-first -consider-gitignore -no-empty-dirs -show-hidden -depth './' */
26 | } %{
27 | try %{
28 | eval -try-client %opt{toolsclient} %{
29 | buffer *filetree*
30 | eval %sh{ [ "$kak_opt_filetree_root_directory" != "$PWD" ] && printf 'fail' }
31 | }
32 | } catch %{
33 | filetree %arg{@}
34 | }
35 | }
36 |
37 | define-command filetree -params .. -docstring '
38 | filetree [] []: open an interactive buffer representing the current directory
39 | Switches:
40 | -files-first: for each level, show files before directories
41 | -dirs-first: for each level, show directories before files
42 | -consider-gitignore: do not show any entries matched by gitignore rules
43 | -no-empty-dirs: do not show empty directories
44 | -show-hidden: show hidden files and directories
45 | -depth : only traverse the root directory up to directories deep (unlimited by default)
46 | -only-dirs: only show directories, not files
47 | -no-report: do not show footer after the tree
48 | ' -shell-script-candidates %{
49 | printf '%s\n' -files-first -dirs-first -consider-gitignore -no-empty-dirs \
50 | -show-hidden -depth -only-dirs -no-report './' */
51 | } %{
52 | eval -save-regs 't' %{
53 | eval %sh{
54 | sorting=''
55 | prune=''
56 | gitignore=''
57 | depth=''
58 | directory=''
59 | hidden=''
60 | only_dirs=''
61 | no_report=''
62 |
63 | arg_num=0
64 | accept_switch='y'
65 | while [ $# -ne 0 ]; do
66 | arg_num=$((arg_num + 1))
67 | arg=$1
68 | shift
69 | if [ $accept_switch = 'y' ]; then
70 | got_switch='y'
71 | if [ "$arg" = '-files-first' ]; then
72 | sorting='--filesfirst'
73 | elif [ "$arg" = '-dirs-first' ]; then
74 | sorting='--dirsfirst'
75 | elif [ "$arg" = '-no-empty-dirs' ]; then
76 | prune='--prune'
77 | elif [ "$arg" = '-show-hidden' ]; then
78 | hidden='-a'
79 | elif [ "$arg" = '-consider-gitignore' ]; then
80 | gitignore='--gitignore'
81 | elif [ "$arg" = '-only-dirs' ]; then
82 | only_dirs="-P '*/'"
83 | elif [ "$arg" = '-no-report' ]; then
84 | no_report="--noreport"
85 | elif [ "$arg" = '-depth' ]; then
86 | if [ $# -eq 0 ]; then
87 | echo 'fail "Missing argument to -depth"'
88 | exit 1
89 | fi
90 | arg_num=$((arg_num + 1))
91 | depth="-L $1"
92 | shift
93 | elif [ "$arg" = '--' ]; then
94 | accept_switch='n'
95 | else
96 | got_switch='n'
97 | fi
98 | [ $got_switch = 'y' ] && continue
99 | fi
100 | if [ "$directory" != '' ]; then
101 | printf "fail \"Unknown argument '%%arg{%s}'\"" "$arg_num"
102 | exit 1
103 | elif [ "$arg" != '' ]; then
104 | directory="$arg"
105 | else
106 | printf "fail \"Invalid directory '%%arg{%s}'\"" "$arg_num"
107 | exit 1
108 | fi
109 | done
110 | [ "$directory" = '' ] && directory='.'
111 | # strip trailing '/'
112 | while [ "$directory" != "${directory%/}" ]; do
113 | directory=${directory%/}
114 | done
115 | if [ ! -d "$directory" ]; then
116 | printf "fail \"Directory '%%arg{%s}' does not exist\"" "$arg_num"
117 | exit 1
118 | fi
119 | fifo=$(mktemp -u)
120 | mkfifo "$fifo"
121 | # $kak_opt_filetree_indentation_level <- need to let the script access this var
122 | perl_script="${kak_opt_filetree_script_path%/*}/filetree.perl"
123 | (tree -p -v $only_dirs $no_report $hidden $sorting $prune $gitignore $depth "$directory" | perl "$perl_script" 'process' > "$fifo") < /dev/null > /dev/null 2>&1 &
124 | printf "set-register t '%s'" "$fifo"
125 | }
126 | try %{ delete-buffer *filetree* }
127 | set-option global filetree_highlight_dirty false
128 |
129 | edit -fifo %reg{t} *filetree*
130 | set-option buffer filetree_root_directory %sh{ printf '%s' "$PWD" }
131 | # put hook in double quotes to interpolate %reg{t}
132 | hook -always -once buffer BufCloseFifo .* "
133 | nop %%sh{ rm '%reg{t}' }
134 | exec ged
135 | set-option global filetree_highlight_dirty true
136 | filetree-refresh-files-highlight
137 | "
138 |
139 | # highlight tree part
140 | add-highlighter buffer/ regex '^[│ ]*[├└]─* ' 0:FileTreePipesColor
141 | # highlight directories (using the /)
142 | add-highlighter buffer/ regex '^(?:[│ ]*[├└]─* )?([^\n]*?)/$' 1:FileTreeDirColor
143 | add-highlighter buffer/ regex '^(?:[│ ]*[├└]─* )([^\n/]*?)$' 1:FileTreeFileName
144 | add-highlighter buffer/ regex '^(?:[│ ]*[├└]─* )(\n)' 1:FileTreeEmptyName
145 | add-highlighter buffer/ ranges filetree_open_files
146 |
147 | map buffer normal ': filetree-open-selected-files'
148 | map buffer normal ': filetree-open-selected-files -create'
149 | map buffer normal ': filetree-select-prev-sibling'
150 | map buffer normal ': filetree-select-next-sibling'
151 | map buffer normal ': filetree-select-parent-directory'
152 | map buffer normal ': filetree-select-first-child'
153 | }
154 | }
155 |
156 | define-command filetree-select-open-files %{
157 | eval select -timestamp %sh{
158 | # TODO might not work with filenames containing '
159 | eval set -- "$kak_quoted_opt_filetree_open_files"
160 | printf '''%s''' "$1"
161 | shift
162 | for val do
163 | printf ' ''%s''' "${val%|*}"
164 | done
165 | }
166 | }
167 |
168 | define-command filetree-create-sibling %{
169 | # TODO handle root dir: forbid entirely?
170 | filetree-select-path-component
171 | try %{
172 | exec -draft '/\z'
173 | exec 'xyP'
174 | exec -draft 'ghh/[├└]r├'
175 | } catch %{
176 | exec 'xyp'
177 | exec -draft 'ghh/[├└]r├'
178 | }
179 | exec 'ghh/[├└]─* l'
180 | try %{ exec '\nGLd' }
181 | }
182 |
183 | define-command filetree-create-child %{
184 | # TODO handle root dir
185 | filetree-select-path-component
186 | # fail if we're not on a directory
187 | try %{
188 | exec -draft '/\z'
189 | } catch %{
190 | fail 'Cannot create child of a regular file'
191 | }
192 | exec 'xyp' # copy line below
193 | exec 'ghh/[├└]─* ' # select end of pipe from the new line
194 | try %{ exec -draft 'l\nGLd' } # remove filename
195 | exec 'yP' # copy-prepend it
196 | exec 'r;k' # replace it with space, and select the first character
197 | try %{
198 | exec '├jr│' # select the one just above: if it's ├, turn into │
199 | } catch %{
200 | exec 'r└j' # otherwise into └
201 | }
202 | exec '/[├└]' # select the real pipe connector
203 | try %{
204 | exec -draft 'j[├└]' # and depending on whether it's the last child
205 | exec 'r├' # replace with ├
206 | } catch %{
207 | exec 'r└' # or └
208 | }
209 | exec 'gll'
210 | }
211 |
212 | define-command filetree-select-parent-directory -docstring '
213 | filetree-select-next-sibling: in the *filetree* buffer, select the parent directory of the current element
214 | ' %{
215 | eval -itersel %{
216 | try %{
217 | exec -draft ';ghH\n.'
218 | } catch %{
219 | fail 'Already at top parent'
220 | }
221 | try %{
222 | exec ';x1s(^[│ ]+)[└├]─* '
223 | exec "^(?!%val{selection})([^\n]*?)/$"
224 | filetree-select-path-component
225 | } catch %{
226 | exec ggxH
227 | }
228 | }
229 | }
230 |
231 | define-command filetree-select-next-sibling -docstring '
232 | filetree-select-next-sibling: in the *filetree* buffer, select the next element in the same directory
233 | ' %{
234 | eval -itersel -save-regs '/' %{
235 | exec ';x'
236 | exec '1s^([ │]*)[└├]'
237 | try %{
238 | exec '\A[└├]\z'
239 | reg slash "\n(([^\n]*\n)*?(^[└├]))?"
240 | } catch %{
241 | reg slash "\n((^%val{selection}[^\n]*\n)*?(^%val{selection}[└├]))?"
242 | }
243 | exec 'gh/'
244 | exec '\A\n\z' # if we didn't match anything, fail
245 | exec ';'
246 | filetree-select-path-component
247 | }
248 | }
249 | define-command filetree-select-prev-sibling -docstring '
250 | filetree-select-prev-sibling: in the *filetree* buffer, select the previous element in the same directory
251 | ' %{
252 | eval -itersel -save-regs '/' %{
253 | exec ';x'
254 | exec '1s^([ │]*)[└├]'
255 | try %{
256 | exec '\A[└├]\z'
257 | reg slash "((^├[^\n]*\n)(^[^\n]*\n)*?)?^."
258 | } catch %{
259 | reg slash "((^%val{selection}├[^\n]*\n)(^%val{selection}[^\n]*\n)*?)?^."
260 | }
261 | exec 'gl'
262 | exec '\A^.\z' # if we didn't match anything, fail
263 | exec ';'
264 | filetree-select-path-component
265 | }
266 | }
267 |
268 | define-command filetree-select-first-child -docstring '
269 | filetree-select-first-child: in the *filetree* buffer, select the first element in the selected directory
270 | ' %{
271 | eval -itersel %{
272 | exec ';x'
273 | exec -draft '/$'
274 | try %{
275 | exec -draft 'ghH\A.\z'
276 | exec 'j'
277 | } catch %{
278 | exec 's^[ │]*[└├]─* '
279 | exec "jx\A^[│ ]{%val{selection_length}}"
280 | }
281 | filetree-select-path-component
282 | }
283 | }
284 |
285 | define-command filetree-select-direct-children -docstring '
286 | filetree-select-direct-children: in the *filetree* buffer, select all elements in the selected directory
287 | ' %{
288 | eval -itersel -save-regs 'l' %{
289 | exec ';x'
290 | exec -draft '/$'
291 | try %{
292 | exec -draft 'ghH\A.\z'
293 | reg l '0'
294 | exec gh
295 | } catch %{
296 | exec 's^[ │]*[└├]─* '
297 | reg l %val{selection_length}
298 | }
299 | exec "gll/(^[│ ]{%reg{l}}[^\n]+\n)*|."
300 | exec '\A.\z'
301 | exec "^[│ ]{%reg{l}}[└├]"
302 | filetree-select-path-component
303 | }
304 | }
305 |
306 | define-command filetree-select-all-children -docstring '
307 | filetree-select-all-children: in the *filetree* buffer, select all elements which are descendents of the selected directory
308 | ' %{
309 | eval -itersel -save-regs 'l' %{
310 | exec ';x'
311 | exec -draft '/$'
312 | try %{
313 | exec -draft 'ghH\A.\z'
314 | reg l '0'
315 | exec gh
316 | } catch %{
317 | exec 's^[ │]*[└├]─* '
318 | reg l %val{selection_length}
319 | }
320 | exec "gll/(^[│ ]{%reg{l}}[^\n]*\n)*|."
321 | exec '\A.\z'
322 | exec ''
323 | filetree-select-path-component
324 | }
325 | }
326 |
327 | define-command -hidden filetree-eval-on-fullpath -params 1 %{
328 | eval -save-regs 'p' %{
329 | eval -draft %{
330 | exec ','
331 | reg p %val{selection}
332 | try %{
333 | # TODO not exactly elegant
334 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
335 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
336 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
337 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
338 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
339 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
340 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
341 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
342 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
343 | filetree-select-parent-directory; reg p "%val{selection}%reg{p}"
344 | }
345 | }
346 | eval %arg{1}
347 | }
348 | }
349 |
350 | define-command -hidden filetree-open-selected-file -params ..1 %{
351 | filetree-eval-on-fullpath %sh{
352 | printf '%s' "try %{
353 | buffer %reg{p}
354 | } catch %{
355 | edit -existing %reg{p}
356 | reg e \"x%reg{e}\"
357 | } catch %{"
358 | if [ "$1" = '-create' ]; then
359 | printf '%s' "
360 | edit %reg{p}
361 | reg c \"x%reg{c}\"
362 | } catch %{"
363 | fi
364 | printf '%s' "reg f \"x%reg{f}\"
365 | }"
366 | }
367 | }
368 |
369 | define-command filetree-open-selected-files -params ..1 -docstring '
370 | filetree-open-selected-files [-create]: open the files currently selected
371 | ' -shell-script-candidates %{
372 | printf "%s\n" '-create'
373 | } %{
374 | eval -save-regs 'cef' %{
375 | reg e ''
376 | reg c ''
377 | reg f ''
378 | filetree-select-path-component
379 | exec '/\z'
380 | try %{
381 | # open all non-main selections in a draft context
382 | eval -draft %{
383 | exec ''
384 | eval -itersel %{ eval -draft -verbatim filetree-open-selected-file %arg{@} }
385 | }
386 | }
387 | filetree-open-selected-file %arg{@}
388 | eval %sh{
389 | # echo some "helpful" info
390 | num_opened="${#kak_reg_e}"
391 | num_created="${#kak_reg_c}"
392 | num_failed="${#kak_reg_f}"
393 | total=$(( num_opened + num_created + num_failed ))
394 | [ "$total" -eq 0 ] && exit
395 |
396 | str_opened="${num_opened} existing file"
397 | [ "$num_opened" -ne 1 ] && str_opened="${str_opened}s"
398 | str_created="${num_created} new file"
399 | [ "$num_created" -ne 1 ] && str_created="${str_created}s"
400 | str_failed="${num_failed} file"
401 | [ "$num_failed" -ne 1 ] && str_failed="${str_failed}s"
402 |
403 | printf "echo '"
404 | if [ "$num_opened" -eq "$total" ]; then
405 | printf "Opened %s" "$str_opened"
406 | elif [ "$num_created" -eq "$total" ]; then
407 | printf "Opened %s" "$str_created"
408 | elif [ "$num_failed" -eq "$total" ]; then
409 | printf "Failed to open %s" "$str_failed"
410 | elif [ "$num_failed" -eq 0 ]; then # opened + created
411 | printf "Opened %s, and %s" "$str_opened" "$str_created"
412 | elif [ "$num_created" -eq 0 ]; then # opened + failed
413 | printf "Opened %s, and failed to open %s" "$str_opened" "$str_failed"
414 | elif [ "$num_opened" -eq 0 ]; then # created + failed
415 | printf "Opened %s, and failed to open %s" "$str_created" "$str_failed"
416 | else
417 | printf "Opened %s, %s, and failed to open %s" "$str_opened" "$str_created" "$str_failed"
418 | fi
419 | printf "'"
420 | }
421 | }
422 | }
423 |
424 | define-command filetree-select-path-component %{
425 | exec ';x1s^[│ ]*[├└]─* (.*)\n'
426 | }
427 |
428 | hook global WinDisplay '^\*filetree\*$' %{
429 | try %{
430 | eval %sh{ [ "$kak_opt_filetree_highlight_dirty" = 'false' ] && printf 'fail' }
431 | filetree-refresh-files-highlight
432 | set global filetree_highlight_dirty false
433 | }
434 | }
435 |
436 | hook global BufCreate .* %{ set global filetree_highlight_dirty true }
437 | hook global BufClose .* %{ set global filetree_highlight_dirty true }
438 |
439 | define-command -hidden filetree-refresh-files-highlight %{
440 | try %{
441 | eval -draft -buffer *filetree* %{
442 | eval select %sh{
443 | script="${kak_opt_filetree_script_path%/*}/filetree.perl"
444 | echo "write '$kak_response_fifo'" > "$kak_command_fifo"
445 | eval set -- "$kak_quoted_buflist"
446 | perl "$script" 'match-buffers' "$@" < "$kak_response_fifo"
447 | }
448 | filetree-select-path-component
449 |
450 | set-option buffer filetree_open_files %val{timestamp}
451 | eval -no-hooks -draft -itersel %{
452 | set -add buffer filetree_open_files "%val{selection_desc}|FileTreeOpenFiles"
453 | }
454 | }
455 | }
456 | }
457 |
458 | define-command filetree-edit -params 1.. -docstring '
459 | filetree-edit: edit the specified files.
460 | The completions are provided by the *filetree* buffer.
461 | ' %{
462 | edit %arg{@}
463 | }
464 |
465 | complete-command -menu filetree-edit shell-script-candidates %{
466 | fifo=$(mktemp -u)
467 | mkfifo "$fifo"
468 | echo "try %{ eval -buffer *filetree* %{ write '$fifo' } } catch %{ echo -to-file '$fifo' '' }" | kak -p "$kak_session"
469 | perl "${kak_opt_filetree_script_path%/*}/filetree.perl" 'flatten-nodirs' < "$fifo"
470 | rm "$fifo"
471 | }
472 |
473 | define-command filetree-goto -params 1.. -docstring '
474 | filetree-goto: select the specified path elements in the *filetree* buffer
475 | ' %{
476 | buffer *filetree*
477 | eval select %sh{
478 | script="${kak_opt_filetree_script_path%/*}/filetree.perl"
479 | echo "write '$kak_response_fifo'" > "$kak_command_fifo"
480 | perl "$script" 'match-buffers' "$@" < "$kak_response_fifo"
481 | }
482 | filetree-select-path-component
483 | }
484 |
485 | complete-command -menu filetree-goto shell-script-candidates %{
486 | fifo=$(mktemp -u)
487 | mkfifo "$fifo"
488 | echo "try %{ eval -buffer *filetree* %{ write '$fifo' } } catch %{ echo -to-file '$fifo' '' }" | kak -p "$kak_session"
489 | perl "${kak_opt_filetree_script_path%/*}/filetree.perl" 'flatten-all' < "$fifo"
490 | rm "$fifo"
491 | }
492 |
493 | }
494 |
495 | require-module filetree
496 |
--------------------------------------------------------------------------------
/filetree.perl:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 |
4 | my $operation = $ARGV[0];
5 | if (not defined($operation)) {
6 | exit(3);
7 | }
8 | shift;
9 |
10 | sub read_line_by_line {
11 | my $callback = $_[0];
12 |
13 | my $padding_size = 0;
14 | my @dir_stack;
15 | my $line_count = 0;
16 |
17 | my $prev_depth = 0;
18 | while (my $input = ) {
19 | chomp($input);
20 | if ($input eq "") {
21 | last;
22 | } elsif ($line_count == 0) {
23 | if ($input ne './') {
24 | push(@dir_stack, substr($input, 0, -1));
25 | }
26 | $line_count = 1;
27 | next;
28 | }
29 |
30 | my $depth = 1;
31 | if ($line_count == 1) {
32 | # need to infer the width of the pipes, based on the first line
33 | if ($input !~ m/\G(└|├)((─)* )/gc) {
34 | exit(1);
35 | }
36 | # the pipe character is actually length '3'
37 | $padding_size = (length($2) - 1) / 3;
38 | } else {
39 | while ($input =~ m/\G(│| ) {$padding_size} /gco) {
40 | $depth += 1;
41 | }
42 | if ($input !~ m/\G(└|├)(─)* /gc) {
43 | exit(1);
44 | }
45 | }
46 | $line_count += 1;
47 |
48 | if ($depth <= $prev_depth) {
49 | my $remove = $prev_depth - $depth + 1;
50 | splice(@dir_stack, -$remove);
51 | } elsif ($depth > $prev_depth + 1) {
52 | # does not make sense to grow by >1 level
53 | exit(1);
54 | }
55 | $input =~ m|\G(.*?)(/?)$|gc;
56 | my $component = $1;
57 | my $is_dir = ($2 eq '/');
58 | $prev_depth = $depth;
59 | push(@dir_stack, $component);
60 | $callback->(join('/', @dir_stack), $is_dir, $line_count);
61 | }
62 | }
63 |
64 | if ($operation eq "flatten-all") {
65 | sub callback1 {
66 | print("$_[0]\n");
67 | }
68 | read_line_by_line(\&callback1);
69 | } elsif ($operation eq "flatten-nodirs") {
70 | sub callback2 {
71 | if (not $_[1]) {
72 | print("$_[0]\n");
73 | }
74 | }
75 | read_line_by_line(\&callback2);
76 | } elsif ($operation eq "match-buffers") {
77 | my %map;
78 | for my $buf (@ARGV) {
79 | $map{$buf} = 1;
80 | }
81 | sub callback3 {
82 | my $path = $_[0];
83 | if (exists($map{$path})) {
84 | my $line = $_[2];
85 | print("'$line.1,$line.1' ");
86 | delete $map{$path};
87 | }
88 | }
89 | read_line_by_line(\&callback3);
90 | } elsif ($operation eq "process") {
91 |
92 | my $repetition = int($ENV{"kak_opt_filetree_indentation_level"} or 3);
93 | my $first = 1;
94 |
95 | while (my $input = ) {
96 | chomp($input);
97 | if ($input eq "") {
98 | print("\n");
99 | last;
100 | }
101 | my $out = "";
102 | if ($first == 1) {
103 | $first = 0;
104 | } else {
105 | while ($input =~ m/\G(?:(│)\xc2\xa0\xc2\xa0|( ) ) /gc) {
106 | $out .= ($1 or $2) . ' ' x ($repetition + 1);
107 | }
108 | if ($input !~ m/\G(└|├)(─)─ /gc) {
109 | exit(1);
110 | }
111 | $out .= $1 . $2 x $repetition . ' ';
112 | }
113 | my $type = '';
114 | if ($input =~ m/\G\[([-sdl]).{9}\] /gc) {
115 | $type = $1;
116 | }
117 | if ($type eq 'l') {
118 | $input =~ m/\G(.*) -> .*?$/gc;
119 | $out .= $1;
120 | } else {
121 | $input =~ m/\G(.*)$/gc;
122 | $out .= $1;
123 | }
124 | if ($type eq 'd') {
125 | $out .= '/';
126 | }
127 | print("$out\n");
128 | }
129 |
130 | my $last = ;
131 | print("$last");
132 |
133 | } else {
134 | exit(2);
135 | }
136 |
--------------------------------------------------------------------------------