-
Notifications
You must be signed in to change notification settings - Fork 193
/
files.lua
2642 lines (2239 loc) · 94 KB
/
files.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--- *mini.files* Navigate and manipulate file system
--- *MiniFiles*
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Features:
--- - Navigate file system using column view (Miller columns) to display nested
--- directories. See |MiniFiles-navigation| for overview.
---
--- - Opt-in preview of file or directory under cursor.
---
--- - Manipulate files and directories by editing text buffers: create, delete,
--- copy, rename, move. See |MiniFiles-manipulation| for overview.
---
--- - Use as default file explorer instead of |netrw|.
---
--- - Configurable:
--- - Filter/prefix/sort of file system entries.
--- - Mappings used for common explorer actions.
--- - UI options: whether to show preview of file/directory under cursor, etc.
---
--- What it doesn't do:
--- - Try to be replacement of system file explorer. It is mostly designed to
--- be used within Neovim to quickly explore file system structure, open
--- files, and perform some quick file system edits.
---
--- - Work on remote locations. Only local file system is supported.
---
--- - Provide built-in interactive toggle of content `filter` and `sort`.
--- See |MiniFiles-examples| for some common examples.
---
--- - Provide out of the box extra information like git or diagnostic status.
--- This can be achieved by setting |extmarks| on appropriate event(s)
--- (see |MiniFiles-events|)
---
--- Notes:
--- - This module is written and thoroughly tested on Linux. Support for other
--- platform/OS (like Windows or MacOS) is a goal, but there is no guarantee.
---
--- - Works on all supported versions but using Neovim>=0.9 is recommended.
---
--- - This module silently reacts to not enough permissions:
--- - In case of missing file, check its or its parent read permissions.
--- - In case of no manipulation result, check write permissions.
---
--- # Dependencies ~
---
--- Suggested dependencies (provide extra functionality, will work without them):
---
--- - Enabled |MiniIcons| module to show icons near file/directory names.
--- Falls back to 'nvim-tree/nvim-web-devicons' plugin or uses default icons.
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.files').setup({})` (replace
--- `{}` with your `config` table). It will create global Lua table `MiniFiles`
--- which you can use for scripting or manually (with `:lua MiniFiles.*`).
---
--- See |MiniFiles.config| for available config settings.
---
--- You can override runtime config settings (like mappings or window options)
--- locally to buffer inside `vim.b.minifiles_config` which should have same
--- structure as `MiniFiles.config`. See |mini.nvim-buffer-local-config| for
--- more details.
---
--- # Comparisons ~
---
--- - 'nvim-tree/nvim-tree.lua':
--- - Provides tree view of file system, while this module uses column view.
--- - File system manipulation is done with custom set of mappings for each
--- action, while this module is designed to do that by editing text.
--- - Has more out of the box functionality with extra configuration, while
--- this module has not (by design).
---
--- - 'stevearc/oil.nvim':
--- - Uses single window to show information only about currently explored
--- directory, while this module uses column view to show whole currently
--- explored branch.
--- - Also uses text editing to manipulate file system entries.
--- - Can work for remote file systems, while this module can not (by design).
---
--- - 'nvim-neo-tree/neo-tree.nvim':
--- - Compares to this module mostly the same as 'nvim-tree/nvim-tree.lua'.
---
--- # Highlight groups ~
---
--- * `MiniFilesBorder` - border of regular windows.
--- * `MiniFilesBorderModified` - border of windows showing modified buffer.
--- * `MiniFilesCursorLine` - cursor line in explorer windows.
--- * `MiniFilesDirectory` - text and icon representing directory.
--- * `MiniFilesFile` - text representing file.
--- * `MiniFilesNormal` - basic foreground/background highlighting.
--- * `MiniFilesTitle` - title of regular windows.
--- * `MiniFilesTitleFocused` - title of focused window.
---
--- To change any highlight group, modify it directly with |:highlight|.
---
--- # Disabling ~
---
--- This plugin provides only manually started functionality, so no disabling
--- is available.
--- Navigation ~
---
--- Every navigation starts by calling |MiniFiles.open()|, either directly or via
--- mapping (see its help for examples of some common scenarios). It will show
--- an explorer consisting of side-by-side floating windows with the following
--- principles:
---
--- - Explorer shows one branch of nested directories at a time.
---
--- - Explorer consists from several windows:
---
--- - Each window displays entries of a single directory in a modifiable
--- scratch buffer.
---
--- - Windows are organized left to right: for any particular window the left
--- neighbor is its parent directory and right neighbor - its child.
---
--- - Explorer windows are the viewport to some part of current branch, meaning
--- that their opening/closing does not affect the branch. This matters, for
--- example, if there are more elements in the branch than can be shown windows.
---
--- - Every buffer line represents separate file system entry following certain
--- format (not visible for users by default; set |conceallevel| to 0 to see it)
---
--- - Once directory is shown, its buffer is not updated automatically following
--- external file system changes. Manually use |MiniFiles.synchronize()| for that.
---
--- After opening explorer, in-buffer navigation is done the same way as any
--- regular buffer, except without some keys reserved for built-in actions.
---
--- Most common ways to navigate are:
---
--- - Press `j` to move cursor onto next (lower) entry in current directory.
--- - Press `k` to move cursor onto previous (higher) entry in current directory.
--- - Press `l` to expand entry under cursor (see "Go in" action).
--- - Press `h` to focus on parent directory (see "Go out" action).
---
--- Cursor positions in each directory buffer are tracked and saved during
--- navigation. This allows for more convenient repeated navigation to some
--- previously visited branch.
---
--- Available built-in actions (see "Details" for more information): >
---
--- | Action | Keys | Description |
--- |-------------|------|------------------------------------------------|
--- | Close | q | Close explorer |
--- |-------------|------|------------------------------------------------|
--- | Go in | l | Expand entry (show directory or open file) |
--- |-------------|------|------------------------------------------------|
--- | Go in plus | L | Expand entry plus extra action |
--- |-------------|------|------------------------------------------------|
--- | Go out | h | Focus on parent directory |
--- |-------------|------|------------------------------------------------|
--- | Go out plus | H | Focus on parent directory plus extra action |
--- |-------------|------|------------------------------------------------|
--- | Reset | <BS> | Reset current explorer |
--- |-------------|------|------------------------------------------------|
--- | Reveal cwd | @ | Reset current current working directory |
--- |-------------|------|------------------------------------------------|
--- | Show help | g? | Show help window |
--- |-------------|------|------------------------------------------------|
--- | Synchronize | = | Synchronize user edits and/or external changes |
--- |-------------|------|------------------------------------------------|
--- | Trim left | < | Trim left part of branch |
--- |-------------|------|------------------------------------------------|
--- | Trim right | > | Trim right part of branch |
--- |-------------|------|------------------------------------------------|
--- <
--- Details:
---
--- - "Go in":
--- - Always opens file in the latest window before `MiniFiles.open()` call.
--- - Never closes explorer.
--- - Works in linewise Visual mode to expand multiple entries.
---
--- - "Go in plus" is regular "Go in" but closes explorer after opening a file.
---
--- - "Go out plus" is regular "Go out" but trims right part of branch.
---
--- - "Reset" focuses only on "anchor" directory (the one used to open current
--- explorer) and resets all stored directory cursor positions.
---
--- - "Reveal cwd" extends branch to include |current-directory|.
--- If it is not an ancestor of the current branch, nothing is done.
---
--- - "Show help" results into new window with helpful information about current
--- explorer. Press `q` to close it.
---
--- - "Synchronize" parses user edits in directory buffers, applies them (after
--- confirmation), and updates all directory buffers with the most relevant
--- file system information. Can also be used without user edits to show up
--- to date file system entries.
--- See |MiniFiles-manipulation| for more info about file system manipulation.
---
--- - "Trim left" and "Trim right" trim parts of the whole branch, not only its
--- currently visible parts.
---
--- Notes:
---
--- - Each action has exported function with more details about it.
---
--- - Keys can be configured with `mappings` table of |MiniFiles.config|.
---@tag MiniFiles-navigation
--- Manipulation ~
---
--- File system manipulation is done by editing text inside directory buffers,
--- which are shown inside dedicated window(s). See |MiniFiles-navigation| for
--- more information about navigating to a particular directory.
---
--- General workflow:
---
--- - Navigate to the directory in which manipulation should be done.
---
--- - Edit buffer in the way representing file system action.
---
--- - Repeat previous steps until all necessary file system actions are recorded.
--- Note: even if directory buffer is hidden, its modifications are preserved,
--- so you can navigate in and out of directory with modified buffer.
---
--- - Execute |MiniFiles.synchronize()| (default key is `=`). This will prompt
--- confirmation dialog listing all file system actions it is about to perform.
--- READ IT CAREFULLY.
---
--- - Confirm by pressing `y`/`<CR>` (applies edits and updates buffers) or
--- don't confirm by pressing `n`/`<Esc>` (updates buffers without applying edits).
---
--- # How does it work ~
---
--- All manipulation functionality is powered by creating and keeping track of
--- path indexes: text of the form `/xxx` (`xxx` is the number path index) placed
--- at the start of every line representing file system entry.
---
--- By default they are hidden as concealed text (along with prefix separators)
--- for more convenience but you can see them by setting |conceallevel| to 0.
--- DO NOT modify text to the left of entry name.
---
--- During synchronization, actual text for entry name is compared to path index
--- at that line (if present) to deduce which file system action to perform.
---
--- # Supported file system actions ~
---
--- ## Create ~
---
--- - Create file by creating new line with file name (including extension).
---
--- - Create directory by creating new line with directory name followed by `/`.
---
--- - Create file or directory inside nested directories by creating new line
--- with text like 'dir/nested-dir/' or 'dir/nested-dir/file'.
--- Always use `/` on any OS.
---
--- ## Delete ~
---
--- - Delete file or directory by deleting **whole line** describing it.
---
--- - If `options.permanent_delete` is `true`, delete is permanent. Otherwise
--- file system entry is moved to a module-specific trash directory
--- (see |MiniFiles.config| for more details).
---
--- ## Rename ~
---
--- - Rename file or directory by editing its name (not icon or path index to
--- the left of it).
---
--- - With default mappings for `h` / `l` it might be not convenient to rename
--- only part of an entry. You can adopt any of the following approaches:
--- - Use different motions, like |$|, |e|, |f|, etc.
--- - Go into Insert mode and navigate inside it.
--- - Change mappings to be more suited for manipulation and not navigation.
--- See "Mappings" section in |MiniFiles.config|.
---
--- - It is not needed to end directory name with `/`.
---
--- - Cyclic renames ("a" to "b" and "b" to "a") are not supported.
---
--- ## Copy ~
---
--- - Copy file or directory by copying **whole line** describing it and pasting
--- it inside buffer of target directory.
---
--- - Change of target path is allowed. Edit only entry name in target location
--- (not icon or path index to the left of it).
---
--- - Copying inside same parent directory is supported only if target path has
--- different name.
---
--- - Copying inside child directory is supported.
---
--- ## Move ~
---
--- - Move file or directory by cutting **whole line** describing it and then
--- pasting it inside target directory.
---
--- - Change of target path is allowed. Edit only entry name in target location
--- (not icon or path index to the left of it).
---
--- - Moving directory inside itself is not supported.
---@tag MiniFiles-manipulation
--- Events ~
---
--- To allow user customization and integration of external tools, certain |User|
--- autocommand events are triggered under common circumstances.
---
--- UI events ~
---
--- - `MiniFilesExplorerOpen` - just after explorer finishes opening.
---
--- - `MiniFilesExplorerClose` - just before explorer starts closing.
---
--- - `MiniFilesBufferCreate` - when buffer is created to show a particular
--- directory. Triggered once per directory during one explorer session.
--- Can be used to create buffer-local mappings.
---
--- - `MiniFilesBufferUpdate` - when directory buffer is updated with new content.
--- Can be used for integrations to set |extmarks| with useful information.
---
--- - `MiniFilesWindowOpen` - when new window is opened. Can be used to set
--- window-local settings (like border, 'winblend', etc.)
---
--- - `MiniFilesWindowUpdate` - when a window is updated. Triggers frequently,
--- for example, for every "go in" or "go out" action.
---
--- Callback for each UI event will receive `data` field (see |nvim_create_autocmd()|)
--- with the following information:
---
--- - <buf_id> - index of target buffer.
--- - <win_id> - index of target window. Can be `nil`, like in
--- `MiniFilesBufferCreate` and buffer's first `MiniFilesBufferUpdate` as
--- they are triggered before window is created.
---
--- File action events ~
---
--- - `MiniFilesActionCreate` - after entry is successfully created.
---
--- - `MiniFilesActionDelete` - after entry is successfully deleted.
---
--- - `MiniFilesActionRename` - after entry is successfully renamed.
---
--- - `MiniFilesActionCopy` - after entry is successfully copied.
---
--- - `MiniFilesActionMove` - after entry is successfully moved.
---
--- Callback for each file action event will receive `data` field
--- (see |nvim_create_autocmd()|) with the following information:
---
--- - <action> - string with action name.
--- - <from> - absolute path of entry before action (`nil` for "create" action).
--- - <to> - absolute path of entry after action (`nil` for "delete" action).
---@tag MiniFiles-events
--- Common configuration examples ~
---
--- # Toggle explorer ~
---
--- Use a combination of |MiniFiles.open()| and |MiniFiles.close()|: >lua
---
--- local minifiles_toggle = function(...)
--- if not MiniFiles.close() then MiniFiles.open(...) end
--- end
--- <
--- # Customize windows ~
---
--- Create an autocommand for `MiniFilesWindowOpen` event: >lua
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesWindowOpen',
--- callback = function(args)
--- local win_id = args.data.win_id
---
--- -- Customize window-local settings
--- vim.wo[win_id].winblend = 50
--- local config = vim.api.nvim_win_get_config(win_id)
--- config.border, config.title_pos = 'double', 'right'
--- vim.api.nvim_win_set_config(win_id, config)
--- end,
--- })
--- <
--- # Customize icons ~
---
--- Use different directory icon: >lua
---
--- local my_prefix = function(fs_entry)
--- if fs_entry.fs_type == 'directory' then
--- -- NOTE: it is usually a good idea to use icon followed by space
--- return ' ', 'MiniFilesDirectory'
--- end
--- return MiniFiles.default_prefix(fs_entry)
--- end
---
--- require('mini.files').setup({ content = { prefix = my_prefix } })
--- <
--- Show no icons: >lua
---
--- require('mini.files').setup({ content = { prefix = function() end } })
--- <
--- # Create mapping to show/hide dot-files ~
---
--- Create an autocommand for `MiniFilesBufferCreate` event which calls
--- |MiniFiles.refresh()| with explicit `content.filter` functions: >lua
---
--- local show_dotfiles = true
---
--- local filter_show = function(fs_entry) return true end
---
--- local filter_hide = function(fs_entry)
--- return not vim.startswith(fs_entry.name, '.')
--- end
---
--- local toggle_dotfiles = function()
--- show_dotfiles = not show_dotfiles
--- local new_filter = show_dotfiles and filter_show or filter_hide
--- MiniFiles.refresh({ content = { filter = new_filter } })
--- end
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesBufferCreate',
--- callback = function(args)
--- local buf_id = args.data.buf_id
--- -- Tweak left-hand side of mapping to your liking
--- vim.keymap.set('n', 'g.', toggle_dotfiles, { buffer = buf_id })
--- end,
--- })
--- <
--- # Create mappings to modify target window via split ~
---
--- Combine |MiniFiles.get_target_window()| and |MiniFiles.set_target_window()|: >lua
---
--- local map_split = function(buf_id, lhs, direction)
--- local rhs = function()
--- -- Make new window and set it as target
--- local new_target_window
--- vim.api.nvim_win_call(MiniFiles.get_target_window(), function()
--- vim.cmd(direction .. ' split')
--- new_target_window = vim.api.nvim_get_current_win()
--- end)
---
--- MiniFiles.set_target_window(new_target_window)
--- end
---
--- -- Adding `desc` will result into `show_help` entries
--- local desc = 'Split ' .. direction
--- vim.keymap.set('n', lhs, rhs, { buffer = buf_id, desc = desc })
--- end
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesBufferCreate',
--- callback = function(args)
--- local buf_id = args.data.buf_id
--- -- Tweak keys to your liking
--- map_split(buf_id, 'gs', 'belowright horizontal')
--- map_split(buf_id, 'gv', 'belowright vertical')
--- end,
--- })
--- <
--- # Create mapping to set current working directory ~
---
--- Use |MiniFiles.get_fs_entry()| together with |vim.fs.dirname()|: >lua
---
--- local files_set_cwd = function(path)
--- -- Works only if cursor is on the valid file system entry
--- local cur_entry_path = MiniFiles.get_fs_entry().path
--- local cur_directory = vim.fs.dirname(cur_entry_path)
--- vim.fn.chdir(cur_directory)
--- end
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesBufferCreate',
--- callback = function(args)
--- vim.keymap.set('n', 'g~', files_set_cwd, { buffer = args.data.buf_id })
--- end,
--- })
--- <
---@tag MiniFiles-examples
---@diagnostic disable:luadoc-miss-type-name
---@alias __minifiles_fs_entry_data_fields - <fs_type> `(string)` - one of "file" or "directory".
--- - <name> `(string)` - basename of an entry (including extension).
--- - <path> `(string)` - full path of an entry.
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
---@diagnostic disable:cast-local-type
-- Module definition ==========================================================
local MiniFiles = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniFiles.config|.
---
---@usage >lua
--- require('mini.files').setup() -- use default config
--- -- OR
--- require('mini.files').setup({}) -- replace {} with your config table
--- <
MiniFiles.setup = function(config)
-- Export module
_G.MiniFiles = MiniFiles
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Define behavior
H.create_autocommands(config)
-- Create default highlighting
H.create_default_hl()
end
--stylua: ignore
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # Content ~
---
--- `content.filter` is a predicate which takes file system entry data as input
--- and returns `true`-ish value if it should be shown.
--- Uses |MiniFiles.default_filter()| by default.
---
--- A file system entry data is a table with the following fields:
--- __minifiles_fs_entry_data_fields
---
--- `content.prefix` describes what text (prefix) to show to the left of file
--- system entry name (if any) and how to highlight it. It also takes file
--- system entry data as input and returns tuple of text and highlight group
--- name to be used to highlight prefix. See |MiniFiles-examples| for common
--- examples of how to use it.
--- Note: due to how lines are parsed to detect user edits for file system
--- manipulation, output of `content.prefix` should not contain `/` character.
--- Uses |MiniFiles.default_prefix()| by default.
---
--- `content.sort` describes in which order directory entries should be shown
--- in directory buffer. Takes as input and returns as output an array of file
--- system entry data. Note: technically, it can be used to filter and modify
--- its elements as well.
--- Uses |MiniFiles.default_sort()| by default.
---
--- # Mappings ~
---
--- `mappings` table can be used to customize buffer-local mappings created in each
--- directory buffer for built-in actions. Entry name corresponds to the function
--- name of the action, value - right hand side of the mapping. Supply empty
--- string to not create a particular mapping.
---
--- Default mappings are mostly designed for consistent navigation experience.
--- Here are some alternatives: >lua
---
--- -- Close explorer after opening file with `l`
--- mappings = {
--- go_in = 'L',
--- go_in_plus = 'l',
--- }
---
--- -- Don't use `h`/`l` for easier cursor navigation during text edit
--- mappings = {
--- go_in = 'L',
--- go_in_plus = '',
--- go_out = 'H',
--- go_out_plus = '',
--- }
--- <
--- # Options ~
---
--- `options.use_as_default_explorer` is a boolean indicating whether this module
--- should be used as a default file explorer for editing directories (instead of
--- |netrw| by default).
---
--- `options.permanent_delete` is a boolean indicating whether to perform
--- permanent delete or move into special trash directory.
--- This is a module-specific variant of "remove to trash".
--- Target directory is 'mini.files/trash' inside standard path of Neovim data
--- directory (execute `:echo stdpath('data')` to see its path in your case).
---
--- # Windows ~
---
--- `windows.max_number` is a maximum number of windows allowed to be open
--- simultaneously. For example, use value 1 to always show single window.
--- There is no constraint by default.
---
--- `windows.preview` is a boolean indicating whether to show preview of
--- file/directory under cursor. Note: it is shown with highlighting if Neovim
--- version is sufficient and file is small enough (less than 1K bytes per line
--- or 1M bytes in total).
---
--- `windows.width_focus` and `windows.width_nofocus` are number of columns used
--- as `width` for focused and non-focused windows respectively.
MiniFiles.config = {
-- Customization of shown content
content = {
-- Predicate for which file system entries to show
filter = nil,
-- What prefix to show to the left of file system entry
prefix = nil,
-- In which order to show file system entries
sort = nil,
},
-- Module mappings created only inside explorer.
-- Use `''` (empty string) to not create one.
mappings = {
close = 'q',
go_in = 'l',
go_in_plus = 'L',
go_out = 'h',
go_out_plus = 'H',
reset = '<BS>',
reveal_cwd = '@',
show_help = 'g?',
synchronize = '=',
trim_left = '<',
trim_right = '>',
},
-- General options
options = {
-- Whether to delete permanently or move into module-specific trash
permanent_delete = true,
-- Whether to use for editing directories
use_as_default_explorer = true,
},
-- Customization of explorer windows
windows = {
-- Maximum number of windows to show side by side
max_number = math.huge,
-- Whether to show preview of file/directory under cursor
preview = false,
-- Width of focused window
width_focus = 50,
-- Width of non-focused window
width_nofocus = 15,
-- Width of preview window
width_preview = 25,
},
}
--minidoc_afterlines_end
--- Open file explorer
---
--- Common ways to use this function: >lua
---
--- -- Open current working directory in a last used state
--- MiniFiles.open()
---
--- -- Fresh explorer in current working directory
--- MiniFiles.open(nil, false)
---
--- -- Open directory of current file (in last used state) focused on the file
--- MiniFiles.open(vim.api.nvim_buf_get_name(0))
---
--- -- Fresh explorer in directory of current file
--- MiniFiles.open(vim.api.nvim_buf_get_name(0), false)
---
--- -- Open last used `path` (per tabpage)
--- -- Current working directory for the first time
--- MiniFiles.open(MiniFiles.get_latest_path())
--- <
---@param path string|nil A valid file system path used as anchor.
--- If it is a path to directory, used directly.
--- If it is a path to file, its parent directory is used as anchor while
--- explorer will focus on the supplied file.
--- Default: path of |current-directory|.
---@param use_latest boolean|nil Whether to load explorer state from history
--- (based on the supplied anchor path). Default: `true`.
---@param opts table|nil Table of options overriding |MiniFiles.config| and
--- `vim.b.minifiles_config` for this particular explorer session.
MiniFiles.open = function(path, use_latest, opts)
-- Validate path: allow only valid file system path
path = H.fs_full_path(path or vim.fn.getcwd())
local fs_type = H.fs_get_type(path)
if fs_type == nil then H.error('`path` is not a valid path ("' .. path .. '")') end
-- - Allow file path to use its parent while focusing on file
local entry_name
if fs_type == 'file' then
path, entry_name = H.fs_get_parent(path), H.fs_get_basename(path)
end
-- Validate rest of the arguments
if use_latest == nil then use_latest = true end
-- Properly close possibly opened in the tabpage explorer
local did_close = MiniFiles.close()
if did_close == false then return end
-- Get explorer to open
local explorer
if use_latest then explorer = H.explorer_path_history[path] end
explorer = explorer or H.explorer_new(path)
-- Update explorer data. Don't use current explorer's data to allow more
-- interactive config change by modifying global/local configs.
explorer.opts = H.normalize_opts(nil, opts)
explorer.target_window = vim.api.nvim_get_current_win()
-- Possibly focus on file entry
explorer = H.explorer_focus_on_entry(explorer, path, entry_name)
-- Refresh and register as opened
H.explorer_refresh(explorer)
-- Register latest used path
H.latest_paths[vim.api.nvim_get_current_tabpage()] = path
-- Track lost focus
H.explorer_track_lost_focus()
-- Trigger appropriate event
H.trigger_event('MiniFilesExplorerOpen')
end
--- Refresh explorer
---
--- Notes:
--- - If in `opts` at least one of `content` entry is not `nil`, all directory
--- buffers are forced to update.
---
---@param opts table|nil Table of options to update.
MiniFiles.refresh = function(opts)
local explorer = H.explorer_get()
if explorer == nil then return end
-- Decide whether buffers should be forcefully updated
local content_opts = (opts or {}).content or {}
local force_update = #vim.tbl_keys(content_opts) > 0
-- Confirm refresh if there is modified buffer
if force_update then force_update = H.explorer_confirm_modified(explorer, 'buffer updates') end
-- Respect explorer local options supplied inside its `open()` call but give
-- current `opts` higher precedence
explorer.opts = H.normalize_opts(explorer.opts, opts)
H.explorer_refresh(explorer, { force_update = force_update })
end
--- Synchronize explorer
---
--- - Parse user edits in directory buffers.
--- - Convert edits to file system actions and apply them after confirmation.
--- - Update all directory buffers with the most relevant file system information.
--- Can be used without user edits to account for external file system changes.
MiniFiles.synchronize = function()
local explorer = H.explorer_get()
if explorer == nil then return end
-- Parse and apply file system operations
local fs_actions = H.explorer_compute_fs_actions(explorer)
if fs_actions ~= nil and H.fs_actions_confirm(fs_actions) then H.fs_actions_apply(fs_actions, explorer.opts) end
H.explorer_refresh(explorer, { force_update = true })
end
--- Reset explorer
---
--- - Show single window focused on anchor directory (which was used as first
--- argument for |MiniFiles.open()|).
--- - Reset all tracked directory cursors to point at first entry.
MiniFiles.reset = function()
local explorer = H.explorer_get()
if explorer == nil then return end
-- Reset branch
explorer.branch = { explorer.anchor }
explorer.depth_focus = 1
-- Reset views
for _, view in pairs(explorer.views) do
view.cursor = { 1, 0 }
end
-- Skip update cursors, as they are already set
H.explorer_refresh(explorer, { skip_update_cursor = true })
end
--- Close explorer
---
---@return boolean|nil Whether closing was done or `nil` if there was nothing to close.
MiniFiles.close = function()
local explorer = H.explorer_get()
if explorer == nil then return nil end
-- Stop tracking lost focus
pcall(vim.loop.timer_stop, H.timers.focus)
-- Confirm close if there is modified buffer
if not H.explorer_confirm_modified(explorer, 'close') then return false end
-- Trigger appropriate event
H.trigger_event('MiniFilesExplorerClose')
-- Focus on target window
explorer = H.explorer_ensure_target_window(explorer)
-- - Use `pcall()` because window might still be invalid
pcall(vim.api.nvim_set_current_win, explorer.target_window)
-- Update currently shown cursors
explorer = H.explorer_update_cursors(explorer)
-- Close shown explorer windows
for i, win_id in pairs(explorer.windows) do
H.window_close(win_id)
explorer.windows[i] = nil
end
-- Close possibly visible help window
for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local buf_id = vim.api.nvim_win_get_buf(win_id)
if vim.bo[buf_id].filetype == 'minifiles-help' then vim.api.nvim_win_close(win_id, true) end
end
-- Invalidate views
for path, view in pairs(explorer.views) do
explorer.views[path] = H.view_invalidate_buffer(H.view_encode_cursor(view))
end
-- Update histories and unmark as opened
local tabpage_id, anchor = vim.api.nvim_get_current_tabpage(), explorer.anchor
H.explorer_path_history[anchor] = explorer
H.opened_explorers[tabpage_id] = nil
-- Return `true` indicating success in closing
return true
end
--- Go in entry under cursor
---
--- Depends on entry under cursor:
--- - If directory, focus on it in the window to the right.
--- - If file, open it in the window which was current during |MiniFiles.open()|.
--- Explorer is not closed after that.
---
---@param opts Options. Possible fields:
--- - <close_on_file> `(boolean)` - whether to close explorer after going
--- inside a file. Powers the `go_in_plus` mapping.
--- Default: `false`.
MiniFiles.go_in = function(opts)
local explorer = H.explorer_get()
if explorer == nil then return end
opts = vim.tbl_deep_extend('force', { close_on_file = false }, opts or {})
local should_close = opts.close_on_file
if should_close then
local fs_entry = MiniFiles.get_fs_entry()
should_close = fs_entry ~= nil and fs_entry.fs_type == 'file'
end
local cur_line = vim.fn.line('.')
explorer = H.explorer_go_in_range(explorer, vim.api.nvim_get_current_buf(), cur_line, cur_line)
H.explorer_refresh(explorer)
if should_close then MiniFiles.close() end
end
--- Go out to parent directory
---
--- - Focus on window to the left showing parent of current directory.
MiniFiles.go_out = function()
local explorer = H.explorer_get()
if explorer == nil then return end
if explorer.depth_focus == 1 then
explorer = H.explorer_open_root_parent(explorer)
else
explorer.depth_focus = explorer.depth_focus - 1
end
H.explorer_refresh(explorer)
end
--- Trim left part of branch
---
--- - Remove all branch paths to the left of currently focused one. This also
--- results into current window becoming the most left one.
MiniFiles.trim_left = function()
local explorer = H.explorer_get()
if explorer == nil then return end
explorer = H.explorer_trim_branch_left(explorer)
H.explorer_refresh(explorer)
end
--- Trim right part of branch
---
--- - Remove all branch paths to the right of currently focused one. This also
--- results into current window becoming the most right one.
MiniFiles.trim_right = function()
local explorer = H.explorer_get()
if explorer == nil then return end
explorer = H.explorer_trim_branch_right(explorer)
H.explorer_refresh(explorer)
end
--- Reveal current working directory
---
--- - Prepend branch with parent paths until current working directory is reached.
--- Do nothing if not inside it.
MiniFiles.reveal_cwd = function()
local explorer = H.explorer_get()
if explorer == nil then return end
local cwd = H.fs_full_path(vim.fn.getcwd())
local cwd_ancestor_pattern = string.format('^%s/.', vim.pesc(cwd))
while explorer.branch[1]:find(cwd_ancestor_pattern) ~= nil do
-- Add parent to branch
local parent, name = H.fs_get_parent(explorer.branch[1]), H.fs_get_basename(explorer.branch[1])
table.insert(explorer.branch, 1, parent)
explorer.depth_focus = explorer.depth_focus + 1
-- Set cursor on child entry
local parent_view = explorer.views[parent] or {}
parent_view.cursor = name
explorer.views[parent] = parent_view
end
H.explorer_refresh(explorer)
end
--- Show help window
---
--- - Open window with helpful information about currently shown explorer and
--- focus on it. To close it, press `q`.
MiniFiles.show_help = function()
local explorer = H.explorer_get()
if explorer == nil then return end
local buf_id = vim.api.nvim_get_current_buf()
if not H.is_opened_buffer(buf_id) then return end
H.explorer_show_help(buf_id, vim.api.nvim_get_current_win())
end
--- Get file system entry data
---
---@param buf_id number|nil Buffer identifier of valid directory buffer.
--- Default: current buffer.
---@param line number|nil Line number of entry for which to return information.
--- Default: cursor line.
---
---@return table|nil Table of file system entry data with the following fields:
--- __minifiles_fs_entry_data_fields
---
--- Returns `nil` if there is no proper file system entry path at the line.
MiniFiles.get_fs_entry = function(buf_id, line)
buf_id = H.validate_opened_buffer(buf_id)
line = H.validate_line(buf_id, line)
local path_id = H.match_line_path_id(H.get_bufline(buf_id, line))
if path_id == nil then return nil end
local path = H.path_index[path_id]
return { fs_type = H.fs_get_type(path), name = H.fs_get_basename(path), path = path }
end
--- Get target window
---
---@return number|nil Window identifier inside which file will be opened or
--- `nil` if no explorer is opened.
MiniFiles.get_target_window = function()
local explorer = H.explorer_get()
if explorer == nil then return end
explorer = H.explorer_ensure_target_window(explorer)
return explorer.target_window
end
--- Set target window
---
---@param win_id number Window identifier inside which file will be opened.
MiniFiles.set_target_window = function(win_id)
if not H.is_valid_win(win_id) then H.error('`win_id` should be valid window identifier.') end
local explorer = H.explorer_get()
if explorer == nil then return end
explorer.target_window = win_id
end
--- Get latest used anchor path
---
--- Note: if latest used `path` argument for |MiniFiles.open()| was for file,
--- this will return its parent (as it was used as anchor path).
MiniFiles.get_latest_path = function() return H.latest_paths[vim.api.nvim_get_current_tabpage()] end