Compare commits

..

287 Commits

Author SHA1 Message Date
Ian Homer
949913f186 feat(api): rename_basename API and action (#1791)
* relative rename action

* 🔥 remove debug print statement

* 🐛 better handling of dot files

Also pickout extension in filename with more one dot

* 🔧 keymap e for relative-rename action

* 📝 update help with relative-rename mapping

*  add API for rename_relative

* 🚨 correct lint warnings

* rename_relative -> rename_root

* stylua

* ♻️ use fnamemodify instead of custom logic

* 💥 refactor renaming api using vim filename modifiers

Rename API now supports filename modifiers as arguments, although
only with limited support of options. The function signature however
will allow improvements going forward. The API signature is backward
compatible, although the behviour has changed as per the next comment.

This change changes the default behaviour of the renames, rename_full is
what rename was, rename now just renames the tail (i.e. the filename)

* 🐛 make api rename, without args, functional

*  allow modifier argument to be used in API call

* 📝 update documentation with new command name

* rename-file.fn takes only a modifier as argument

* add Api.fs.rename_basename, specify modifiers for rename, rename_sub

* add Api.fs.rename_node

* rename-file tidy allowed modifiers

* 🐛 fix bugs after last refactoring

rename ":t" and ":t:r" was moving file to root of project and not
maintaining sub-directory

* 🐛 correct absolute rename

which was loosing sub-directory on rename

* 🔥 remove debug print statements

* stylua

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-16 13:32:48 +11:00
Alexander Courtis
a8d26bb088 Revert "fix(#1815): don't schedule find_file calls, debounce update_focused_file with 15ms default (#1820)"
This reverts commit 623cecb809.
2022-12-16 13:15:32 +11:00
Alexander Courtis
623cecb809 fix(#1815): don't schedule find_file calls, debounce update_focused_file with 15ms default (#1820)
* fix(#1815): don't schedule find file calls

* fix(#1815): debounce BufEnter find_file

* fix(#1815): deprecate nvim-tree.find_file

* fix(#1815): debounce BufEnter find_file

* fix(#1815): debounce BufEnter find_file
2022-12-16 13:01:37 +11:00
Alexander Courtis
95ed588211 fix(#549): add more profiling ~tree init 2022-12-16 12:12:06 +11:00
Alexander Courtis
899ed45602 fix(watcher): only purge on subsequent setup calls, add git_purge log 2022-12-16 11:47:50 +11:00
David
0cd8ac4751 fix: Implicit current buf on centralize selection (#1792) 2022-12-12 13:14:41 +01:00
Anton
a2c75567ad feat(event): add WillRenameNode (#1821) 2022-12-12 11:56:50 +11:00
Alexander Courtis
8b4aaff783 remove legacy g: option migration 2022-12-12 09:50:52 +11:00
Eric Haynes
7177d95ac0 feat: paste and create always target closed folder, remove create_in_closed_folder (#1802)
* Fix default for file creation in closed directories

* Make paste in closed directories consistent with create

* doc: clarify create_in_closed_folder

* Remove create_in_closed_folder option

* doc: clarify create_in_closed_folder removal message (whoops)

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-11 14:40:38 +11:00
Alexander Courtis
b9aaf805a1 doc: format help 2022-12-11 12:12:19 +11:00
Alexander Courtis
3c5d9dd31f doc: consolidate and clarify :help examples 2022-12-10 16:25:02 +11:00
Alexander Courtis
c5dc80c36b feat(view): add filters.git_clean, filters.no_buffer (#1784)
* feat(view): add filters.git_clean

* feat(view): add filters.git_clean

* feat(view): add filters.no_buffer

* feat(view): filters.no_buffer misses unloaded, handles buffer in/out

* feat(view): filters.no_buffer matches directories specifically

* feat(view): filters.no_buffer clarify targets

* feat: add placeholder filters.diagnostics_ok, refactor filters

* feat(view): remove placeholder filters.diagnostics_ok
2022-12-10 15:55:33 +11:00
Anton
e49fa4e529 feat(event): dispatch Event.NodeRenamed on cut-paste (#1817) 2022-12-10 11:29:05 +11:00
rishabhjain9191
69a07d169a feat(view): always enable cursorline, users may change this behaviour via Event.TreeOpen (#1814)
* Update view.lua

* set cursorline to true
2022-12-10 11:20:40 +11:00
Richard Li
f8489c9929 fix(git): git rename not showing up for the renamed file (#1783)
* fixed git rename not showing up for the renamed file

* considered " -> " being a part of the filename

Fixed -> pattern to escape -
Fixed "\"" and "\\" in filename

* using string.find(, , true) to match plain ->

* Using -z and removed unnecessary logic
2022-12-03 14:56:38 +11:00
Richard Li
9d9c5711dc 1786 git next prev land on dirs (#1787)
* Filtered dir with git status that are open when show_on_open_dir is false

* refactored for single source of truth of existence of git status on a node

Putting `has_git_status()` in `explorer.common` because that's where node.status is constructed
Or at least I think that's where it's constructed

* 1786 semantic nit

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-03 14:39:00 +11:00
gegoune
07149daa0c docs: Update feature_request.md (#1788)
* Update feature_request.md

Closes #1654

* Update feature_request.md

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-03 13:01:30 +11:00
Richard Li
829e9f68e1 feat: add diagnostics.show_on_open_dirs git.show_on_open_dirs (#1778)
* feat(diagnostics): only show diagnostic on closed folder

* feat(git): only show git icon on closed folder
2022-11-29 11:12:34 +11:00
baahrens
0b319a1b28 feat(renderer): add NvimTreeOpenedFolderIcon NvimTreeClosedFolderIcon (#1768)
* feat: Add highlight group for opened folder

closes #1674

* docs: Add NvimTreeOpenedFolderIcon default

* feat: Add NvimTreeClosedFolderIcon highlight group

Defaults to NvimTreeFolderIcon
2022-11-28 11:17:09 +11:00
Alexander Courtis
9f7bed5536 doc: specify that the terminal emulator must be configured to use the patched font 2022-11-28 10:26:37 +11:00
Alexander Courtis
b17358ff4d fix(#1731 #1723 #1716): handle all external file system changes (#1757)
* fix(#1731): watcher refreshes node rather than the first node matching absolute path, profile refresh

* fix(#1731): reload explorer reloads closed folders

* fix(#1731): do not fire folder created event on file create

* fix(#1731): reload profile absolute path, not link to

* fix(#1731): find-file locks/profiles on real path, reloads when watchers disabled

* Revert "fix(#1731): reload explorer reloads closed folders"

This reverts commit 5dfd8bd2fa.

* fix(#1731): tidy watch reload

* fix(#1731): move refresh_node from watch to reload

* fix(#1731): find-file reloads all nodes for the containing directory

* fix(#1731): create-file refreshes synchronously

* fix(#1731): remove unused watch node

* fix(#1731): find-file refreshes root

* fix(#1716): create-file invokes find-file

* fix(#1731): refresh path walks down the tree to the targedt
2022-11-26 14:19:09 +11:00
David Aguilera
99d713644d feat(renderer): add renderer.root_folder_label (#1746)
* Add new renderer setting `add_root_updir` to fix #1743.

* Fix default value in docs.

* Remove proposed “add_root_updir” and rename “root_folder_modifier” to “root_folder_label”. Also, “root_folder_label” can be also a function now.

* chore: warn users about breaking change

* fix(#1743): use silent migration of root_folder_modifier

* fix(#1743): add example, document previous renderer.root_folder_modifier

* Add check to validate return type of “root_folder_label” is string.

* Change “root_folder_label” default value to “:~:s?$?/..?”.

* Add missing keyword “local” to local variable “label”.

Co-authored-by: David Aguilera <david.aguilera@neliosoftware.com>
Co-authored-by: gegoune <dev@clog.rocks>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-26 14:02:05 +11:00
Ibrahim Abdelkareem
68a2a0971e feat(diagnostics): add diagnostics.severity (#1755)
* feat: Support diagnostics severity

* fix: Revert Hunk

* feat: Supports min/max severity

* feat: Supports min/max severity: tidy doc

* feat: Supports min/max severity: tidy doc

* feat: Supports min/max severity: tidy doc

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-21 09:09:09 +11:00
Alexander Courtis
cc18122be1 fix(notify): log file notice info->debug 2022-11-20 12:26:59 +11:00
Alexander Courtis
e38e061710 feat(api): add api.tree.get_nodes 2022-11-19 15:54:16 +11:00
Alexander Courtis
a65063cb0a fix(notify): remove unused varargs, log file notice debug->info 2022-11-19 14:34:39 +11:00
Wessel Blokzijl
c49499413a feat(tabs): add tab.sync options (#1698)
* Sync closing of nvim-tree across tabs

* chore: remove vim.* "requires"

* Sync closing of nvim-tree across tabs

* Fix api.close calls

* Fix issue from merge

* Implement changes

* Finish todos and add close_all_tabs

* silently refactor options, add doc

* fix vinegar example

* Refactor close to work with tabid

* Close nvim tree if last buffer

* close and abandon all tabs on subsequent setup calls

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-19 13:57:45 +11:00
yazdani kiyan
1837751efb fix(paste): allow pasting into empty root directory
assign main explorer when node name is `..` to allow pasting into the
root directory when its empty.
Fixes #1736
2022-11-16 13:41:35 +01:00
yazdani kiyan
059e4cadd6 fix(paste): paste into empty root directory 2022-11-16 13:40:48 +01:00
Alexander Courtis
9d241e5f58 fix(system-open): use notify for system-open failures and tidy messages 2022-11-15 13:01:15 +11:00
Maxim Sokolov
cf908370fb fix(#1740): Error while running :NvimTreeCollapseKeepBuffers (#1741) 2022-11-13 15:52:44 +11:00
Alexander Courtis
f1f89f2062 contributions reference wiki 2022-11-13 15:09:20 +11:00
Alexander Courtis
bed442fd93 move troubleshooting from readme to wiki 2022-11-13 14:45:13 +11:00
Alexander Courtis
77dd666e36 doc: move tips to wiki 2022-11-13 14:33:31 +11:00
Alexander Courtis
e0e23f2d62 doc: add labels hint to contributing 2022-11-13 10:37:21 +11:00
Alexander Courtis
e204a7d819 fix(#1728): escape cwd changes to prevent environment variable expansion (#1729) 2022-11-12 14:50:14 +11:00
Alexander Courtis
bcb2a5a80d fix(#1720): .git watch only FETCH_HEAD, HEAD, HEAD.lock, config, index (#1732)
* fix(#1720): .git watch only HEAD, config and index

* fix(#1720): .git watch only FETCH_HEAD, HEAD, HEAD.lock, config, index
2022-11-12 14:38:33 +11:00
Alexander Courtis
7e892767bd Revert "fix(#1716): focus file/directory when created in a sub-directory, don't dispatch FolderCreated on file creation (#1722)"
This reverts commit bdc4ec6abd.
2022-11-06 13:32:24 +11:00
Alexander Courtis
d91f885819 Revert "fix(#1723): find_file for externally created new file results in folder unable to be opened"
This reverts commit be2ccd4b1a.
2022-11-06 13:21:24 +11:00
Alexander Courtis
8cc369695b fix: replace vim.* "requires" with explicit calls to vim functions (#1701) 2022-11-06 10:37:33 +11:00
Tomohiro Endo
6d6a44626d fix(watcher): failure on watcher teardown message (#1726) 2022-11-06 10:30:12 +11:00
Alexander Courtis
bdc4ec6abd fix(#1716): focus file/directory when created in a sub-directory, don't dispatch FolderCreated on file creation (#1722)
* fix(#1716): focus file/directory when created in a sub-directory, don't dispatch FolderCreated on file creation

* fix(#1716): focus file/directory when created in a sub-directory

* fix(#1716): focus file/directory when created in a sub-directory
2022-11-06 10:08:32 +11:00
Alexander Courtis
be2ccd4b1a fix(#1723): find_file for externally created new file results in folder unable to be opened
* fix(#1723): find_file for externally created new file results in folder unable to be opened

* fix(#1723): find_file for externally created new file results in folder unable to be opened
2022-11-05 16:34:41 +11:00
Alexander Courtis
a0f3e99b2d feat(watcher): tear down watcher on failue, warning the user (#1707) 2022-11-05 10:25:14 +11:00
Alexander Courtis
ed9db632a8 feat(watcher): add filesystem_watchers.ignore_dirs (#1705) 2022-11-05 10:24:25 +11:00
Alexander Courtis
33ce8e3c73 fix(#1711): open in a new window when no window picker and no available window (#1715) 2022-11-05 10:23:03 +11:00
kylo252
6ca6f99e76 feat(notify): add notify.threshold (#1693)
* feat: configurable notification level

add `notify.threshold` to setup opts

* feat: configurable notification level: add threshold example doc

* feat: configurable notification level: log always comes last

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-01 10:24:40 +11:00
David Brouwer
ada2c6441d fix(#1712): invalid window ID on colorscheme (#1714) 2022-11-01 10:04:47 +11:00
wyrid
cd2f7569db feat(api): add api.marks.clear (#1708) 2022-11-01 08:46:56 +11:00
wyrid
cbb5313f90 feat(api): add api.fs.clear_clipboard (#1706)
* feat: command to clear the clipboard

* feat: command to clear the clipboard: stylua

* feat: command to clear the clipboard: add to :help

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-10-31 15:48:05 +11:00
Sabu Siyad
fba97517bb fix(#1679): renderer.full_name correctly shows for one character outside (#1688)
ref: `:h getwininfo()`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

Signed-off-by: Sabu Siyad <hello@ssiyad.com>
2022-10-31 15:38:09 +11:00
Alexander Courtis
1044eba9e7 doc: update event subscription example 2022-10-31 13:59:03 +11:00
wyrid
3845039c1a fix: use pcall to prevent live-filter regex errors (#1689)
* fix: use pcall to prevent live-filter regex errors

Wrap live filter's match function in pcall to prevent errors caused by
invalid regex while typing.

* nit: use ok for pcall rc

* nit: stylua fix

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-10-29 14:22:14 +11:00
Alexander Courtis
dd90bfa155 fix(#1671): split with no window picker will always find an available window (#1677) 2022-10-29 13:42:56 +11:00
Alexander Courtis
cb98892dea doc: add hitest notes and add termguicolors to example setups 2022-10-29 12:45:24 +11:00
kiyan
65c2ba8952 fix(colorscheme): update winhl on colorscheme change
fixes #1696
2022-10-28 14:19:01 +02:00
Alexander Courtis
1be1e17be5 doc(#1676): clarify view.mapping.list contents including case of key 2022-10-25 09:37:02 +11:00
Alexander Courtis
49c32c0dda Revert "fix(#1676) case insensitive mapping key remove and override (#1682)"
This reverts commit 5a798b3be0.
2022-10-25 08:00:17 +11:00
Ofir Gal
23c0fe9a0a Added NvimTreeLineNr higlight (#1684) 2022-10-24 19:53:13 +00:00
Alexander Courtis
5a798b3be0 fix(#1676) case insensitive mapping key remove and override (#1682)
* fix(#1676): remove_keymaps matches case insensitively

* fix(#1676): mappings.list.n.key matches case insensitively for overrides
2022-10-24 14:51:07 +11:00
Alexander Courtis
58055a0397 doc(#1672): clarify mappings example and doc 2022-10-24 11:45:33 +11:00
Alexander Courtis
0122a71fce doc(#1655): add roadmap and expand on API/events/actions (#1666)
* doc(#1655): add roadmap and expand on API/events/actions

* doc(#1655): add roadmap and expand on API/events/actions
2022-10-23 15:01:58 +11:00
Alexander Courtis
3170e33462 doc(#1606): specify watcher and git when profiling 2022-10-23 10:47:47 +11:00
Alexander Courtis
ea09ab497e doc(#731): add single mouse mapping notes 2022-10-23 10:25:18 +11:00
Alexander Courtis
e94f517798 fix(#1675): open-file sanity check mode 2022-10-22 14:40:20 +11:00
Alexander Courtis
2b970635d1 fix(#1668): update issue link 2022-10-18 11:34:26 +11:00
Alexander Courtis
3a2f68b9d5 fix(#1668): revert all startup behaviour changes back to 540055b 2022-10-18 11:14:35 +11:00
Alexander Courtis
4e24505e2b fix(#1664): respect hijack_directories.enable on startup when not open_on_setup (#1665) 2022-10-17 14:49:57 +11:00
Alexander Courtis
48992fd3e8 fix(#1639): ensure tree autocommands match filetype as well as name (#1640)
* fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer

* Revert "fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer"

This reverts commit e7136078f7.

* fix(#1629): nvim start with file named *NvimTree* treats file as tree

* fix(#1629): nvim start with file named *NvimTree* treats file as tree

* fix(#1639): ensure tree autocommands match filetype as well as name

* fix(#1639): fix bad merge

* fix(#1639): ensure tree autocommands match filetype as well as name
2022-10-17 12:31:41 +11:00
Ali Almohaya
c995ce0878 fix(#1643): preview on floating window (#1648)
* fix: preview on floating window

* chore: redrawing the tree after setting current win

* chore: ignore winleave autocmd on preview action

* fix: typo in comment

* chore: call correct window id

* chore: revert changes in focus method

* chore: check if float window is enabled before ignoring WinLeave
2022-10-16 12:17:17 +11:00
Alexander Courtis
c446527056 chore(#1649): remove workaround for https://github.com/neovim/neovim/issues/17395 which was fixed in nvim 0.7.0 (#1650) 2022-10-15 13:49:13 +11:00
Alexander Courtis
55aa0062b9 fix(#1270): ensure explorer exists at startup before propagating FS changes 2022-10-15 12:44:59 +11:00
emmanueltouzery
187388b7f5 fix(#1632): autocenter: avoid use of feedkeys (#1632) 2022-10-15 11:10:42 +11:00
Alexander Courtis
6ff828b25b doc: vim.g.loaded -> vim.g.loaded_netrw 2022-10-15 10:50:07 +11:00
Alexander Courtis
c4ac723a83 fix(#1626): obey splitright/below when splitting existing windows (#1641) 2022-10-15 10:22:03 +11:00
Alexander Courtis
23a564f1cd doc: link screenshots to showcases page 2022-10-15 10:19:14 +11:00
Ali Almohaya
b07701f9da chore: remove pngs from .github folder and use direct links for imgs in README.md (#1642) 2022-10-13 16:55:12 +02:00
Ali Almohaya
b01e7beaa6 fix(#1628): quit_on_open e: do not open in the tree's window (#1637)
* fix: prevent quit_on_open from opening in same window

* chore: remove condition for quit_on_open

* fix: prevent opening file in a new window on floting nvim-tree

* stylua

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-10-11 10:28:30 +11:00
Alexander Courtis
c66cbdfc25 fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer (#1634)
* fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer

* Revert "fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer"

This reverts commit e7136078f7.

* fix(#1629): nvim start with file named *NvimTree* treats file as tree

* fix(#1629): nvim start with file named *NvimTree* treats file as tree
2022-10-11 10:00:03 +11:00
Alexander Courtis
875d38e52c doc: add alex-courtis screenshot with source 2022-10-09 14:54:55 +11:00
Alexander Courtis
0db4977303 doc: update hero screenshots with default options 2022-10-09 14:18:00 +11:00
Alexander Courtis
54afa503a9 chore: replace urls from kyazdani42 -> nvim-tree 2022-10-09 12:52:54 +11:00
kiyan
3d58a9b2cf fix(fs): create file failure when reloading watch path for node
because node can be a file or a symlink.
fixes #1633
2022-10-08 16:11:34 +02:00
kiyan
b4d704e88d chore: replace urls from kyazdani42 -> nvim-tree 2022-10-08 11:31:57 +02:00
Alexander Courtis
4a01f90d11 feat(view): float.quit_on_focus_loss documentation clarification 2022-10-08 14:39:23 +11:00
emmanueltouzery
79f631bc1d feat(view): add float.quit_on_focus_loss, float respects actions.open_file.quit_on_open (#1621) 2022-10-08 14:35:20 +11:00
Alexander Courtis
be2b4f58e6 fix(#1615): focus created file when command line prompt requires confirmation (#1622)
* fix(#1615): focus created file when command line prompt requires confirmation

* fix(#1615): focus created file when command line prompt requires confirmation
2022-10-08 14:26:31 +11:00
Alexander Courtis
c5536db0b7 fix(#1270): open_on_setup_file does not override open_on_setup, hijack_directories does not override startup behaviour (#1618) 2022-10-08 14:25:38 +11:00
longguzzz
7282f7de8a feat: add NvimTreeCursorLineNr (#1616) 2022-10-01 12:51:22 +02:00
rapan931
45d386a359 fix: remove unnecessary conditions (#1614) 2022-09-30 13:37:31 +02:00
kiyan
43fd138544 fix(ci): stylua action version number 2022-09-29 14:04:30 +02:00
kiyan
4aef454cd2 fix(styling): empty line 2022-09-29 13:59:05 +02:00
rapan931
11b524899f fix: restore eventignore (#1612) 2022-09-29 13:55:24 +02:00
Xyhlon
9914780cba fix(#1547): pass explicit system arguments to for git toplevel and untracked actions
* the nice fix

* fix(#1547): pass git toplevel cwd unescaped, pass git untracked arguments as per toplevel

Co-authored-by: Maximilian Philipp <philipp@student.tugraz.at>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-09-25 12:58:05 +10:00
Alexander Courtis
52b0c32152 chore: add help screenshot, update example 2022-09-23 11:16:21 +10:00
Alexander Courtis
0417d9148b feat: focus_empty_on_setup 2022-09-22 15:15:03 +10:00
Alexander Courtis
540055be5f chore: document :NvimTreeFindFile! and add bang :NvimTreeFindFileToggle! 2022-09-22 14:18:49 +10:00
Alexander Courtis
fbd421da71 chore: remove non-functional sides bottom/top 2022-09-22 13:53:09 +10:00
Alexander Courtis
ac8d259bad fix(prompt): add select_prompts to suit UI decorator plugins such as dressing and telescope 2022-09-22 13:23:49 +10:00
Alexander Courtis
5cb87c037d fix(#1553): set correct side on vim open directory (#1594) 2022-09-22 09:06:23 +10:00
KuuWang
3676e0b124 feat(sorters): allow user sort_by
* feat: Mixin Sorter (#1565) Self Solved

adding `mixin` sort options for `rust` like package systems

```

package.rs
package/
  __inside__

lib.rs
lib/
  _inside_

a.rs
b.rs
module.rs

```

* feat: sort_by, after_sort options for more convinient using

```
*nvim-tree.sort_by*
Changes how files within the same directory are sorted.
Can be one of 'name', 'case_sensitive', 'modification_time' or 'extension',
'function'.
>
  sort_by = function(a, b)
    if not (a and b) then
      return true
    end
    if a.nodes and not b.nodes then
      return true
    elseif not a.nodes and b.nodes then
      return false
    end

    return a.name:lower() <= b.name:lower()
  end

  end
  Type: `string | function(a, b)`, Default: `"name"`

*nvim-tree.after_sort*
Related to nvim-tree.sort_by, this function runs without mergesort.
Can be defined by your own after-sort works.
  Type: `function(table)`, Default: `disable`

>
  after_sort = function(t)
    local i = 1

    while i <= #t do
      if t[i] and t[i].nodes then
        local j = i + 1
        while j <= #t do
          if t[j] and not t[j].nodes and t[i].name:lower() == t[j].name:lower():match "(.+)%..+$" then
            local change_target = t[j]
            table.remove(t, j)
            table.insert(t, i, change_target)
            break
          end
          j = j + 1
        end
      end
      i = i + 1
    end
  end

```

* remove: after_sort ( misunderstood feature )

sort_by parameter can be function.

``` lua
  sort_by = function(t)
    local sorters = require "nvim-tree.explorer.sorters"
    local comparator = sorters.retrieve_comparator("name")
    sorters.split_merge(t, 1, #t, comparator) -- run default merge_sort
    local i = 1

    while i <= #t do
      if t[i] and t[i].nodes then
        local j = i + 1
        while j <= #t do
          if t[j] and not t[j].nodes and t[i].name:lower() == t[j].name:lower():match "(.+)%..+$" then
            local change_target = t[j]
            table.remove(t, j)
            table.insert(t, i, change_target)
            break
          end
          j = j + 1
        end
      end
      i = i + 1
    end
  end,

```

* try-fix: change existing merge_sort function, call user's sort_by

hope.. like it...?

* doc: explain function parameter and return, add more complex example

* fix: reorder with user-comparator exceed memory limit

apply merge_sort
check nil & type for senitize

* fix: user_index based sorting ( create index )

for performance, create index once,
using index to re-ordering

* fix: fence problems

* doc & fix: merge_sort problem fix & nil sorting

add complex example

* fix: sort_by detect and use string and nil

* doc: revert sort_by to simple

* fix: sort_by does not return anything

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-09-18 16:00:49 +10:00
Alexander Courtis
fb8735e96c doc: eager netrw disabling 2022-09-11 11:41:12 +10:00
Alexander Courtis
3e49d9b748 doc: direct users to ask questions instead of raising issues 2022-09-05 14:22:38 +10:00
Alexander Courtis
9ba5366115 doc: direct users to ask questions instead of raising issues 2022-09-05 14:21:03 +10:00
Alexander Courtis
b83e06f7fe Revert "doc: direct users to ask questions instead of raising issues"
This reverts commit 904110f8a9.
2022-09-05 14:15:54 +10:00
Alexander Courtis
904110f8a9 doc: direct users to ask questions instead of raising issues 2022-09-05 14:15:03 +10:00
Alexander Courtis
e282420111 fix(#1564): reset &bufhidden when opening an existing (maybe previewed) buffer (#1573) 2022-09-05 10:10:58 +10:00
ADoyle
951e10a64e fix(#1568): show relative path of symlink destination (#1569) 2022-09-04 12:23:56 +10:00
Krzysztof Cieśla
d753a1da9a fix(view): file filter and info popup above floating view
Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
2022-09-03 14:29:18 +10:00
Alexander Courtis
011a7816b8 doc: add PR tips to contributing 2022-08-30 16:42:55 +10:00
Mivort
e8bf3d778a feat(renderer): add renderer.indent_width (#1505)
* feat: add config option for a tree indent width

add 'indent_width' option to configure visible indent for tree nesting
levels (default is 2).

* add 'bottom' char for a corner extension

* apply stylua formatting

* provide value constraints in documentation

* limit minimal indent width

* make marker symbols have one utf8 char width

* match stylua formatting

* add the commentary regarding utf-8 first symbol match
2022-08-30 09:44:30 +10:00
Bryan Baron
757951ba6b feat(view): floating window's optional adaptive size specification (#1559) 2022-08-30 08:50:18 +10:00
Krzysztof Cieśla
07f59e7450 fix(#1539): Fix closing nvim-tree float when file is removed (#1546)
* Fix closing nvim-tree float when file is removed

* Revert changes for non-float

Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
2022-08-30 08:47:13 +10:00
Piotr Doan
4a725c0ca5 fix(#1555): incorrect exe highlight in Windows filesystem from WSL (#1557) 2022-08-29 10:53:23 +10:00
Krzysztof Cieśla
ce5d0a6b7d fix(#1543): Do not resize nvim-tree window if float is enabled (#1556)
Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
2022-08-27 13:36:26 +10:00
Alexander Courtis
c272c88daf fix(#1551): handle git status TT as staged 2022-08-27 11:58:16 +10:00
Alexander Courtis
c3ea264947 feat(view): allow function for view.float.open_win_config (#1538) 2022-08-23 17:14:23 +10:00
Alexander Courtis
259efeee62 fix(#1540): watcher ignore directories with name exactly '.git' 2022-08-23 10:29:45 +10:00
Alexander Courtis
e3353c4cb4 fix(#1529): ensure tree window exists before closing (#1537) 2022-08-22 16:58:41 +10:00
Alexander Courtis
2bb15fd98f doc: add PR tips to contributing 2022-08-22 16:22:32 +10:00
Sebastian Volland
90dcf42bba fix(#1533): make toggle_mark ignore non-togglable nodes. (#1534) 2022-08-22 14:24:25 +10:00
Sebastian Volland
049cdd3073 fix(#1518): sort_by=modification_time not reordering on refresh. (#1519) 2022-08-22 14:19:06 +10:00
Sebastian Volland
c5fba1ec18 fix(#1520): file type changes are not detected. (#1521) 2022-08-22 11:41:11 +10:00
Alexander Courtis
81eb718394 fix: inverted diagnostic navigation keymaps 2022-08-20 14:40:09 +10:00
Jonathan Gollnick
9fd7b7ae29 fix(#1514): inverted git navigation keymaps (#1515) 2022-08-20 14:37:51 +10:00
Alexander Courtis
d9edddb849 fix(#1503): focus last win before close (#1509) 2022-08-20 14:32:28 +10:00
axlauri
09a51266bc fix(#1494): git showUntracked value and log (#1504)
* should_show_untracked correctly evaluates status.showUntrackedFiles

* git.Runner:_run_git_job removes nils before logging args
2022-08-15 14:30:22 +10:00
Carlos Castillo
b314b3a699 fix(#1500): focusing directories with a trailing slash in their path doesn't work (#1501) 2022-08-14 15:00:04 +10:00
Alexander Courtis
261a5c380c fix(#1480): break symlink cycle on find-file, search-node (#1482)
* fix(#1480): break symlink cycle on find-file

* fix(#1480): break symlink cycle on search-node

* fix(#1480): break symlink cycle on search-node

* fix(#1480): break symlink cycle on find-file
2022-08-08 12:46:09 +10:00
Hoang Nguyen
a73d0d4800 feat(file-popup): add actions.file_popup.open_win_config
* file-popup: add nvim_open_win configuration

* docs: update file-popup configuration
2022-08-08 10:52:14 +10:00
Alexander Courtis
ff6e7966f3 fix(#1484): better error handling in git utils get_toplevel 2022-08-07 12:16:03 +10:00
Krzysztof Cieśla
7323c81bd6 feat(view): Floating nvim tree window #1377 (#1462)
* Simple mock-up of floating nvim-tree window

* Passing whole table to nvim_open_win()

* Run update-help.sh

* Use vim.api alias

* Add comment to float options

* Added `anchor` to float options

* Enabling float window enforces `actions.open_file.quit_on_open`

* Added documentation

* add view.float.open_win_config, skipping validation

* Made nvim-tree window closes when float is enabled

* Close nvim-tree window when out of focus

* Update help

Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-08-06 15:40:07 +10:00
Alexander Courtis
1685484738 doc: reinstate mapping doc, mark on_attach as experimental (#1481) 2022-08-02 13:59:51 +02:00
Alexander Courtis
cfc4692a3f fix(#1479): apply remove_keymaps to default mappings 2022-08-02 09:29:57 +10:00
kiyan
451901ca9c chore(keymap): extract filtering into function 2022-07-31 12:32:55 +02:00
kiyan
9bbf95e616 fix(keymaps): get_keymaps takes remove_keymaps as parameter 2022-07-30 10:50:10 +02:00
kiyan
665813b9e6 fix(perf): explorer was creating new table for each new entry
augment performance on large folder by a factor of 10.
my /nix/store explorer goes from ~12sec to ~1.5sec.
2022-07-29 09:35:15 +02:00
Austin Harris
7fcb48c852 feat: add option for folder arrows to be inline with indent markers (#1468) 2022-07-28 11:49:23 +02:00
kiyan
ac90664001 fix(watchers): disable watchers on kernel filesystems
fixes #1465
2022-07-28 11:45:47 +02:00
Kian-Meng Ang
2928f8fe31 fix(docs): typos (#1470) 2022-07-27 13:48:14 +02:00
kiyan
e632ac7c81 fix(create-file): when node is nil, create the file at root
fixes #1467
2022-07-27 09:33:01 +02:00
Kiyan
64cc3c17e1 feat(mapping): deprecate user mappings and add on_attach (#1424) 2022-07-26 11:09:39 +02:00
kiyan
5f30a7bee4 chore(config): enable filesystem watchers by default 2022-07-26 10:46:59 +02:00
Alexander Courtis
eff1db341c chore(watchers): Watcher shares single fs_event from Event, node watchers use unique path prefixed debounce context (#1453) 2022-07-26 10:43:58 +02:00
kiyan
e5222970d9 chore(api): add missing functions 2022-07-25 15:02:55 +02:00
kiyan
e95bfbfc2d fix(api): use copy.node instead of copy.name
ref #1461
2022-07-25 14:35:01 +02:00
kiyan
a0448184af fix(api): copy is a table, copy -> copy.name
fixes #1461
2022-07-25 13:38:28 +02:00
Alexander Courtis
86b9da5ca5 chore(git): get_project_root cache cwd_to_project_root after checking existence (#1457) 2022-07-25 11:27:12 +02:00
Kiyan
e7832785d2 feat(api): add public API module (#1432) 2022-07-25 11:11:48 +02:00
John Fred Fadrigalan
d927e89aa9 refactor(actions): remove linefeed on info messages. (#1450) 2022-07-22 10:10:58 +02:00
kiyan
08ab346f03 fix(scripts): default mappings should put a comma at the end of each line 2022-07-21 14:38:29 +02:00
Gutyina Gergő
522bde4ea5 fix(docs) Add commas in example config for docs (#1447) 2022-07-21 14:31:18 +02:00
kiyan
79434c2b3c feat(tab_change): introduce new option to filter buffer by bufname or ft
Also fixes changing tab by deferring the call on tab enter.
New option `ignore_buf_on_tab_change` to avoid opening for some tabs.
Some example could be neogit, vim fugitive, man pages ...
2022-07-21 11:14:40 +02:00
FotiadisM
1e3c578eeb fix: count unicode codepoints instead of bytes (#1445) 2022-07-20 23:05:44 +02:00
kiyan
630305c233 fix(executable): prevent nil extensions in executable check
fix on windows
fixes #1444
2022-07-19 14:11:53 +02:00
yehy4
c964fa24d0 fix(git): fix inverted condition logic introduced in #1433 (#1443) 2022-07-19 11:48:32 +02:00
kiyan
8dc2144e87 refactor: use vim.ui.input for y/n selections
also add clear_prompt again.
fixes #1441
2022-07-19 11:31:09 +02:00
Klesh Wong
b754eb8359 fix(explorer): reload executable stat (#1427) 2022-07-19 11:25:41 +02:00
Alexander Courtis
f85b4d9952 chore(git): profile git toplevel and untracked calls (#1435) 2022-07-19 17:44:24 +10:00
yehy4
203bb7e176 fix(git): prevent execution of git commands when git integration is disabled (#1433) 2022-07-19 17:44:05 +10:00
Alexander Courtis
ecca8118f8 doc: remove fs_poll interval and update doc 2022-07-19 09:39:03 +10:00
kiyan
2edbe759cd fix(open-file): vim.op -> vim.opt 2022-07-18 15:44:52 +02:00
kiyan
ba5c18dc2b feat: add confirmation kind to select y/n 2022-07-18 14:38:32 +02:00
kiyan
1018a83e10 fix(create-file): pass file in parameter 2022-07-18 14:34:10 +02:00
kiyan
1ee6a3ea65 feat(create-file): use vim.ui.select for confirmation
fixes #1434
fixes #1294
2022-07-18 14:32:19 +02:00
kiyan
2d629cab78 feat(remove-file): use vim.ui.select 2022-07-18 14:28:08 +02:00
kiyan
7cffe14743 feat(trash): use vim.ui.select for confirmation 2022-07-18 14:25:52 +02:00
kiyan
1b667bc99e feat(search-node): use vim.ui.input 2022-07-18 14:20:55 +02:00
kiyan
69aec67edb feat(copy-paste): use vim.ui from prompting 2022-07-18 14:17:25 +02:00
kiyan
18447132fc feat(notify): switch all print/nvim_*write statements to utils.notify 2022-07-18 14:04:48 +02:00
kiyan
21fadc1f38 chore: move nvim-tree.utils.warn -> notify.warn
add notify.error and notify.info
2022-07-18 13:46:11 +02:00
kiyan
ec530e73be fix(wipe): wipe all nvim-tree rogue buffers
also prevent find_file if bufnr is not valid
fixes #1438
2022-07-18 13:29:56 +02:00
Alexander Courtis
16753d5e25 doc: add help hint to invalid config warning 2022-07-18 10:13:16 +10:00
Alexander Courtis
06e48c29c4 chore(watchers): refactor events and make debouncer safe
- fs poll -> fs events
- make debouncer safe and fix diagnostics events
2022-07-17 08:50:24 +02:00
Kiyan
26512c369f feat(marks): add bulk move action (#1419) 2022-07-17 08:25:11 +02:00
kiyan
208ce0b153 doc(contrib): add notice for git hook setup 2022-07-16 15:44:05 +02:00
kiyan
4900d66370 fix(open-file): focus file if already opened 2022-07-16 15:38:50 +02:00
Kiyan
89becc7604 feat(marks): add navigation (next, previous, select) (#1415) 2022-07-16 10:40:47 +02:00
Kiyan
b32c88333f feat(movement): allow circular movement for sibling next and prev (#1416) 2022-07-16 10:39:24 +02:00
kiyan
449b5bd0cb fix(renderer): padding indent with folders only 2022-07-15 18:09:28 +02:00
kiyan
9a02dedd92 fix(renderer): indent markers with arrows
breaking: glyphs for indent markers should only be one block large
2022-07-15 09:33:40 +02:00
kiyan
19425c5896 refactor(renderer): extract bools into variables 2022-07-14 19:07:12 +02:00
kiyan
8632ac2739 fix(renderer): indent markers + folder arrows should offset
fixes #1421
2022-07-14 19:04:01 +02:00
kiyan
80dc86e874 refactor(actions): use vim.keymap.set/del 2022-07-14 09:57:37 +02:00
kiyan
7087af83f3 fix(keypress): use <cmd> instead of : to avoid triggering CmdLineEnter
fixes #1417
2022-07-14 09:33:19 +02:00
ii14
c231933fcd feat: add -bar option to command definitions (#1422)
Co-authored-by: ii14 <ii14@users.noreply.github.com>
2022-07-14 09:17:30 +02:00
kiyan
0f96e32326 fix(actions): dispatching filter should not match for "live" keyword
fixes #1420
2022-07-12 10:16:01 +02:00
kiyan
6a49a0301f refactor(marks): fix offset line and move into init.lua
also set node in marks record instead of true
2022-07-12 09:34:26 +02:00
kiyan
078a9e5bf9 chore: move focus_file to utils 2022-07-11 16:55:33 +02:00
Kiyan
df92f1527f feat(bookmarks): add bookmark feature (#1412) 2022-07-11 10:00:12 +02:00
kiyan
0fa2ec1950 fix(actions): create file should defer more to focus 2022-07-11 09:49:10 +02:00
Alexander Courtis
26d0757bd9 doc: fix bad link in readme 2022-07-11 09:44:43 +10:00
kiyan
ad1f3ef3bc feat(renderer): show symlink folder destination
fixes https://github.com/kyazdani42/nvim-tree.lua/issues/980
2022-07-10 10:47:51 +02:00
kiyan
8d0c93db4a refactor(collapse-all): extract buffer matching logic 2022-07-10 10:03:48 +02:00
kiyan
2d2cbe63f4 refactor(actions): split movements into multiple modules 2022-07-10 09:53:58 +02:00
Kiyan
831f1158c3 refactor(actions): move actions into semantic modules (#1410) 2022-07-10 09:47:52 +02:00
kiyan
90bf14014e fix(file rename): edit buffer when renaming to reset filetype
fixes https://github.com/kyazdani42/nvim-tree.lua/issues/1404
2022-07-10 09:39:11 +02:00
Alexander Courtis
fd562ede63 fix(#1406): allow nvim-tree.renderer.icons.show.folder_arrow
* fix(#1406): allow nvim-tree.renderer.icons.show.folder_arrow when not folder

* fix(#1406): allow nvim-tree.renderer.icons.show.folder_arrow when indent markers enabled

* fix(builder): highlight first iteration for arrow column

* fix stylua

Co-authored-by: kiyan <yazdani.kiyan@protonmail.com>
2022-07-10 12:14:18 +10:00
kiyan
95c57e034a fix(dispatch): dispatch help toggle when its not shown
fixes #1411
2022-07-09 14:42:14 +02:00
kiyan
c037c7ae84 refactor(change-dir): add profile from higher order function 2022-07-09 12:14:41 +02:00
Arthur
fdcdb0ddf3 :help nvim-tree.setup -> :help nvim-tree-setup (#1409) 2022-07-09 12:10:59 +02:00
kiyan
1e7019f91e refactor(dispatch): cleanup dispatch logic 2022-07-09 12:09:51 +02:00
kiyan
63831d5179 refactor(actions): move on_keypress to dispatch module 2022-07-09 11:43:58 +02:00
Alexander Courtis
b81ab199a5 fix(help): clear git signs before draw 2022-07-09 11:44:39 +10:00
Alexander Courtis
d0ca2dab00 stylua 0.14.0 2022-07-09 11:26:55 +10:00
Alexander Courtis
08db5a576d doc: clarify need for patched font for nvim-web-devicons 2022-07-09 11:14:15 +10:00
Alexander Courtis
22044589fe doc: add tip to hide .git folder 2022-07-09 10:37:18 +10:00
kiyan
4bd919a75f fix(get-node-from-path): group dirs should be returned before nodes 2022-07-06 14:00:43 +02:00
kiyan
9d3602e8ea fix(find-file): do not recurse on closed nodes 2022-07-06 13:52:36 +02:00
kiyan
c84735483f feat: add on_tree_resize event 2022-07-06 13:35:29 +02:00
kiyan
eb6dde4733 fix(change-dir): cd command concatenation 2022-07-06 09:06:29 +02:00
Edwar Martinez Vale
418fc971fc fix(iterators): the index does not increase (#1399) 2022-07-06 01:07:36 +02:00
Rammiah
269820e800 fix(actions): change_dir to wrong directory (#1398) 2022-07-05 19:29:15 +02:00
Toby O'Sullivan
38fabe86cb Optionally suppress the symlink destination (#1396) 2022-07-05 10:05:21 +02:00
Kiyan
f43b8af8f4 chore(iterators): create Iterator module and migrate iterators to use it (#1392) 2022-07-04 14:13:14 +02:00
kiyan
70bdf496ea chore: remove quit_on_open from view and use abandon current window 2022-07-04 14:12:56 +02:00
Grzegorz Rozdzialik
40e515df87 fix(view): do not close window when NvimTree buffer is replaced (#1391) 2022-07-04 14:11:42 +02:00
kiyan
28c4bb01f6 fix: close view before hijacking current window 2022-07-04 10:17:06 +02:00
kiyan
19dcacf06e chore: cleanup change dir module 2022-07-03 12:41:13 +02:00
Alexander Courtis
736cc843e1 feat(#1389): add git.show_on_dirs (#1390) 2022-07-03 11:57:12 +02:00
Alexander Courtis
80d4f28383 feat(#1245): add next_diag_item and prev_diag_item actions 2022-07-03 16:04:49 +10:00
Krasimir Zahariev
21516f447b feat(actions): expand_all 'exclude' option (#1388) 2022-07-02 18:17:39 +02:00
kiyan
cbbc799e6c fix(trash): do not run trash command when trash is not executable 2022-07-02 12:08:46 +02:00
Rammiah
ec09b80c7b fix(actions): close_node doesn't close for grouped node (#1385) 2022-07-01 15:49:31 +02:00
Alexander Courtis
72858986f9 fix(#1366): warn when trash cmd missing (#1378) 2022-06-28 18:29:42 +02:00
Alexander Courtis
c18aa389a3 doc: setup may be invoked multiple times 2022-06-28 11:27:14 +10:00
Alexander Courtis
e401a4c957 feat(watcher): debounce FS watchers 2022-06-28 11:18:22 +10:00
Alexander Courtis
7a795d78fa feat(watcher): partial git refresh (#1373) 2022-06-27 11:12:28 +10:00
Alexander Courtis
247f80b633 doc: clarify quit-on-last-window-hack support status 2022-06-27 09:34:24 +10:00
Kiyan
e6c1b4cd5b chore(setup): make setup idempotent (#1340)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-06-26 12:14:03 +02:00
Alexander Courtis
0c13bd76a8 chore: update_root, sync_root_with_cwd, refactor with move_missing_val (#1359)
* chore: opts.update_focused_file.update_cwd -> update_root

* chore: opts.update_cwd -> sync_root_with_cwd

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val
2022-06-26 12:18:14 +10:00
lixvbnet
b299a877ad fix (#1363): use change_dir.fn instead of lib.open in M.change_root 2022-06-25 14:14:08 +10:00
Alexander Courtis
65beb55ac7 doc(#1368): more quit-on-last-window warnings 2022-06-25 12:43:14 +10:00
Alexander Courtis
0db63a350a doc: tidy help 2022-06-25 11:39:24 +10:00
Alexander Courtis
7160e68d5a doc: tidy help 2022-06-25 11:23:19 +10:00
litao91
79258f1d67 fix: window picker can't be correctly rendered when cmdheight = 0 (#1349)
Co-authored-by: litao <litao912002@hotmail.com>
2022-06-20 08:29:47 +02:00
Alexander Courtis
104292c8f9 chore: rename and simplify help update script 2022-06-20 11:54:14 +10:00
Alexander Courtis
6548287e8b feat: add cwd arg to open_replacing_current_buffer: retain existing valid buffer check 2022-06-20 09:52:56 +10:00
Kiyan
f262236107 chore: add matrix link to readme 2022-06-19 10:49:53 +02:00
kiyan
3bc2207f4a chore: simplify get_alt_or_next_buf 2022-06-19 09:51:40 +02:00
Alexander Courtis
d9aaa2f985 fix(#1356): view.close switch_buf_if_last_buf prefers alt buf (#1357) 2022-06-19 09:51:01 +02:00
javiertury
6b7b1b34fa feat: add cwd arg to open_replacing_current_buffer (#1348) 2022-06-19 13:59:35 +10:00
Alexander Courtis
1fc0eee946 fix(#1354): add missing :hi NvimTreeFileIgnored (#1358) 2022-06-18 10:42:12 +02:00
btstream
e82a921baa fix(view): prevent buffer override when actions.open_file.quit_on_open
* fix(view): prevent open buffer on NvimTree window when actions.open_file.quit_on_open is true

* fix(view): add view.quit_on_open function to prevent open on NvimTree window

Co-authored-by: btstream <btstream@gmail.com>
2022-06-18 17:07:24 +10:00
lixvbnet
b08003f546 feat: add NvimTreeFindFile!, root_dirs and prefer_startup_root 2022-06-18 15:32:56 +10:00
Alexander Courtis
84c2bd77ff docs: remove duplicate help tag 2022-06-18 14:18:37 +10:00
gegoune
aba394896b docs: lighten up readme and rework docs (#1327)
* docs: lighten up readme and rework docs

* docs: clean up mappings from readme + some other small changes

* docs: move sections around

* ci: remove readme parts of docs' autogen

* docs: discuss nvim-web-devicons and provide an example

* docs: add an example setup

* docs: fix opts scraping; try and make more macos compatible

* docs: add *nvim-tree* tag at start of help

* docs: add an example setup

* docs: update quick start to match readme

* docs: add basic commands

* docs: add g? hint

* docs: add :help links to readme

* docs: restore help wanted

* docs: add example screenshot

* docs: add features

* docs: add example screenshot

* docs: add features to help

* docs: clarify option functions

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-06-18 14:14:42 +10:00
kiyan
bdb6d4a254 fix(actions): reloading copy-paste logic inverted
fixes #1331
2022-06-11 11:22:45 +02:00
Alexander Courtis
9219831aa2 fix: .luarc.json globals/disables 2022-06-11 13:41:33 +10:00
kylo252
1caca62854 feat(view): ability to centralize view on entering (#1310) 2022-06-07 10:09:58 +10:00
lu5je0
821f050fda feat: full-name of node in floating window (#1305) 2022-06-06 11:15:03 +02:00
Raafat Turki
3c936c7cb6 feat(trash): default command 'trash' -> 'gio trash' (#1323) 2022-06-06 11:13:37 +02:00
Alexander Courtis
f6eef4a1f0 feat(explorer): add filesystem watchers: reinstate git output logging 2022-06-06 11:19:55 +10:00
Rammiah
6f6eab14dc feat(renderer): indent_markers add a item icon (#1321) 2022-06-05 14:19:19 +02:00
Kiyan
b0d27c09b6 feat(explorer): add filesystem watchers (#1304)
* feat(explorer): add experimental watchers

This commit introduces watchers to update the tree.
This behavior is introduced behind an "filesystem_watchers" option
which should prevent instabilities.
It will become the default at some point.

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-06-05 12:39:39 +02:00
Alexander Courtis
a5793f1edb doc: add help wanted and contributing (#1319) 2022-06-05 11:52:08 +02:00
kiyan
25921aa87a fix(open-file): do not open file when target winid is nil 2022-06-04 14:06:35 +02:00
kiyan
92ed3c487a fix(view): call get size after setting the height
fixes bottom and top placement for the tree
fixes #1311
2022-06-04 11:56:03 +02:00
Alexander Courtis
3aeb59b075 doc: setup call is only allowed once 2022-06-04 12:37:02 +10:00
kiyan
a0f705995a fix(view): grow condition for left or right was always true for right 2022-05-31 20:25:13 +02:00
Kiyan
2002b21be7 feat(resize): add ability to grow and shrink the tree when drawing (#1293)
Will adapt the view size based on the longest line.
fixes #865
2022-05-31 09:05:00 +02:00
kiyan
6b26628acf fix(preview): open file in preview should delete buffer when hidden
fixes #1307
2022-05-30 18:59:43 +02:00
Alexander Courtis
b1ecb75a6c feat: guard against multiple setup calls (#1308) 2022-05-30 12:46:56 +02:00
Alexander Courtis
8198fa01fc doc: default mappings 2022-05-30 12:53:28 +10:00
kiyan
5e900c2f29 refacto: tree explorer root should be absolute_path not cwd 2022-05-29 11:40:06 +02:00
emmanueltouzery
3806653d75 new option: close windows displaying removed files (#1300) 2022-05-29 11:23:01 +02:00
Kiyan
3a95c5a9cf feat(actions): expand all under folder (#1292) 2022-05-29 11:15:32 +02:00
Alexander Courtis
0373680819 #1301 nvim uses LuaJIT 2.1 -> lua 5.1 2022-05-29 12:29:40 +10:00
Alexander Courtis
c3b7be8d19 add .luarc.json for lua-language-server and fix a couple of nits (#1296) 2022-05-28 11:16:54 +02:00
Alexander Courtis
e482bad61c doc: tidy spacing and consistency 2022-05-28 15:45:41 +10:00
Alexander Courtis
84cb79e760 update auto-close answer 2022-05-28 12:13:50 +10:00
Alexander Courtis
3ba383d591 chore/remove globals (#1279)
* remove renderer globals: nvim_tree_add_trailing nvim_tree_highlight_opened_files nvim_tree_root_folder_modifier nvim_tree_special_files

* remove renderer globals: nvim_tree_icon_padding

* remove renderer globals: nvim_tree_symlink_arrow

* remove renderer globals: nvim_tree_show_icons, nvim_tree_show_icons

* remove renderer globals: nvim_tree_git_hl

* remove renderer globals: nvim_tree_group_empty

* remove renderer globals: respect_buf_cwd

* remove renderer globals: nvim_tree_create_in_closed_folder

* remove globals: consistency in legacy checks

* remove renderer globals: nvim_tree_special_files

* renderer.icons.symbols -> glyphs
2022-05-28 11:08:40 +10:00
bstaint
6abc87b1d9 feat: msys2 git support (#1295) 2022-05-26 15:22:42 +02:00
kiyan
540c811cb2 fix(open file): do not trigger buf enter event when setting target win
Fixes #1288
Also starts a refactoring of the open-file code, to make it easier to
debug and improve.
2022-05-26 13:37:50 +02:00
Taxo Rubio
b2ba6dea71 feat: optional path argument for NvimTreeToggle and NvimTreeFindFileToggle (#1276) 2022-05-21 13:31:14 +02:00
kiyan
73ab312820 refactor: simplify opening file in new tab
fixes #1271. Also fixes opening a file in new tab when close_on_open was
true.

This introduces breaking change since we don't do any extra behavior
and let the buffer be opened by the tree.
The previous behavior was a bit old and i believe this should've been
fixed by now.
Reference this commit if unexpected behavior appears while opening files
in new tabs from nvim-tree.
2022-05-21 12:09:09 +02:00
kiyan
9d6f4c184b chore: remove custom set local implementation
Seems vim.opt_local has been fixed.
see https://github.com/neovim/neovim/issues/14670
2022-05-21 11:36:07 +02:00
kiyan
17d5bd64e8 chore(config): auto resize the tree by default when opening a file.
config.open_file.auto_resize is now true by default.
Breaking change for default configurations.
See https://github.com/kyazdani42/nvim-tree.lua/issues/1275#issuecomment-1133515999
2022-05-21 11:27:49 +02:00
Michael
9563a11ce0 feat: reload explorer on buf enter (#1265) 2022-05-17 10:04:08 +02:00
Kiyan
6343813a35 feat(live-filter): add ability to live filter out nodes in the tree (#1056) 2022-05-17 10:03:49 +02:00
Alexander Courtis
99e32fea14 add fish performance tip to README.md 2022-05-17 13:03:59 +10:00
kiyan
9d26594b6c fix(renderer): empty space at end of line
fixes #1253
2022-05-15 10:28:17 +02:00
kiyan
7293f8dc70 fix(renderer): padding when git icons are after the name
fixes #1253
2022-05-14 13:41:58 +02:00
kiyan
d88d12f5bc Revert "#1253 only pad git icons when they are present (#1259)"
This reverts commit 90d7b8edb1.
fixes #1267
2022-05-14 13:31:37 +02:00
muro3r
aefa66c04d feat: extension sorter (#1181) (#1264) 2022-05-14 10:54:01 +02:00
Kiyan
f8312cd06f feat(renderer): add ability to set git icons in signcolumn (#1242) 2022-05-14 09:54:27 +02:00
Michael
46014449b6 refactor: use lua api for user commands and autocommands (#1206)
BREAKING: plugin now requires nvim-0.7
2022-05-14 09:49:45 +02:00
84 changed files with 6471 additions and 3077 deletions

View File

@@ -5,7 +5,14 @@ body:
- type: markdown
attributes:
value: |
Before reporting: search [existing issues](https://github.com/kyazdani42/nvim-tree.lua/issues) and make sure that nvim-tree is updated to the latest version. If you are experiencing performance issues, please [enable profiling](https://github.com/kyazdani42/nvim-tree.lua#performance-issues) and attach the logs.
Is this a question?
* Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a bug.
Before reporting:
* search [existing issues](https://github.com/nvim-tree/nvim-tree.lua/issues)
* ensure that nvim-tree is updated to the latest version
If you are experiencing performance issues, please [enable profiling](https://github.com/nvim-tree/nvim-tree.lua#performance-issues) and attach the logs.
- type: textarea
attributes:
label: "Description"
@@ -15,7 +22,7 @@ body:
- type: textarea
attributes:
label: "Neovim version"
description: "Output of `nvim --version`. Please see nvim-tree.lua [minimum required version](https://github.com/kyazdani42/nvim-tree.lua#notice)."
description: "Output of `nvim --version`. Please see nvim-tree.lua [minimum required version](https://github.com/nvim-tree/nvim-tree.lua#notice)."
placeholder: |
NVIM v0.6.1
Build type&#58 Release
@@ -42,7 +49,7 @@ body:
label: "Minimal config"
description: "Minimal(!) configuration necessary to reproduce the issue.
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/kyazdani42/nvim-tree.lua/master/.github/ISSUE_TEMPLATE/nvt-min.lua) to `/tmp` and run using `nvim -nu /tmp/nvt-min.lua`
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/nvim-tree/nvim-tree.lua/master/.github/ISSUE_TEMPLATE/nvt-min.lua) to `/tmp` and run using `nvim -nu /tmp/nvt-min.lua`
If _absolutely_ necessary, add plugins and modify the nvim-tree setup at the indicated lines.

View File

@@ -6,6 +6,12 @@ labels: feature request
assignees: ''
---
**Is this a question?**
Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a feature request.
**Can this functionality be implemented utilising API?**
nvim-tree exposes extensive API (see `:h nvim-tree-api`). Can it be used to achieve your goal? Is there a missing API that would make it possible?
Given stable status of nvim-tree it's preferred to add new API than new functionality.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

View File

@@ -6,8 +6,8 @@ local function load_plugins()
require("packer").startup {
{
"wbthomason/packer.nvim",
"kyazdani42/nvim-tree.lua",
"kyazdani42/nvim-web-devicons",
"nvim-tree/nvim-tree.lua",
"nvim-tree/nvim-web-devicons",
-- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
},
config = {

BIN
.github/screenshot.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

View File

@@ -28,7 +28,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: JohnnyMorganz/stylua-action@1.0.0
- uses: JohnnyMorganz/stylua-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: 0.15.1
args: --color always --check lua/

19
.luarc.json Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"runtime.version" : "Lua 5.1",
"diagnostics": {
"globals": [
"vim"
],
"disable": [
"cast-local-type",
"lowercase-global",
"missing-parameter",
"missing-return",
"missing-return-value",
"need-check-nil",
"param-type-mismatch"
]
}
}

View File

@@ -12,13 +12,22 @@ luarocks install luacheck
cargo install stylua
```
You can setup the git hooks by running `scripts/setup-hooks.sh`.
## Adding new actions
To add a new action, add a file in `actions/name-of-the-action.lua`. You should export a `setup` function if some configuration is needed.
Once you did, you should run the `scripts/update-help.sh`.
## Documentation
When adding new options, you should declare the defaults in the main `nvim-tree.lua` file.
Once you did, you should run the `update-default-opts.sh` script which will update the default documentation in the README and the help file.
Once you did, you should run the `scripts/update-help.sh`.
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file.
## Pull Request
Please reference any issues in the description e.g. "resolves #1234".
Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks.

439
README.md
View File

@@ -1,392 +1,175 @@
# A File Explorer For Neovim Written In Lua
[![CI](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml)
[![CI](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml)
## Notice
<img align="left" width="149" height="484" src="https://user-images.githubusercontent.com/17254073/195207026-f3434ba1-dc86-4c48-8ab3-b2efc3b85227.png">
<img align="left" width="149" height="484" src="https://user-images.githubusercontent.com/17254073/195207023-7b709e35-7f10-416b-aafb-5bb61268c7d3.png">
This plugin requires [neovim >=0.6.0](https://github.com/neovim/neovim/wiki/Installing-Neovim).
Automatic updates
If you have issues since the recent setup migration, check out [this guide](https://github.com/kyazdani42/nvim-tree.lua/issues/674)
File type icons
Git integration
Diagnostics integration: LSP and COC
(Live) filtering
Cut, copy, paste, rename, delete, create
Highly customisable
<br clear="left"/>
<br />
Take a look at the [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki) for Showcases, Tips, Recipes and more.
[Join us on matrix](https://matrix.to/#/#nvim-tree:matrix.org)
## Requirements
[neovim >=0.7.0](https://github.com/neovim/neovim/wiki/Installing-Neovim)
[nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional and used to display file icons. It requires a [patched font](https://www.nerdfonts.com/). Your terminal emulator must be configured to use that font, usually "Hack Nerd Font"
## Install
Install with [vim-plug](https://github.com/junegunn/vim-plug):
```vim
" requires
Plug 'kyazdani42/nvim-web-devicons' " for file icons
Plug 'kyazdani42/nvim-tree.lua'
Plug 'nvim-tree/nvim-web-devicons' " optional, for file icons
Plug 'nvim-tree/nvim-tree.lua'
```
Install with [packer](https://github.com/wbthomason/packer.nvim):
or with [packer](https://github.com/wbthomason/packer.nvim):
```lua
use {
'kyazdani42/nvim-tree.lua',
requires = {
'kyazdani42/nvim-web-devicons', -- optional, for file icon
},
tag = 'nightly' -- optional, updated every week. (see issue #1193)
'nvim-tree/nvim-tree.lua',
requires = {
'nvim-tree/nvim-web-devicons', -- optional, for file icons
},
tag = 'nightly' -- optional, updated every week. (see issue #1193)
}
```
## Setup
Options are currently being migrated into the setup function, you need to run `require'nvim-tree'.setup()` in your personal configurations.
Setup should be run in a lua file or in a lua heredoc (`:help lua-heredoc`) if using in a vim file.
Note that options under the `g:` command should be set **BEFORE** running the setup function.
These are being migrated to the setup function incrementally, check [this issue](https://github.com/kyazdani42/nvim-tree.lua/issues/674) if you encounter any problems related to configs not working after update.
```vim
" vimrc
let g:nvim_tree_git_hl = 1 "0 by default, will enable file highlight for git attributes (can be used without the icons).
let g:nvim_tree_highlight_opened_files = 1 "0 by default, will enable folder and file icon highlight for opened files/directories.
let g:nvim_tree_root_folder_modifier = ':~' "This is the default. See :help filename-modifiers for more options
let g:nvim_tree_add_trailing = 1 "0 by default, append a trailing slash to folder names
let g:nvim_tree_group_empty = 1 " 0 by default, compact folders that only contain a single folder into one node in the file tree
let g:nvim_tree_icon_padding = ' ' "one space by default, used for rendering the space between the icon and the filename. Use with caution, it could break rendering if you set an empty string depending on your font.
let g:nvim_tree_symlink_arrow = ' >> ' " defaults to ' ➛ '. used as a separator between symlinks' source and target.
let g:nvim_tree_respect_buf_cwd = 1 "0 by default, will change cwd of nvim-tree to that of new buffer's when opening nvim-tree.
let g:nvim_tree_create_in_closed_folder = 1 "0 by default, When creating files, sets the path of a file when cursor is on a closed folder to the parent folder when 0, and inside the folder when 1.
let g:nvim_tree_special_files = { 'README.md': 1, 'Makefile': 1, 'MAKEFILE': 1 } " List of filenames that gets highlighted with NvimTreeSpecialFile
let g:nvim_tree_show_icons = {
\ 'git': 1,
\ 'folders': 0,
\ 'files': 0,
\ 'folder_arrows': 0,
\ }
"If 0, do not show the icons for one of 'git' 'folder' and 'files'
"1 by default, notice that if 'files' is 1, it will only display
"if nvim-web-devicons is installed and on your runtimepath.
"if folder is 1, you can also tell folder_arrows 1 to show small arrows next to the folder icons.
"but this will not work when you set renderer.indent_markers.enable (because of UI conflict)
" default will show icon by default if no icon is provided
" default shows no icon by default
let g:nvim_tree_icons = {
\ 'default': "",
\ 'symlink': "",
\ 'git': {
\ 'unstaged': "✗",
\ 'staged': "✓",
\ 'unmerged': "",
\ 'renamed': "➜",
\ 'untracked': "★",
\ 'deleted': "",
\ 'ignored': "◌"
\ },
\ 'folder': {
\ 'arrow_open': "",
\ 'arrow_closed': "",
\ 'default': "",
\ 'open': "",
\ 'empty': "",
\ 'empty_open': "",
\ 'symlink': "",
\ 'symlink_open': "",
\ }
\ }
nnoremap <C-n> :NvimTreeToggle<CR>
nnoremap <leader>r :NvimTreeRefresh<CR>
nnoremap <leader>n :NvimTreeFindFile<CR>
" More available functions:
" NvimTreeOpen
" NvimTreeClose
" NvimTreeFocus
" NvimTreeFindFileToggle
" NvimTreeResize
" NvimTreeCollapse
" NvimTreeCollapseKeepBuffers
set termguicolors " this variable must be enabled for colors to be applied properly
" a list of groups can be found at `:help nvim_tree_highlight`
highlight NvimTreeFolderIcon guibg=blue
```
Setup should be run in a lua file or in a lua heredoc [:help lua-heredoc](https://neovim.io/doc/user/lua.html) if using in a vim file.
```lua
-- init.lua
-- examples for your init.lua
-- empty setup using defaults: add your own options
require'nvim-tree'.setup {
}
-- disable netrw at the very start of your init.lua (strongly advised)
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
-- OR
-- set termguicolors to enable highlight groups
vim.opt.termguicolors = true
-- setup with all defaults
-- each of these are documented in `:help nvim-tree.OPTION_NAME`
-- nested options are documented by accessing them with `.` (eg: `:help nvim-tree.view.mappings.list`).
require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
auto_reload_on_write = true,
disable_netrw = false,
hijack_cursor = false,
hijack_netrw = true,
hijack_unnamed_buffer_when_opening = false,
ignore_buffer_on_setup = false,
open_on_setup = false,
open_on_setup_file = false,
open_on_tab = false,
sort_by = "name",
update_cwd = false,
-- empty setup using defaults
require("nvim-tree").setup()
-- OR setup with some options
require("nvim-tree").setup({
sort_by = "case_sensitive",
view = {
width = 30,
height = 30,
hide_root_folder = false,
side = "left",
preserve_window_proportions = false,
number = false,
relativenumber = false,
signcolumn = "yes",
adaptive_size = true,
mappings = {
custom_only = false,
list = {
-- user mappings go here
{ key = "u", action = "dir_up" },
},
},
},
renderer = {
indent_markers = {
enable = false,
icons = {
corner = "",
edge = "",
none = " ",
},
},
icons = {
webdev_colors = true,
git_placement = "before",
}
},
hijack_directories = {
enable = true,
auto_open = true,
},
update_focused_file = {
enable = false,
update_cwd = false,
ignore_list = {},
},
ignore_ft_on_setup = {},
system_open = {
cmd = "",
args = {},
},
diagnostics = {
enable = false,
show_on_dirs = false,
icons = {
hint = "",
info = "",
warning = "",
error = "",
},
group_empty = true,
},
filters = {
dotfiles = false,
custom = {},
exclude = {},
dotfiles = true,
},
git = {
enable = true,
ignore = true,
timeout = 400,
},
actions = {
use_system_clipboard = true,
change_dir = {
enable = true,
global = false,
restrict_above_cwd = false,
},
open_file = {
quit_on_open = false,
resize_window = false,
window_picker = {
enable = true,
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
exclude = {
filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" },
buftype = { "nofile", "terminal", "help" },
},
},
},
},
trash = {
cmd = "trash",
require_confirm = true,
},
log = {
enable = false,
truncate = false,
types = {
all = false,
config = false,
copy_paste = false,
diagnostics = false,
git = false,
profile = false,
},
},
} -- END_DEFAULT_OPTS
})
```
## Key Bindings
For complete list of available configuration options see [:help nvim-tree-setup](doc/nvim-tree-lua.txt)
### Default actions
Each option is documented in `:help nvim-tree.OPTION_NAME`. Nested options can be accessed by appending `.`, for example [:help nvim-tree.view.mappings](doc/nvim-tree-lua.txt)
- `<CR>` or `o` on the root folder will cd in the above directory
- `<C-]>` will cd in the directory under the cursor
- `<BS>` will close current opened directory or parent
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
> you can add multiple directories by doing foo/bar/baz/f and it will add foo bar and baz directories and f as a file
- type `r` to rename a file
- type `<C-r>` to rename a file and omit the filename on input
- type `x` to add/remove file/directory to cut clipboard
- type `c` to add/remove file/directory to copy clipboard
- type `y` will copy name to system clipboard
- type `Y` will copy relative path to system clipboard
- type `gy` will copy absolute path to system clipboard
- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation)
- type `d` to delete a file (will prompt for confirmation)
- type `D` to trash a file (configured in setup())
- type `]c` to go to next git item
- type `[c` to go to prev git item
- type `-` to navigate up to the parent directory of the current file/directory
- type `s` to open a file with default system application or a folder with default file manager (if you want to change the command used to do it see `:h nvim-tree.setup` under `system_open`)
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)
- `<C-v>` will open the file in a vertical split
- `<C-x>` will open the file in a horizontal split
- `<C-t>` will open the file in a new tab
- `<Tab>` will open the file as a preview (keeps the cursor in the tree)
- `I` will toggle visibility of hidden folders / files
- `H` will toggle visibility of dotfiles (files/folders starting with a `.`)
- `R` will refresh the tree
- Double left click acts like `<CR>`
- Double right click acts like `<C-]>`
- `W` will collapse the whole tree
- `S` will prompt the user to enter a path and then expands the tree to match the path
- `.` will enter vim command mode with the file the cursor is on
- `C-k` will toggle a popup with file infos about the file under the cursor
## Commands
### Settings
See [:help nvim-tree-commands](doc/nvim-tree-lua.txt)
The `list` option in `view.mappings.list` is a table of
```lua
-- key can be either a string or a table of string (lhs)
-- action is the name of the action, set to `""` to remove default action
-- action_cb is the function that will be called, it receives the node as a parameter. Optional for default actions
-- mode is normal by default
Basic commands:
local tree_cb = require'nvim-tree.config'.nvim_tree_callback
`:NvimTreeToggle` Open or close the tree. Takes an optional path argument.
local function print_node_path(node) {
print(node.absolute_path)
}
`:NvimTreeFocus` Open the tree if it is closed, and then focus on the tree.
local list = {
{ key = {"<CR>", "o" }, action = "edit", mode = "n"},
{ key = "p", action = "print_path", action_cb = print_node_path },
{ key = "s", cb = tree_cb("vsplit") }, --tree_cb and the cb property are deprecated
{ key = "<2-RightMouse>", action = "" }, -- will remove default cd action
}
```
`:NvimTreeFindFile` Move the cursor in the tree for the current buffer, opening folders if needed.
These are the default bindings:
```lua
`:NvimTreeCollapse` Collapses the nvim-tree recursively.
-- default mappings
local list = {
{ key = {"<CR>", "o", "<2-LeftMouse>"}, action = "edit" },
{ key = "<C-e>", action = "edit_in_place" },
{ key = {"O"}, action = "edit_no_picker" },
{ key = {"<2-RightMouse>", "<C-]>"}, action = "cd" },
{ key = "<C-v>", action = "vsplit" },
{ key = "<C-x>", action = "split" },
{ key = "<C-t>", action = "tabnew" },
{ key = "<", action = "prev_sibling" },
{ key = ">", action = "next_sibling" },
{ key = "P", action = "parent_node" },
{ key = "<BS>", action = "close_node" },
{ key = "<Tab>", action = "preview" },
{ key = "K", action = "first_sibling" },
{ key = "J", action = "last_sibling" },
{ key = "I", action = "toggle_git_ignored" },
{ key = "H", action = "toggle_dotfiles" },
{ key = "R", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "D", action = "trash" },
{ key = "r", action = "rename" },
{ key = "<C-r>", action = "full_rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "y", action = "copy_name" },
{ key = "Y", action = "copy_path" },
{ key = "gy", action = "copy_absolute_path" },
{ key = "[c", action = "prev_git_item" },
{ key = "]c", action = "next_git_item" },
{ key = "-", action = "dir_up" },
{ key = "s", action = "system_open" },
{ key = "q", action = "close" },
{ key = "g?", action = "toggle_help" },
{ key = "W", action = "collapse_all" },
{ key = "S", action = "search_node" },
{ key = "<C-k>", action = "toggle_file_info" },
{ key = ".", action = "run_file_command" }
}
```
## Mappings
You can toggle the help UI by pressing `g?`.
nvim-tree comes with number of mappings; for default mappings please see [:help nvim-tree-default-mappings](doc/nvim-tree-lua.txt), for way of configuring mappings see [:help nvim-tree-mappings](doc/nvim-tree-lua.txt)
## Tips & reminders
`g?` toggles help, showing all the mappings and their actions.
1. You can add a directory by adding a `/` at the end of the paths, entering multiple directories `BASE/foo/bar/baz` will add directory foo, then bar and add a file baz to it.
2. You can update window options for the tree by setting `require"nvim-tree.view".View.winopts.MY_OPTION = MY_OPTION_VALUE`
3. `toggle` has a second parameter which allows to toggle without focusing the explorer (`require"nvim-tree".toggle(false, true)`).
4. You can allow nvim-tree to behave like vinegar (see `:help nvim-tree-vinegar`).
5. If you `:set nosplitright`, the files will open on the left side of the tree, placing the tree window in the right side of the file you opened.
6. You can automatically close the tab/vim when nvim-tree is the last window in the tab. WARNING: other plugins or automation may interfere with this:
```vim
autocmd BufEnter * ++nested if winnr('$') == 1 && bufname() == 'NvimTree_' . tabpagenr() | quit | endif
```
## Roadmap
## Diagnostic Logging
nvim-tree is stable and new major features will not be added. The focus is on existing user experience.
You may enable diagnostic logging to `$XDG_CACHE_HOME/nvim/nvim-tree.log`. See `:help nvim-tree.log`.
Users are encouraged to add their own custom features via the public [API](#api).
## Performance Issues
Development is focused on:
* Bug fixes
* Performance
* Quality of Life improvements
* API / Events
* Enhancements to existing features
If you are experiencing performance issues with nvim-tree.lua, you can enable profiling in the logs. It is advisable to enable git logging at the same time, as that can be a source of performance problems.
## API
```lua
log = {
enable = true,
truncate = true,
types = {
git = true,
profile = true,
},
},
```
nvim-tree exposes a public API. This is non breaking, with additions made as necessary.
Please attach `$XDG_CACHE_HOME/nvim/nvim-tree.log` if you raise an issue.
Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficent for your needs. [Contributions](#Contributing) are always welcome.
*Performance Tips:*
[:help nvim-tree-api](doc/nvim-tree-lua.txt)
* If you are using fish as an editor shell (which might be fixed in the future), try set `shell=/bin/bash` in your vim config.
### Events
* Try manually running the git command (see the logs) in your shell e.g. `git --no-optional-locks status --porcelain=v1 --ignored=matching -u`.
Users may subscribe to events that nvim-tree will dispatch in a variety of situations.
* Huge git repositories may timeout after the default `git.timeout` of 400ms. Try increasing that in your setup if you see `[git] job timed out` in the logs.
[:help nvim-tree-events](doc/nvim-tree-lua.txt)
* Try temporarily disabling git integration by setting `git.enable = false` in your setup.
### Actions
Custom actions may be mapped which can invoke API or perform your own actions.
[:help nvim-tree-mappings](doc/nvim-tree-lua.txt)
## Contributing
PRs are always welcome. See [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started.
See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started.
### Help Wanted
Developers with the following environments:
* Apple macOS
* Windows
* WSL
* msys
* powershell
Help triaging, diagnosing and fixing issues specific to those environments is needed, as the nvim-tree developers do not have access to or expertise in these environments.
Let us know you're interested by commenting on issues and raising PRs.
## Screenshots
![alt text](.github/screenshot.png?raw=true "kyazdani42 tree")
![alt text](.github/screenshot2.png?raw=true "akin909 tree")
![alt text](.github/screenshot3.png?raw=true "stsewd tree")
![alt text](.github/screenshot4.png?raw=true "reyhankaplan tree")
See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources.
Please add your own!

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,82 @@
local luv = vim.loop
local api = vim.api
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local colors = require "nvim-tree.colors"
local renderer = require "nvim-tree.renderer"
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local change_dir = require "nvim-tree.actions.change-dir"
local change_dir = require "nvim-tree.actions.root.change-dir"
local legacy = require "nvim-tree.legacy"
local core = require "nvim-tree.core"
local reloaders = require "nvim-tree.actions.reloaders.reloaders"
local copy_paste = require "nvim-tree.actions.fs.copy-paste"
local collapse_all = require "nvim-tree.actions.tree-modifiers.collapse-all"
local git = require "nvim-tree.git"
local filters = require "nvim-tree.explorer.filters"
local _config = {}
local M = {}
local M = {
setup_called = false,
init_root = "",
}
function M.focus()
M.open()
view.focus()
end
---@deprecated
M.on_keypress = require("nvim-tree.actions").on_keypress
function M.change_root(filepath, bufnr)
-- skip if current file is in ignore_list
local ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or ""
for _, value in pairs(_config.update_focused_file.ignore_list) do
if utils.str_find(filepath, value) or utils.str_find(ft, value) then
return
end
end
function M.toggle(find_file, no_focus)
local cwd = core.get_cwd()
local vim_cwd = vim.fn.getcwd()
-- test if in vim_cwd
if utils.path_relative(filepath, vim_cwd) ~= filepath then
if vim_cwd ~= cwd then
change_dir.fn(vim_cwd)
end
return
end
-- test if in cwd
if utils.path_relative(filepath, cwd) ~= filepath then
return
end
-- otherwise test M.init_root
if _config.prefer_startup_root and utils.path_relative(filepath, M.init_root) ~= filepath then
change_dir.fn(M.init_root)
return
end
-- otherwise root_dirs
for _, dir in pairs(_config.root_dirs) do
dir = vim.fn.fnamemodify(dir, ":p")
if utils.path_relative(filepath, dir) ~= filepath then
change_dir.fn(dir)
return
end
end
-- finally fall back to the folder containing the file
change_dir.fn(vim.fn.fnamemodify(filepath, ":p:h"))
end
---@deprecated
M.on_keypress = require("nvim-tree.actions.dispatch").dispatch
function M.toggle(find_file, no_focus, cwd, bang)
if view.is_visible() then
view.close()
else
local previous_buf = api.nvim_get_current_buf()
M.open()
local previous_buf = vim.api.nvim_get_current_buf()
M.open(cwd)
if _config.update_focused_file.enable or find_file then
M.find_file(false, previous_buf)
M.find_file(false, previous_buf, bang)
end
if no_focus then
vim.cmd "noautocmd wincmd p"
@@ -48,31 +94,37 @@ function M.open(cwd)
end
end
function M.open_replacing_current_buffer()
function M.open_replacing_current_buffer(cwd)
if view.is_visible() then
return
end
local buf = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(buf)
local buf = vim.api.nvim_get_current_buf()
local bufname = vim.api.nvim_buf_get_name(buf)
if bufname == "" or vim.loop.fs_stat(bufname) == nil then
return
end
local cwd = vim.fn.fnamemodify(bufname, ":p:h")
if cwd == "" or cwd == nil then
cwd = vim.fn.fnamemodify(bufname, ":p:h")
end
if not core.get_explorer() or cwd ~= core.get_cwd() then
core.init(cwd)
end
view.open_in_current_win { hijack_current_buf = false, resize = false }
require("nvim-tree.renderer").draw()
require("nvim-tree.actions.find-file").fn(bufname)
require("nvim-tree.actions.finders.find-file").fn(bufname)
end
function M.tab_change()
function M.tab_enter()
if view.is_visible { any_tabpage = true } then
local bufname = vim.api.nvim_buf_get_name(0)
if bufname:match "Neogit" ~= nil or bufname:match "--graph" ~= nil then
return
local ft = vim.api.nvim_buf_get_option(0, "ft")
for _, filter in ipairs(M.config.tab.sync.ignore) do
if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then
return
end
end
view.open { focus_tree = false }
require("nvim-tree.renderer").draw()
@@ -81,36 +133,26 @@ end
local function find_existing_windows()
return vim.tbl_filter(function(win)
local buf = api.nvim_win_get_buf(win)
return api.nvim_buf_get_name(buf):match "NvimTree" ~= nil
end, api.nvim_list_wins())
local buf = vim.api.nvim_win_get_buf(win)
return vim.api.nvim_buf_get_name(buf):match "NvimTree" ~= nil
end, vim.api.nvim_list_wins())
end
local function is_file_readable(fname)
local stat = luv.fs_stat(fname)
return stat and stat.type == "file" and luv.fs_access(fname, "R")
local stat = vim.loop.fs_stat(fname)
return stat and stat.type == "file" and vim.loop.fs_access(fname, "R")
end
local function update_base_dir_with_filepath(filepath, bufnr)
local ft = api.nvim_buf_get_option(bufnr, "filetype") or ""
for _, value in pairs(_config.update_focused_file.ignore_list) do
if utils.str_find(filepath, value) or utils.str_find(ft, value) then
return
end
end
if not vim.startswith(filepath, core.get_explorer().cwd) then
change_dir.fn(vim.fn.fnamemodify(filepath, ":p:h"))
end
end
function M.find_file(with_open, bufnr)
function M.find_file(with_open, bufnr, bang)
if not with_open and not core.get_explorer() then
return
end
bufnr = bufnr or api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
if not vim.api.nvim_buf_is_valid(bufnr) then
return
end
local bufname = vim.api.nvim_buf_get_name(bufnr)
local filepath = utils.canonical_path(vim.fn.fnamemodify(bufname, ":p"))
if not is_file_readable(filepath) then
return
@@ -120,12 +162,12 @@ function M.find_file(with_open, bufnr)
M.open()
end
-- if we don't schedule, it will search for NvimTree
vim.schedule(function()
-- if we don't schedule, it will search for NvimTree
if _config.update_focused_file.update_cwd then
update_base_dir_with_filepath(filepath, bufnr)
if bang or _config.update_focused_file.update_root then
M.change_root(filepath, bufnr)
end
require("nvim-tree.actions.find-file").fn(filepath)
require("nvim-tree.actions.finders.find-file").fn(filepath)
end)
end
@@ -137,8 +179,8 @@ function M.open_on_directory()
return
end
local buf = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(buf)
local buf = vim.api.nvim_get_current_buf()
local bufname = vim.api.nvim_buf_get_name(buf)
if vim.fn.isdirectory(bufname) ~= 1 then
return
end
@@ -148,6 +190,7 @@ end
function M.reset_highlight()
colors.setup()
view.reset_winhl()
renderer.render_hl(view.get_bufnr())
end
@@ -164,32 +207,32 @@ function M.place_cursor_on_node()
return
end
local line = api.nvim_get_current_line()
local cursor = api.nvim_win_get_cursor(0)
local line = vim.api.nvim_get_current_line()
local cursor = vim.api.nvim_win_get_cursor(0)
local idx = vim.fn.stridx(line, node.name)
if idx >= 0 then
api.nvim_win_set_cursor(0, { cursor[1], idx })
vim.api.nvim_win_set_cursor(0, { cursor[1], idx })
end
end
function M.on_enter(netrw_disabled)
local bufnr = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
local buftype = api.nvim_buf_get_option(bufnr, "filetype")
local bufnr = vim.api.nvim_get_current_buf()
local bufname = vim.api.nvim_buf_get_name(bufnr)
local buftype = vim.api.nvim_buf_get_option(bufnr, "filetype")
local ft_ignore = _config.ignore_ft_on_setup
local stats = luv.fs_stat(bufname)
local stats = vim.loop.fs_stat(bufname)
local is_dir = stats and stats.type == "directory"
local is_file = stats and stats.type == "file"
local cwd
if is_dir then
cwd = vim.fn.expand(bufname)
cwd = vim.fn.expand(vim.fn.fnameescape(bufname))
-- INFO: could potentially conflict with rooter plugins
vim.cmd("noautocmd cd " .. cwd)
vim.cmd("noautocmd cd " .. vim.fn.fnameescape(cwd))
end
local lines = not is_dir and api.nvim_buf_get_lines(bufnr, 0, -1, false) or {}
local lines = not is_dir and vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) or {}
local buf_has_content = #lines > 1 or (#lines == 1 and lines[1] ~= "")
local buf_is_dir = is_dir and netrw_disabled
@@ -220,7 +263,7 @@ function M.on_enter(netrw_disabled)
-- Session that left a NvimTree Buffer opened, reopen with it
local existing_tree_wins = find_existing_windows()
if existing_tree_wins[1] then
api.nvim_set_current_win(existing_tree_wins[1])
vim.api.nvim_set_current_win(existing_tree_wins[1])
end
if should_open or should_hijack or existing_tree_wins[1] ~= nil then
@@ -252,19 +295,29 @@ local function manage_netrw(disable_netrw, hijack_netrw)
end
local function setup_vim_commands()
vim.cmd [[
command! -nargs=? -complete=dir NvimTreeOpen lua require'nvim-tree'.open("<args>")
command! NvimTreeClose lua require'nvim-tree.view'.close()
command! NvimTreeToggle lua require'nvim-tree'.toggle(false)
command! NvimTreeFocus lua require'nvim-tree'.focus()
command! NvimTreeRefresh lua require'nvim-tree.actions.reloaders'.reload_explorer()
command! NvimTreeClipboard lua require'nvim-tree.actions.copy-paste'.print_clipboard()
command! NvimTreeFindFile lua require'nvim-tree'.find_file(true)
command! NvimTreeFindFileToggle lua require'nvim-tree'.toggle(true)
command! -nargs=1 NvimTreeResize lua require'nvim-tree'.resize("<args>")
command! NvimTreeCollapse lua require'nvim-tree.actions.collapse-all'.fn()
command! NvimTreeCollapseKeepBuffers lua require'nvim-tree.actions.collapse-all'.fn(true)
]]
vim.api.nvim_create_user_command("NvimTreeOpen", function(res)
M.open(res.args)
end, { nargs = "?", complete = "dir" })
vim.api.nvim_create_user_command("NvimTreeClose", view.close, { bar = true })
vim.api.nvim_create_user_command("NvimTreeToggle", function(res)
M.toggle(false, false, res.args)
end, { nargs = "?", complete = "dir" })
vim.api.nvim_create_user_command("NvimTreeFocus", M.focus, { bar = true })
vim.api.nvim_create_user_command("NvimTreeRefresh", reloaders.reload_explorer, { bar = true })
vim.api.nvim_create_user_command("NvimTreeClipboard", copy_paste.print_clipboard, { bar = true })
vim.api.nvim_create_user_command("NvimTreeFindFile", function(res)
M.find_file(true, nil, res.bang)
end, { bang = true, bar = true })
vim.api.nvim_create_user_command("NvimTreeFindFileToggle", function(res)
M.toggle(true, false, res.args, res.bang)
end, { bang = true, nargs = "?", complete = "dir" })
vim.api.nvim_create_user_command("NvimTreeResize", function(res)
M.resize(res.args)
end, { nargs = 1, bar = true })
vim.api.nvim_create_user_command("NvimTreeCollapse", collapse_all.fn, { bar = true })
vim.api.nvim_create_user_command("NvimTreeCollapseKeepBuffers", function()
collapse_all.fn(true)
end, { bar = true })
end
function M.change_dir(name)
@@ -276,40 +329,136 @@ function M.change_dir(name)
end
local function setup_autocommands(opts)
vim.cmd "augroup NvimTree"
vim.cmd "autocmd!"
local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true })
local function create_nvim_tree_autocmd(name, custom_opts)
local default_opts = { group = augroup_id }
vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
end
-- reset highlights when colorscheme is changed
vim.cmd "au ColorScheme * lua require'nvim-tree'.reset_highlight()"
if opts.auto_reload_on_write then
vim.cmd "au BufWritePost * lua require'nvim-tree.actions.reloaders'.reload_explorer()"
end
vim.cmd "au User FugitiveChanged,NeogitStatusRefreshed lua require'nvim-tree.actions.reloaders'.reload_git()"
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight })
if opts.open_on_tab then
vim.cmd "au TabEnter * lua require'nvim-tree'.tab_change()"
-- prevent new opened file from opening in the same window as nvim-tree
create_nvim_tree_autocmd("BufWipeout", {
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
view._prevent_buffer_override()
end
end,
})
local has_watchers = opts.filesystem_watchers.enable
if opts.auto_reload_on_write and not has_watchers then
create_nvim_tree_autocmd("BufWritePost", { callback = reloaders.reload_explorer })
end
create_nvim_tree_autocmd("BufReadPost", {
callback = function()
if filters.config.filter_no_buffer then
reloaders.reload_explorer()
end
end,
})
create_nvim_tree_autocmd("BufUnload", {
callback = function(data)
if filters.config.filter_no_buffer then
reloaders.reload_explorer(nil, data.buf)
end
end,
})
if not has_watchers and opts.git.enable then
create_nvim_tree_autocmd("User", {
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
callback = reloaders.reload_git,
})
end
if opts.tab.sync.open then
create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) })
end
if opts.hijack_cursor then
vim.cmd "au CursorMoved NvimTree_* lua require'nvim-tree'.place_cursor_on_node()"
create_nvim_tree_autocmd("CursorMoved", {
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
M.place_cursor_on_node()
end
end,
})
end
if opts.update_cwd then
vim.cmd "au DirChanged * lua require'nvim-tree'.change_dir(vim.loop.cwd())"
if opts.sync_root_with_cwd then
create_nvim_tree_autocmd("DirChanged", {
callback = function()
M.change_dir(vim.loop.cwd())
end,
})
end
if opts.update_focused_file.enable then
vim.cmd "au BufEnter * lua require'nvim-tree'.find_file(false)"
end
if not opts.actions.open_file.quit_on_open then
vim.cmd "au BufWipeout NvimTree_* lua require'nvim-tree.view'._prevent_buffer_override()"
else
vim.cmd "au BufWipeout NvimTree_* lua require'nvim-tree.view'.abandon_current_window()"
create_nvim_tree_autocmd("BufEnter", {
callback = function()
M.find_file(false)
end,
})
end
if opts.hijack_directories.enable then
vim.cmd "au BufEnter,BufNewFile * lua require'nvim-tree'.open_on_directory()"
create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory })
end
vim.cmd "augroup end"
if opts.reload_on_bufenter and not has_watchers then
create_nvim_tree_autocmd("BufEnter", {
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
reloaders.reload_explorer()
end
end,
})
end
if opts.view.centralize_selection then
create_nvim_tree_autocmd("BufEnter", {
pattern = "NvimTree_*",
callback = function()
vim.schedule(function()
vim.api.nvim_buf_call(0, function()
vim.cmd [[norm! zz]]
end)
end)
end,
})
end
if opts.diagnostics.enable then
create_nvim_tree_autocmd("DiagnosticChanged", {
callback = function()
log.line("diagnostics", "DiagnosticChanged")
require("nvim-tree.diagnostics").update()
end,
})
create_nvim_tree_autocmd("User", {
pattern = "CocDiagnosticChange",
callback = function()
log.line("diagnostics", "CocDiagnosticChange")
require("nvim-tree.diagnostics").update()
end,
})
end
if opts.view.float.enable and opts.view.float.quit_on_focus_loss then
create_nvim_tree_autocmd("WinLeave", {
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
view.close()
end
end,
})
end
end
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
@@ -321,12 +470,19 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
ignore_buffer_on_setup = false,
open_on_setup = false,
open_on_setup_file = false,
open_on_tab = false,
sort_by = "name",
update_cwd = false,
root_dirs = {},
prefer_startup_root = false,
sync_root_with_cwd = false,
reload_on_bufenter = false,
respect_buf_cwd = false,
on_attach = "disable",
remove_keymaps = false,
select_prompts = false,
view = {
adaptive_size = false,
centralize_selection = false,
width = 30,
height = 30,
hide_root_folder = false,
side = "left",
preserve_window_proportions = false,
@@ -339,20 +495,76 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
-- user mappings go here
},
},
float = {
enable = false,
quit_on_focus_loss = true,
open_win_config = {
relative = "editor",
border = "rounded",
width = 30,
height = 30,
row = 1,
col = 1,
},
},
},
renderer = {
add_trailing = false,
group_empty = false,
highlight_git = false,
full_name = false,
highlight_opened_files = "none",
root_folder_label = ":~:s?$?/..?",
indent_width = 2,
indent_markers = {
enable = false,
inline_arrows = true,
icons = {
corner = " ",
edge = " ",
none = " ",
corner = "",
edge = "",
item = "",
bottom = "",
none = " ",
},
},
icons = {
webdev_colors = true,
git_placement = "before",
padding = " ",
symlink_arrow = "",
show = {
file = true,
folder = true,
folder_arrow = true,
git = true,
},
glyphs = {
default = "",
symlink = "",
bookmark = "",
folder = {
arrow_closed = "",
arrow_open = "",
default = "",
open = "",
empty = "",
empty_open = "",
symlink = "",
symlink_open = "",
},
git = {
unstaged = "",
staged = "",
unmerged = "",
renamed = "",
untracked = "",
deleted = "",
ignored = "",
},
},
},
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
symlink_destination = true,
},
hijack_directories = {
enable = true,
@@ -360,7 +572,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
},
update_focused_file = {
enable = false,
update_cwd = false,
update_root = false,
ignore_list = {},
},
ignore_ft_on_setup = {},
@@ -371,6 +583,12 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
diagnostics = {
enable = false,
show_on_dirs = false,
show_on_open_dirs = true,
debounce_delay = 50,
severity = {
min = vim.diagnostic.severity.HINT,
max = vim.diagnostic.severity.ERROR,
},
icons = {
hint = "",
info = "",
@@ -380,12 +598,21 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
},
filters = {
dotfiles = false,
git_clean = false,
no_buffer = false,
custom = {},
exclude = {},
},
filesystem_watchers = {
enable = true,
debounce_delay = 50,
ignore_dirs = {},
},
git = {
enable = true,
ignore = true,
show_on_dirs = true,
show_on_open_dirs = true,
timeout = 400,
},
actions = {
@@ -395,9 +622,22 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
global = false,
restrict_above_cwd = false,
},
expand_all = {
max_folder_discovery = 300,
exclude = {},
},
file_popup = {
open_win_config = {
col = 1,
row = 1,
relative = "cursor",
border = "shadow",
style = "minimal",
},
},
open_file = {
quit_on_open = false,
resize_window = false,
resize_window = true,
window_picker = {
enable = true,
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
@@ -407,11 +647,28 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
},
},
},
remove_file = {
close_window = true,
},
},
trash = {
cmd = "trash",
cmd = "gio trash",
require_confirm = true,
},
live_filter = {
prefix = "[FILTER]: ",
always_show_folders = true,
},
tab = {
sync = {
open = false,
close = false,
ignore = {},
},
},
notify = {
threshold = vim.log.levels.INFO,
},
log = {
enable = false,
truncate = false,
@@ -419,9 +676,11 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
all = false,
config = false,
copy_paste = false,
dev = false,
diagnostics = false,
git = false,
profile = false,
watcher = false,
},
},
} -- END_DEFAULT_OPTS
@@ -430,9 +689,16 @@ local function merge_options(conf)
return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {})
end
local FIELD_SKIP_VALIDATE = {
open_win_config = true,
}
local FIELD_OVERRIDE_TYPECHECK = {
width = { string = true, ["function"] = true, number = true },
height = { string = true, ["function"] = true, number = true },
remove_keymaps = { boolean = true, table = true },
on_attach = { ["function"] = true, string = true },
sort_by = { ["function"] = true, string = true },
root_folder_label = { ["function"] = true, string = true },
}
local function validate_options(conf)
@@ -445,25 +711,28 @@ local function validate_options(conf)
end
for k, v in pairs(user) do
local invalid
local override_typecheck = FIELD_OVERRIDE_TYPECHECK[k] or {}
if def[k] == nil then
-- option does not exist
invalid = string.format("unknown option: %s%s", prefix, k)
elseif type(v) ~= type(def[k]) and not override_typecheck[type(v)] then
-- option is of the wrong type and is not a function
invalid = string.format("invalid option: %s%s expected: %s actual: %s", prefix, k, type(def[k]), type(v))
end
if invalid then
if msg then
msg = string.format("%s | %s", msg, invalid)
else
msg = string.format("%s", invalid)
if not FIELD_SKIP_VALIDATE[k] then
local invalid
local override_typecheck = FIELD_OVERRIDE_TYPECHECK[k] or {}
if def[k] == nil then
-- option does not exist
invalid = string.format("[NvimTree] unknown option: %s%s", prefix, k)
elseif type(v) ~= type(def[k]) and not override_typecheck[type(v)] then
-- option is of the wrong type and is not a function
invalid =
string.format("[NvimTree] invalid option: %s%s expected: %s actual: %s", prefix, k, type(def[k]), type(v))
end
if invalid then
if msg then
msg = string.format("%s | %s", msg, invalid)
else
msg = string.format("%s", invalid)
end
user[k] = nil
else
validate(v, def[k], prefix .. k .. ".")
end
user[k] = nil
else
validate(v, def[k], prefix .. k .. ".")
end
end
end
@@ -471,11 +740,18 @@ local function validate_options(conf)
validate(conf, DEFAULT_OPTS, "")
if msg then
utils.warn(msg)
vim.notify_once(msg .. " | see :help nvim-tree-setup for available configuration options", vim.log.levels.WARN)
end
end
function M.setup(conf)
if vim.fn.has "nvim-0.7" == 0 then
vim.notify_once("nvim-tree.lua requires Neovim 0.7 or higher", vim.log.levels.WARN)
return
end
M.init_root = vim.fn.getcwd()
legacy.migrate_legacy_options(conf or {})
validate_options(conf)
@@ -483,6 +759,8 @@ function M.setup(conf)
local opts = merge_options(conf)
local netrw_disabled = opts.disable_netrw or opts.hijack_netrw
_config.root_dirs = opts.root_dirs
_config.prefer_startup_root = opts.prefer_startup_root
_config.update_focused_file = opts.update_focused_file
_config.open_on_setup = opts.open_on_setup
_config.open_on_setup_file = opts.open_on_setup_file
@@ -494,12 +772,14 @@ function M.setup(conf)
manage_netrw(opts.disable_netrw, opts.hijack_netrw)
M.config = opts
require("nvim-tree.notify").setup(opts)
require("nvim-tree.log").setup(opts)
log.line("config", "default config + user")
log.raw("config", "%s\n", vim.inspect(opts))
require("nvim-tree.actions").setup(opts)
require("nvim-tree.keymap").setup(opts)
require("nvim-tree.colors").setup()
require("nvim-tree.diagnostics").setup(opts)
require("nvim-tree.explorer").setup(opts)
@@ -507,10 +787,30 @@ function M.setup(conf)
require("nvim-tree.view").setup(opts)
require("nvim-tree.lib").setup(opts)
require("nvim-tree.renderer").setup(opts)
require("nvim-tree.live-filter").setup(opts)
require("nvim-tree.marks").setup(opts)
if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then
require("nvim-web-devicons").setup()
end
setup_vim_commands()
setup_autocommands(opts)
if not M.setup_called then
-- first call to setup
setup_vim_commands()
else
-- subsequent calls to setup
require("nvim-tree.watcher").purge_watchers()
view.close_all_tabs()
view.abandon_all_windows()
if core.get_explorer() ~= nil then
git.purge_state()
TreeExplorer = nil
end
end
M.setup_called = true
vim.schedule(function()
M.on_enter(netrw_disabled)
end)

View File

@@ -1,52 +0,0 @@
local a = vim.api
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {
current_tab = a.nvim_get_current_tabpage(),
}
function M.fn(name, with_open)
if not core.get_explorer() then
return
end
local foldername = name == ".." and vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h") or name
local no_cwd_change = vim.fn.expand(foldername) == core.get_cwd()
or M.options.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1)
local new_tab = a.nvim_get_current_tabpage()
local is_window = (vim.v.event.scope == "window" or vim.v.event.changed_window) and new_tab == M.current_tab
if no_cwd_change or is_window then
return
end
M.current_tab = new_tab
M.force_dirchange(foldername, with_open)
end
function M.force_dirchange(foldername, with_open)
local ps = log.profile_start("change dir %s", foldername)
if M.options.enable and vim.tbl_isempty(vim.v.event) then
if M.options.global then
vim.cmd("cd " .. vim.fn.fnameescape(foldername))
else
vim.cmd("lcd " .. vim.fn.fnameescape(foldername))
end
end
core.init(foldername)
if with_open then
require("nvim-tree.lib").open()
else
require("nvim-tree.renderer").draw()
end
log.profile_end(ps, "change dir %s", foldername)
end
function M.setup(options)
M.options = options.actions.change_dir
end
return M

View File

@@ -1,43 +0,0 @@
local renderer = require "nvim-tree.renderer"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {}
function M.fn(keep_buffers)
if not core.get_explorer() then
return
end
local buffer_paths = {}
for _, buffer in ipairs(vim.api.nvim_list_bufs()) do
table.insert(buffer_paths, vim.api.nvim_buf_get_name(buffer))
end
local function iter(nodes)
for _, node in pairs(nodes) do
if node.open then
local new_open = false
if keep_buffers == true then
for _, buffer_path in ipairs(buffer_paths) do
local matches = utils.str_find(buffer_path, node.absolute_path)
if matches then
new_open = true
end
end
end
node.open = new_open
end
if node.nodes then
iter(node.nodes)
end
end
end
iter(core.get_explorer().nodes)
renderer.draw()
end
return M

View File

@@ -0,0 +1,131 @@
local view = require "nvim-tree.view"
local lib = require "nvim-tree.lib"
local M = {}
local Actions = {
close = view.close,
-- Tree modifiers
collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn,
expand_all = require("nvim-tree.actions.tree-modifiers.expand-all").fn,
toggle_dotfiles = require("nvim-tree.actions.tree-modifiers.toggles").dotfiles,
toggle_custom = require("nvim-tree.actions.tree-modifiers.toggles").custom,
toggle_git_ignored = require("nvim-tree.actions.tree-modifiers.toggles").git_ignored,
toggle_git_clean = require("nvim-tree.actions.tree-modifiers.toggles").git_clean,
toggle_no_buffer = require("nvim-tree.actions.tree-modifiers.toggles").no_buffer,
-- Filesystem operations
copy_absolute_path = require("nvim-tree.actions.fs.copy-paste").copy_absolute_path,
copy_name = require("nvim-tree.actions.fs.copy-paste").copy_filename,
copy_path = require("nvim-tree.actions.fs.copy-paste").copy_path,
copy = require("nvim-tree.actions.fs.copy-paste").copy,
create = require("nvim-tree.actions.fs.create-file").fn,
cut = require("nvim-tree.actions.fs.copy-paste").cut,
full_rename = require("nvim-tree.actions.fs.rename-file").fn ":p",
paste = require("nvim-tree.actions.fs.copy-paste").paste,
trash = require("nvim-tree.actions.fs.trash").fn,
remove = require("nvim-tree.actions.fs.remove-file").fn,
rename = require("nvim-tree.actions.fs.rename-file").fn ":t",
rename_basename = require("nvim-tree.actions.fs.rename-file").fn ":t:r",
-- Movements in tree
close_node = require("nvim-tree.actions.moves.parent").fn(true),
first_sibling = require("nvim-tree.actions.moves.sibling").fn "first",
last_sibling = require("nvim-tree.actions.moves.sibling").fn "last",
next_diag_item = require("nvim-tree.actions.moves.item").fn("next", "diag"),
next_git_item = require("nvim-tree.actions.moves.item").fn("next", "git"),
next_sibling = require("nvim-tree.actions.moves.sibling").fn "next",
parent_node = require("nvim-tree.actions.moves.parent").fn(false),
prev_diag_item = require("nvim-tree.actions.moves.item").fn("prev", "diag"),
prev_git_item = require("nvim-tree.actions.moves.item").fn("prev", "git"),
prev_sibling = require("nvim-tree.actions.moves.sibling").fn "prev",
-- Other types
refresh = require("nvim-tree.actions.reloaders.reloaders").reload_explorer,
dir_up = require("nvim-tree.actions.root.dir-up").fn,
search_node = require("nvim-tree.actions.finders.search-node").fn,
run_file_command = require("nvim-tree.actions.node.run-command").run_file_command,
toggle_file_info = require("nvim-tree.actions.node.file-popup").toggle_file_info,
system_open = require("nvim-tree.actions.node.system-open").fn,
toggle_mark = require("nvim-tree.marks").toggle_mark,
bulk_move = require("nvim-tree.marks.bulk-move").bulk_move,
}
local function handle_action_on_help_ui(action)
if action == "close" or action == "toggle_help" then
require("nvim-tree.actions.tree-modifiers.toggles").help()
end
end
local function handle_filter_actions(action)
if action == "live_filter" then
require("nvim-tree.live-filter").start_filtering()
elseif action == "clear_live_filter" then
require("nvim-tree.live-filter").clear_filter()
end
end
local function change_dir_action(node)
if node.name == ".." then
require("nvim-tree.actions.root.change-dir").fn ".."
elseif node.nodes ~= nil then
require("nvim-tree.actions.root.change-dir").fn(lib.get_last_group_node(node).absolute_path)
end
end
local function open_file(action, node)
local path = node.absolute_path
if node.link_to and not node.nodes then
path = node.link_to
end
require("nvim-tree.actions.node.open-file").fn(action, path)
end
local function handle_tree_actions(action)
local node = lib.get_node_at_cursor()
if not node then
return
end
local custom_function = M.custom_keypress_funcs[action]
local defined_action = Actions[action]
if type(custom_function) == "function" then
return custom_function(node)
elseif defined_action then
return defined_action(node)
end
local is_parent = node.name == ".."
if action == "preview" and is_parent then
return
end
if action == "cd" or is_parent then
return change_dir_action(node)
end
if node.nodes then
lib.expand_or_collapse(node)
else
open_file(action, node)
end
end
function M.dispatch(action)
if view.is_help_ui() or action == "toggle_help" then
handle_action_on_help_ui(action)
elseif action == "live_filter" or action == "clear_live_filter" then
handle_filter_actions(action)
else
handle_tree_actions(action)
end
end
function M.setup(custom_keypress_funcs)
M.custom_keypress_funcs = custom_keypress_funcs
end
return M

View File

@@ -1,77 +0,0 @@
local log = require "nvim-tree.log"
local uv = vim.loop
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local renderer = require "nvim-tree.renderer"
local core = require "nvim-tree.core"
local M = {}
local running = {}
---Find a path in the tree, expand it and focus it
---@param fname string full path
function M.fn(fname)
if running[fname] or not core.get_explorer() then
return
end
running[fname] = true
local ps = log.profile_start("find file %s", fname)
-- always match against the real path
local fname_real = uv.fs_realpath(fname)
if not fname_real then
return
end
local i = core.get_nodes_starting_line() - 1
local tree_altered = false
local function iterate_nodes(nodes)
for _, node in ipairs(nodes) do
i = i + 1
if not node.absolute_path or not uv.fs_stat(node.absolute_path) then
break
end
-- match against node absolute and link, as symlinks themselves will differ
if node.absolute_path == fname_real or node.link_to == fname_real then
return i
end
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator)
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator)
local path_matches = node.nodes and (abs_match or link_match)
if path_matches then
if not node.open then
node.open = true
tree_altered = true
end
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
if iterate_nodes(node.nodes) ~= nil then
return i
end
-- mandatory to iterate i
elseif node.open then
iterate_nodes(node.nodes)
end
end
end
local index = iterate_nodes(core.get_explorer().nodes)
if tree_altered then
renderer.draw()
end
if index and view.is_visible() then
view.set_cursor { index, 0 }
end
running[fname] = false
log.profile_end(ps, "find file %s", fname)
end
return M

View File

@@ -0,0 +1,77 @@
local log = require "nvim-tree.log"
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local renderer = require "nvim-tree.renderer"
local core = require "nvim-tree.core"
local reload = require "nvim-tree.explorer.reload"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {}
local running = {}
---Find a path in the tree, expand it and focus it
---@param fname string full path
function M.fn(fname)
if not core.get_explorer() then
return
end
-- always match against the real path
local fname_real = vim.loop.fs_realpath(fname)
if not fname_real then
return
end
if running[fname_real] then
return
end
running[fname_real] = true
local ps = log.profile_start("find file %s", fname_real)
-- we cannot wait for watchers
reload.refresh_nodes_for_path(vim.fn.fnamemodify(fname_real, ":h"))
local line = core.get_nodes_starting_line()
local absolute_paths_searched = {}
local found = Iterator.builder(core.get_explorer().nodes)
:matcher(function(node)
return node.absolute_path == fname_real or node.link_to == fname_real
end)
:applier(function(node)
line = line + 1
if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then
return
end
table.insert(absolute_paths_searched, node.absolute_path)
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator)
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator)
if abs_match or link_match then
node.open = true
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
end
end)
:recursor(function(node)
return node.open and node.nodes
end)
:iterate()
if found and view.is_visible() then
renderer.draw()
view.set_cursor { line, 0 }
end
running[fname_real] = false
log.profile_end(ps, "find file %s", fname_real)
end
return M

View File

@@ -0,0 +1,91 @@
local core = require "nvim-tree.core"
local filters = require "nvim-tree.explorer.filters"
local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {}
local function search(search_dir, input_path)
local realpaths_searched = {}
if not search_dir then
return
end
local function iter(dir)
local realpath, path, name, stat, handle, _
local filter_status = filters.prepare()
handle, _ = vim.loop.fs_scandir(dir)
if not handle then
return
end
realpath, _ = vim.loop.fs_realpath(dir)
if not realpath or vim.tbl_contains(realpaths_searched, realpath) then
return
end
table.insert(realpaths_searched, realpath)
name, _ = vim.loop.fs_scandir_next(handle)
while name do
path = dir .. "/" .. name
stat, _ = vim.loop.fs_stat(path)
if not stat then
break
end
if not filters.should_filter(path, filter_status) then
if string.find(path, "/" .. input_path .. "$") then
return path
end
if stat.type == "directory" then
path = iter(path)
if path then
return path
end
end
end
name, _ = vim.loop.fs_scandir_next(handle)
end
end
return iter(search_dir)
end
function M.fn()
if not core.get_explorer() then
return
end
-- temporarily set &path
local bufnr = vim.api.nvim_get_current_buf()
local path_existed, path_opt = pcall(vim.api.nvim_buf_get_option, bufnr, "path")
vim.api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**")
vim.ui.input({ prompt = "Search: ", completion = "file_in_path" }, function(input_path)
if not input_path or input_path == "" then
return
end
-- reset &path
if path_existed then
vim.api.nvim_buf_set_option(bufnr, "path", path_opt)
else
vim.api.nvim_buf_set_option(bufnr, "path", nil)
end
-- strip trailing slash
input_path = string.gsub(input_path, "/$", "")
-- search under cwd
local found = search(core.get_cwd(), input_path)
if found then
find_file(found)
end
end)
end
return M

View File

@@ -1,10 +1,9 @@
local a = vim.api
local uv = vim.loop
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local M = {}
@@ -17,7 +16,7 @@ local function do_copy(source, destination)
local source_stats, handle
local success, errmsg
source_stats, errmsg = uv.fs_stat(source)
source_stats, errmsg = vim.loop.fs_stat(source)
if not source_stats then
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
return false, errmsg
@@ -31,14 +30,14 @@ local function do_copy(source, destination)
end
if source_stats.type == "file" then
success, errmsg = uv.fs_copyfile(source, destination)
success, errmsg = vim.loop.fs_copyfile(source, destination)
if not success then
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", errmsg)
return false, errmsg
end
return true
elseif source_stats.type == "directory" then
handle, errmsg = uv.fs_scandir(source)
handle, errmsg = vim.loop.fs_scandir(source)
if type(handle) == "string" then
return false, handle
elseif not handle then
@@ -46,14 +45,14 @@ local function do_copy(source, destination)
return false, errmsg
end
success, errmsg = uv.fs_mkdir(destination, source_stats.mode)
success, errmsg = vim.loop.fs_mkdir(destination, source_stats.mode)
if not success then
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, errmsg)
return false, errmsg
end
while true do
local name, _ = uv.fs_scandir_next(handle)
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
@@ -80,35 +79,39 @@ local function do_single_paste(source, dest, action_type, action_fn)
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
dest_stats, errmsg, errcode = uv.fs_stat(dest)
dest_stats, errmsg, errcode = vim.loop.fs_stat(dest)
if not dest_stats and errcode ~= "ENOENT" then
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
notify.error("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
return false, errmsg
end
local should_process = true
local should_rename = false
if dest_stats then
print(dest .. " already exists. Overwrite? y/n/r(ename)")
local ans = utils.get_user_input_char()
utils.clear_prompt()
should_process = ans:match "^y"
should_rename = ans:match "^r"
end
if should_rename then
local new_dest = vim.fn.input("New name: ", dest)
return do_single_paste(source, new_dest, action_type, action_fn)
end
if should_process then
local function on_process()
success, errmsg = action_fn(source, dest)
if not success then
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
notify.error("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
return false, errmsg
end
end
if dest_stats then
local prompt_select = "Overwrite " .. dest .. " ?"
local prompt_input = prompt_select .. " y/n/r(ename): "
lib.prompt(prompt_input, prompt_select, { "y", "n", "r" }, { "Yes", "No", "Rename" }, function(item_short)
utils.clear_prompt()
if item_short == "y" then
on_process()
elseif item_short == "r" then
vim.ui.input({ prompt = "Rename to ", default = dest, completion = "dir" }, function(new_dest)
utils.clear_prompt()
if new_dest then
do_single_paste(source, new_dest, action_type, action_fn)
end
end)
end
end)
else
on_process()
end
end
local function add_to_clipboard(node, clip)
@@ -119,11 +122,17 @@ local function add_to_clipboard(node, clip)
for idx, _node in ipairs(clip) do
if _node.absolute_path == node.absolute_path then
table.remove(clip, idx)
return a.nvim_out_write(node.absolute_path .. " removed to clipboard.\n")
return notify.info(node.absolute_path .. " removed to clipboard.")
end
end
table.insert(clip, node)
a.nvim_out_write(node.absolute_path .. " added to clipboard.\n")
notify.info(node.absolute_path .. " added to clipboard.")
end
function M.clear_clipboard()
clipboard.move = {}
clipboard.copy = {}
utils.notify.info "Clipboard has been emptied."
end
function M.copy(node)
@@ -137,7 +146,7 @@ end
local function do_paste(node, action_type, action_fn)
node = lib.get_last_group_node(node)
if node.name == ".." then
return
node = core.get_explorer()
end
local clip = clipboard[action_type]
if #clip == 0 then
@@ -145,18 +154,15 @@ local function do_paste(node, action_type, action_fn)
end
local destination = node.absolute_path
local stats, errmsg, errcode = uv.fs_stat(destination)
local stats, errmsg, errcode = vim.loop.fs_stat(destination)
if not stats and errcode ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
a.nvim_err_writeln("Could not " .. action_type .. " " .. destination .. " - " .. (errmsg or "???"))
notify.error("Could not " .. action_type .. " " .. destination .. " - " .. (errmsg or "???"))
return
end
local is_dir = stats and stats.type == "directory"
if not is_dir then
destination = vim.fn.fnamemodify(destination, ":p:h")
elseif not node.open then
destination = vim.fn.fnamemodify(destination, ":p:h:h")
end
for _, _node in ipairs(clip) do
@@ -165,7 +171,9 @@ local function do_paste(node, action_type, action_fn)
end
clipboard[action_type] = {}
return require("nvim-tree.actions.reloaders").reload_explorer()
if M.enable_reload then
return require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end
local function do_cut(source, destination)
@@ -176,12 +184,14 @@ local function do_cut(source, destination)
return true
end
local success, errmsg = uv.fs_rename(source, destination)
events._dispatch_will_rename_node(source, destination)
local success, errmsg = vim.loop.fs_rename(source, destination)
if not success then
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
return false, errmsg
end
utils.rename_loaded_buffers(source, destination)
events._dispatch_node_renamed(source, destination)
return true
end
@@ -208,18 +218,18 @@ function M.print_clipboard()
end
end
return a.nvim_out_write(table.concat(content, "\n") .. "\n")
return notify.info(table.concat(content, "\n") .. "\n")
end
local function copy_to_clipboard(content)
if M.use_system_clipboard == true then
vim.fn.setreg("+", content)
vim.fn.setreg('"', content)
return a.nvim_out_write(string.format("Copied %s to system clipboard! \n", content))
return notify.info(string.format("Copied %s to system clipboard!", content))
else
vim.fn.setreg('"', content)
vim.fn.setreg("1", content)
return a.nvim_out_write(string.format("Copied %s to neovim clipboard \n", content))
return notify.info(string.format("Copied %s to neovim clipboard!", content))
end
end
@@ -242,6 +252,7 @@ end
function M.setup(opts)
M.use_system_clipboard = opts.actions.use_system_clipboard
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -1,36 +1,36 @@
local a = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local lib = require "nvim-tree.lib"
local core = require "nvim-tree.core"
local notify = require "nvim-tree.notify"
local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {}
local function focus_file(file)
local _, i = utils.find_node(core.get_explorer().nodes, function(node)
return node.absolute_path == file
end)
require("nvim-tree.view").set_cursor { i + 1, 1 }
local function create_and_notify(file)
local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
if not ok then
notify.error("Couldn't create file " .. file)
return
end
vim.loop.fs_close(fd)
events._dispatch_file_created(file)
end
local function create_file(file)
if utils.file_exists(file) then
print(file .. " already exists. Overwrite? y/n")
local ans = utils.get_user_input_char()
utils.clear_prompt()
if ans ~= "y" then
return
end
local prompt_select = "Overwrite " .. file .. " ?"
local prompt_input = prompt_select .. " y/n: "
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
utils.clear_prompt()
if item_short == "y" then
create_and_notify(file)
end
end)
else
create_and_notify(file)
end
local ok, fd = pcall(uv.fs_open, file, "w", 420)
if not ok then
a.nvim_err_writeln("Couldn't create file " .. file)
return
end
uv.fs_close(fd)
events._dispatch_file_created(file)
end
local function get_num_nodes(iter)
@@ -42,8 +42,7 @@ local function get_num_nodes(iter)
end
local function get_containing_folder(node)
local is_open = vim.g.nvim_tree_create_in_closed_folder == 1 or node.open
if node.nodes ~= nil and is_open then
if node.nodes ~= nil then
return utils.path_add_trailing(node.absolute_path)
end
local node_name_size = #(node.name or "")
@@ -51,8 +50,8 @@ local function get_containing_folder(node)
end
function M.fn(node)
node = lib.get_last_group_node(node)
if node.name == ".." then
node = node and lib.get_last_group_node(node)
if not node or node.name == ".." then
node = {
absolute_path = core.get_cwd(),
nodes = core.get_explorer().nodes,
@@ -65,14 +64,13 @@ function M.fn(node)
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" }
vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt()
if not new_file_path or new_file_path == containing_folder then
return
end
utils.clear_prompt()
if utils.file_exists(new_file_path) then
utils.warn "Cannot create: file already exists"
notify.warn "Cannot create: file already exists"
return
end
@@ -95,21 +93,26 @@ function M.fn(node)
if is_last_path_file and idx == num_nodes then
create_file(path_to_create)
elseif not utils.file_exists(path_to_create) then
local success = uv.fs_mkdir(path_to_create, 493)
local success = vim.loop.fs_mkdir(path_to_create, 493)
if not success then
a.nvim_err_writeln("Could not create folder " .. path_to_create)
notify.error("Could not create folder " .. path_to_create)
is_error = true
break
end
events._dispatch_folder_created(new_file_path)
end
end
if not is_error then
a.nvim_out_write(new_file_path .. " was properly created\n")
notify.info(new_file_path .. " was properly created")
end
events._dispatch_folder_created(new_file_path)
require("nvim-tree.actions.reloaders").reload_explorer()
focus_file(new_file_path)
-- synchronously refreshes as we can't wait for the watchers
find_file(utils.path_remove_trailing(new_file_path))
end)
end
function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -0,0 +1,108 @@
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local view = require "nvim-tree.view"
local lib = require "nvim-tree.lib"
local notify = require "nvim-tree.notify"
local M = {}
local function close_windows(windows)
if view.View.float.enable and #vim.api.nvim_list_wins() == 1 then
return
end
for _, window in ipairs(windows) do
if vim.api.nvim_win_is_valid(window) then
vim.api.nvim_win_close(window, true)
end
end
end
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then
local winnr = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
if not view.View.float.enable then
vim.api.nvim_set_current_win(winnr)
end
end
vim.api.nvim_buf_delete(buf.bufnr, { force = true })
if M.close_window then
close_windows(buf.windows)
end
return
end
end
end
local function remove_dir(cwd)
local handle = vim.loop.fs_scandir(cwd)
if type(handle) == "string" then
return notify.error(handle)
end
while true do
local name, t = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local new_cwd = utils.path_join { cwd, name }
if t == "directory" then
local success = remove_dir(new_cwd)
if not success then
return false
end
else
local success = vim.loop.fs_unlink(new_cwd)
if not success then
return false
end
clear_buffer(new_cwd)
end
end
return vim.loop.fs_rmdir(cwd)
end
function M.fn(node)
if node.name == ".." then
return
end
local prompt_select = "Remove " .. node.name .. " ?"
local prompt_input = prompt_select .. " y/n: "
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
utils.clear_prompt()
if item_short == "y" then
if node.nodes ~= nil and not node.link_to then
local success = remove_dir(node.absolute_path)
if not success then
return notify.error("Could not remove " .. node.name)
end
events._dispatch_folder_removed(node.absolute_path)
else
local success = vim.loop.fs_unlink(node.absolute_path)
if not success then
return notify.error("Could not remove " .. node.name)
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
end
notify.info(node.absolute_path .. " was properly removed.")
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end
end)
end
function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable
M.close_window = opts.actions.remove_file.close_window
end
return M

View File

@@ -0,0 +1,92 @@
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local M = {}
local ALLOWED_MODIFIERS = {
[":p"] = true,
[":t"] = true,
[":t:r"] = true,
}
local function err_fmt(from, to, reason)
return string.format("Cannot rename %s -> %s: %s", from, to, reason)
end
function M.rename(node, to)
if utils.file_exists(to) then
notify.warn(err_fmt(node.absolute_path, to, "file already exists"))
return
end
events._dispatch_will_rename_node(node.absolute_path, to)
local success, err = vim.loop.fs_rename(node.absolute_path, to)
if not success then
return notify.warn(err_fmt(node.absolute_path, to, err))
end
notify.info(node.absolute_path .. "" .. to)
utils.rename_loaded_buffers(node.absolute_path, to)
events._dispatch_node_renamed(node.absolute_path, to)
end
function M.fn(default_modifier)
default_modifier = default_modifier or ":t"
return function(node, modifier)
if type(node) ~= "table" then
node = lib.get_node_at_cursor()
end
if type(modifier) ~= "string" then
modifier = default_modifier
end
-- support for only specific modifiers have been implemented
if not ALLOWED_MODIFIERS[modifier] then
return notify.warn(
"Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ",")
)
end
node = lib.get_last_group_node(node)
if node.name == ".." then
return
end
local namelen = node.name:len()
local directory = node.absolute_path:sub(0, namelen * -1 - 1)
local default_path
local prepend = ""
local append = ""
default_path = vim.fn.fnamemodify(node.absolute_path, modifier)
if modifier:sub(0, 2) == ":t" then
prepend = directory
end
if modifier == ":t:r" then
local extension = vim.fn.fnamemodify(node.name, ":e")
append = extension:len() == 0 and "" or "." .. extension
end
local input_opts = { prompt = "Rename to ", default = default_path, completion = "file" }
vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt()
if not new_file_path then
return
end
M.rename(node, prepend .. new_file_path .. append)
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
end
end
function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -0,0 +1,115 @@
local lib = require "nvim-tree.lib"
local notify = require "nvim-tree.notify"
local M = {
config = {
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
is_unix = vim.fn.has "unix" == 1,
},
}
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then
local winnr = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
vim.api.nvim_set_current_win(winnr)
end
vim.api.nvim_buf_delete(buf.bufnr, {})
return
end
end
end
function M.fn(node)
if node.name == ".." then
return
end
-- configs
if M.config.is_unix then
if M.config.trash.cmd == nil then
M.config.trash.cmd = "trash"
end
if M.config.trash.require_confirm == nil then
M.config.trash.require_confirm = true
end
else
notify.warn "Trash is currently a UNIX only feature!"
return
end
local binary = M.config.trash.cmd:gsub(" .*$", "")
if vim.fn.executable(binary) == 0 then
notify.warn(binary .. " is not executable.")
return
end
local err_msg = ""
local function on_stderr(_, data)
err_msg = err_msg .. (data and table.concat(data, " "))
end
-- trashes a path (file or folder)
local function trash_path(on_exit)
vim.fn.jobstart(M.config.trash.cmd .. ' "' .. node.absolute_path .. '"', {
detach = true,
on_exit = on_exit,
on_stderr = on_stderr,
})
end
local function do_trash()
if node.nodes ~= nil and not node.link_to then
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_folder_removed(node.absolute_path)
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
else
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
end
end
if M.config.trash.require_confirm then
local prompt_select = "Trash " .. node.name .. " ?"
local prompt_input = prompt_select .. " y/n: "
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
utils.clear_prompt()
if item_short == "y" then
do_trash()
end
end)
else
do_trash()
end
end
function M.setup(opts)
M.config.trash = opts.trash or {}
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -1,145 +1,290 @@
local a = vim.api
-- @deprecated: new implementation in nvim-tree.keymap. Please do not edit this file.
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local view = require "nvim-tree.view"
local util = require "nvim-tree.utils"
local nvim_tree_callback = require("nvim-tree.config").nvim_tree_callback
local notify = require "nvim-tree.notify"
-- BEGIN_DEFAULT_MAPPINGS
local DEFAULT_MAPPINGS = {
{
key = { "<CR>", "o", "<2-LeftMouse>" },
action = "edit",
desc = "open a file or folder; root will cd to the above directory",
},
{
key = "<C-e>",
action = "edit_in_place",
desc = "edit the file in place, effectively replacing the tree explorer",
},
{
key = "O",
action = "edit_no_picker",
desc = "same as (edit) with no window picker",
},
{
key = { "<C-]>", "<2-RightMouse>" },
action = "cd",
desc = "cd in the directory under the cursor",
},
{
key = "<C-v>",
action = "vsplit",
desc = "open the file in a vertical split",
},
{
key = "<C-x>",
action = "split",
desc = "open the file in a horizontal split",
},
{
key = "<C-t>",
action = "tabnew",
desc = "open the file in a new tab",
},
{
key = "<",
action = "prev_sibling",
desc = "navigate to the previous sibling of current file/directory",
},
{
key = ">",
action = "next_sibling",
desc = "navigate to the next sibling of current file/directory",
},
{
key = "P",
action = "parent_node",
desc = "move cursor to the parent directory",
},
{
key = "<BS>",
action = "close_node",
desc = "close current opened directory or parent",
},
{
key = "<Tab>",
action = "preview",
desc = "open the file as a preview (keeps the cursor in the tree)",
},
{
key = "K",
action = "first_sibling",
desc = "navigate to the first sibling of current file/directory",
},
{
key = "J",
action = "last_sibling",
desc = "navigate to the last sibling of current file/directory",
},
{
key = "C",
action = "toggle_git_clean",
desc = "toggle visibility of git clean via |filters.git_clean| option",
},
{
key = "I",
action = "toggle_git_ignored",
desc = "toggle visibility of files/folders hidden via |git.ignore| option",
},
{
key = "H",
action = "toggle_dotfiles",
desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
},
{
key = "B",
action = "toggle_no_buffer",
desc = "toggle visibility of files/folders hidden via |filters.no_buffer| option",
},
{
key = "U",
action = "toggle_custom",
desc = "toggle visibility of files/folders hidden via |filters.custom| option",
},
{
key = "R",
action = "refresh",
desc = "refresh the tree",
},
{
key = "a",
action = "create",
desc = "add a file; leaving a trailing `/` will add a directory",
},
{
key = "d",
action = "remove",
desc = "delete a file (will prompt for confirmation)",
},
{
key = "D",
action = "trash",
desc = "trash a file via |trash| option",
},
{
key = "r",
action = "rename",
desc = "rename a file",
},
{
key = "<C-r>",
action = "full_rename",
desc = "rename a file and omit the filename on input",
},
{
key = "e",
action = "rename_basename",
desc = "rename a file with filename-modifiers ':t:r' without changing extension",
},
{
key = "x",
action = "cut",
desc = "add/remove file/directory to cut clipboard",
},
{
key = "c",
action = "copy",
desc = "add/remove file/directory to copy clipboard",
},
{
key = "p",
action = "paste",
desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
},
{
key = "y",
action = "copy_name",
desc = "copy name to system clipboard",
},
{
key = "Y",
action = "copy_path",
desc = "copy relative path to system clipboard",
},
{
key = "gy",
action = "copy_absolute_path",
desc = "copy absolute path to system clipboard",
},
{
key = "[e",
action = "prev_diag_item",
desc = "go to next diagnostic item",
},
{
key = "[c",
action = "prev_git_item",
desc = "go to next git item",
},
{
key = "]e",
action = "next_diag_item",
desc = "go to prev diagnostic item",
},
{
key = "]c",
action = "next_git_item",
desc = "go to prev git item",
},
{
key = "-",
action = "dir_up",
desc = "navigate up to the parent directory of the current file/directory",
},
{
key = "s",
action = "system_open",
desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
},
{
key = "f",
action = "live_filter",
desc = "live filter nodes dynamically based on regex matching.",
},
{
key = "F",
action = "clear_live_filter",
desc = "clear live filter",
},
{
key = "q",
action = "close",
desc = "close tree window",
},
{
key = "W",
action = "collapse_all",
desc = "collapse the whole tree",
},
{
key = "E",
action = "expand_all",
desc = "expand the whole tree, stopping after expanding |actions.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
},
{
key = "S",
action = "search_node",
desc = "prompt the user to enter a path and then expands the tree to match the path",
},
{
key = ".",
action = "run_file_command",
desc = "enter vim command mode with the file the cursor is on",
},
{
key = "<C-k>",
action = "toggle_file_info",
desc = "toggle a popup with file infos about the file under the cursor",
},
{
key = "g?",
action = "toggle_help",
desc = "toggle help",
},
{
key = "m",
action = "toggle_mark",
desc = "Toggle node in bookmarks",
},
{
key = "bmv",
action = "bulk_move",
desc = "Move all bookmarked nodes into specified location",
},
}
-- END_DEFAULT_MAPPINGS
local M = {
mappings = {
{ key = { "<CR>", "o", "<2-LeftMouse>" }, action = "edit" },
{ key = "<C-e>", action = "edit_in_place" },
{ key = "O", action = "edit_no_picker" },
{ key = { "<2-RightMouse>", "<C-]>" }, action = "cd" },
{ key = "<C-v>", action = "vsplit" },
{ key = "<C-x>", action = "split" },
{ key = "<C-t>", action = "tabnew" },
{ key = "<", action = "prev_sibling" },
{ key = ">", action = "next_sibling" },
{ key = "P", action = "parent_node" },
{ key = "<BS>", action = "close_node" },
{ key = "<Tab>", action = "preview" },
{ key = "K", action = "first_sibling" },
{ key = "J", action = "last_sibling" },
{ key = "I", action = "toggle_git_ignored" },
{ key = "H", action = "toggle_dotfiles" },
{ key = "R", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "D", action = "trash" },
{ key = "r", action = "rename" },
{ key = "<C-r>", action = "full_rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "y", action = "copy_name" },
{ key = "Y", action = "copy_path" },
{ key = "gy", action = "copy_absolute_path" },
{ key = "[c", action = "prev_git_item" },
{ key = "]c", action = "next_git_item" },
{ key = "-", action = "dir_up" },
{ key = "s", action = "system_open" },
{ key = "q", action = "close" },
{ key = "g?", action = "toggle_help" },
{ key = "W", action = "collapse_all" },
{ key = "S", action = "search_node" },
{ key = ".", action = "run_file_command" },
{ key = "<C-k>", action = "toggle_file_info" },
{ key = "U", action = "toggle_custom" },
},
mappings = {},
custom_keypress_funcs = {},
}
local keypress_funcs = {
close = view.close,
close_node = require("nvim-tree.actions.movements").parent_node(true),
collapse_all = require("nvim-tree.actions.collapse-all").fn,
copy_absolute_path = require("nvim-tree.actions.copy-paste").copy_absolute_path,
copy_name = require("nvim-tree.actions.copy-paste").copy_filename,
copy_path = require("nvim-tree.actions.copy-paste").copy_path,
copy = require("nvim-tree.actions.copy-paste").copy,
create = require("nvim-tree.actions.create-file").fn,
cut = require("nvim-tree.actions.copy-paste").cut,
dir_up = require("nvim-tree.actions.dir-up").fn,
first_sibling = require("nvim-tree.actions.movements").sibling(-math.huge),
full_rename = require("nvim-tree.actions.rename-file").fn(true),
last_sibling = require("nvim-tree.actions.movements").sibling(math.huge),
next_git_item = require("nvim-tree.actions.movements").find_git_item "next",
next_sibling = require("nvim-tree.actions.movements").sibling(1),
parent_node = require("nvim-tree.actions.movements").parent_node(false),
paste = require("nvim-tree.actions.copy-paste").paste,
prev_git_item = require("nvim-tree.actions.movements").find_git_item "prev",
prev_sibling = require("nvim-tree.actions.movements").sibling(-1),
refresh = require("nvim-tree.actions.reloaders").reload_explorer,
remove = require("nvim-tree.actions.remove-file").fn,
rename = require("nvim-tree.actions.rename-file").fn(false),
run_file_command = require("nvim-tree.actions.run-command").run_file_command,
search_node = require("nvim-tree.actions.search-node").fn,
toggle_file_info = require("nvim-tree.actions.file-popup").toggle_file_info,
system_open = require("nvim-tree.actions.system-open").fn,
toggle_dotfiles = require("nvim-tree.actions.toggles").dotfiles,
toggle_help = require("nvim-tree.actions.toggles").help,
toggle_custom = require("nvim-tree.actions.toggles").custom,
toggle_git_ignored = require("nvim-tree.actions.toggles").git_ignored,
trash = require("nvim-tree.actions.trash").fn,
}
function M.on_keypress(action)
if view.is_help_ui() and action == "close" then
action = "toggle_help"
end
if view.is_help_ui() and action ~= "toggle_help" then
return
end
local node = lib.get_node_at_cursor()
if not node then
return
end
local custom_function = M.custom_keypress_funcs[action]
local default_function = keypress_funcs[action]
if type(custom_function) == "function" then
return custom_function(node)
elseif default_function then
return default_function(node)
end
if action == "preview" then
if node.name == ".." then
return
local function set_map_for(bufnr)
local opts = { noremap = true, silent = true, nowait = true, buffer = bufnr }
return function(mode, rhs)
return function(lhs)
vim.keymap.set(mode or "n", lhs, rhs, opts)
end
if not node.nodes then
return require("nvim-tree.actions.open-file").fn("preview", node.absolute_path)
end
elseif node.name == ".." then
return require("nvim-tree.actions.change-dir").fn ".."
elseif action == "cd" then
if node.nodes ~= nil then
require("nvim-tree.actions.change-dir").fn(lib.get_last_group_node(node).absolute_path)
end
return
end
end
if node.link_to and not node.nodes then
require("nvim-tree.actions.open-file").fn(action, node.link_to)
elseif node.nodes ~= nil then
lib.expand_or_collapse(node)
else
require("nvim-tree.actions.open-file").fn(action, node.absolute_path)
local function run_dispatch(action)
return function()
require("nvim-tree.actions.dispatch").dispatch(action)
end
end
function M.apply_mappings(bufnr)
local setter_for = set_map_for(bufnr)
for _, b in pairs(M.mappings) do
local mapping_rhs = b.cb or nvim_tree_callback(b.action)
if type(b.key) == "table" then
for _, key in pairs(b.key) do
a.nvim_buf_set_keymap(bufnr, b.mode or "n", key, mapping_rhs, { noremap = true, silent = true, nowait = true })
local rhs = b.cb or run_dispatch(b.action)
if rhs then
local setter = setter_for(b.mode, rhs)
local keys = type(b.key) == "table" and b.key or { b.key }
for _, key in pairs(keys) do
setter(key)
end
elseif mapping_rhs then
a.nvim_buf_set_keymap(bufnr, b.mode or "n", b.key, mapping_rhs, { noremap = true, silent = true, nowait = true })
end
end
end
@@ -175,7 +320,7 @@ local function merge_mappings(user_mappings)
if not is_empty(map.action) then
M.custom_keypress_funcs[map.action] = map.action_cb
else
util.warn "action can't be empty if action_cb provided"
notify.warn "action can't be empty if action_cb provided"
end
end
end
@@ -216,17 +361,59 @@ local function copy_mappings(user_mappings)
return user_mappings
end
local function cleanup_existing_mappings()
local bufnr = view.get_bufnr()
if bufnr == nil or not vim.api.nvim_buf_is_valid(bufnr) then
return
end
for _, b in pairs(M.mappings) do
local keys = type(b.key) == "table" and b.key or { b.key }
for _, key in pairs(keys) do
vim.keymap.del(b.mode or "n", key, { buffer = bufnr })
end
end
end
local function filter_mappings(mappings, keys)
if type(keys) == "boolean" and keys then
return {}
elseif type(keys) == "table" then
return vim.tbl_filter(function(m)
if type(m.key) == "table" then
m.key = vim.tbl_filter(function(k)
return not vim.tbl_contains(keys, k)
end, m.key)
return #m.key > 0
else
return not vim.tbl_contains(keys, m.key)
end
end, vim.deepcopy(mappings))
else
return vim.deepcopy(mappings)
end
end
local DEFAULT_MAPPING_CONFIG = {
custom_only = false,
list = {},
}
function M.setup(opts)
require("nvim-tree.actions.system-open").setup(opts.system_open)
require("nvim-tree.actions.trash").setup(opts.trash)
require("nvim-tree.actions.open-file").setup(opts)
require("nvim-tree.actions.change-dir").setup(opts)
require("nvim-tree.actions.copy-paste").setup(opts)
require("nvim-tree.actions.fs.trash").setup(opts)
require("nvim-tree.actions.node.system-open").setup(opts)
require("nvim-tree.actions.node.file-popup").setup(opts)
require("nvim-tree.actions.node.open-file").setup(opts)
require("nvim-tree.actions.root.change-dir").setup(opts)
require("nvim-tree.actions.fs.create-file").setup(opts)
require("nvim-tree.actions.fs.rename-file").setup(opts)
require("nvim-tree.actions.fs.remove-file").setup(opts)
require("nvim-tree.actions.fs.copy-paste").setup(opts)
require("nvim-tree.actions.tree-modifiers.expand-all").setup(opts)
cleanup_existing_mappings()
M.mappings = filter_mappings(DEFAULT_MAPPINGS, opts.remove_keymaps)
local user_map_config = (opts.view or {}).mappings or {}
local options = vim.tbl_deep_extend("force", DEFAULT_MAPPING_CONFIG, user_map_config)
@@ -236,6 +423,8 @@ function M.setup(opts)
M.mappings = merge_mappings(options.list)
end
require("nvim-tree.actions.dispatch").setup(M.custom_keypress_funcs)
log.line("config", "active mappings")
log.raw("config", "%s\n", vim.inspect(M.mappings))
end

View File

@@ -1,145 +0,0 @@
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local renderer = require "nvim-tree.renderer"
local core = require "nvim-tree.core"
local lib = require "nvim-tree.lib"
local M = {}
local function get_line_from_node(node, find_parent)
local node_path = node.absolute_path
if find_parent then
node_path = node.absolute_path:match("(.*)" .. utils.path_separator)
end
local line = core.get_nodes_starting_line()
local function iter(nodes, recursive)
for _, _node in ipairs(nodes) do
local n = lib.get_last_group_node(_node)
if node_path == n.absolute_path then
return line, _node
end
line = line + 1
if _node.open == true and recursive then
local _, child = iter(_node.nodes, recursive)
if child ~= nil then
return line, child
end
end
end
end
return iter
end
function M.parent_node(should_close)
return function(node)
if should_close and node.open then
node.open = false
return renderer.draw()
end
local parent = node.parent
if not parent or parent.cwd then
return view.set_cursor { 1, 0 }
end
local _, line = utils.find_node(core.get_explorer().nodes, function(n)
return n.absolute_path == parent.absolute_path
end)
view.set_cursor { line + 1, 0 }
if should_close then
parent.open = false
renderer.draw()
end
end
end
function M.sibling(direction)
return function(node)
if node.name == ".." or not direction then
return
end
local iter = get_line_from_node(node, true)
local node_path = node.absolute_path
local line = 0
local parent, _
-- Check if current node is already at root nodes
for index, _node in ipairs(core.get_explorer().nodes) do
if node_path == _node.absolute_path then
line = index
end
end
if line > 0 then
parent = core.get_explorer()
else
_, parent = iter(core.get_explorer().nodes, true)
if parent ~= nil and #parent.nodes > 1 then
line, _ = get_line_from_node(node)(parent.nodes)
end
-- Ignore parent line count
line = line - 1
end
local index = line + direction
if index < 1 then
index = 1
elseif index > #parent.nodes then
index = #parent.nodes
end
local target_node = parent.nodes[index]
line, _ = get_line_from_node(target_node)(core.get_explorer().nodes, true)
view.set_cursor { line, 0 }
end
end
function M.find_git_item(where)
return function()
local node_cur = lib.get_node_at_cursor()
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
local cur, first, prev, nex = nil, nil, nil, nil
for line, node in pairs(nodes_by_line) do
if not first and node.git_status then
first = line
end
if node == node_cur then
cur = line
elseif node.git_status then
if not cur then
prev = line
end
if cur and not nex then
nex = line
break
end
end
end
if where == "prev" then
if prev then
view.set_cursor { prev, 0 }
end
else
if cur then
if nex then
view.set_cursor { nex, 0 }
end
elseif first then
view.set_cursor { first, 0 }
end
end
end
end
return M

View File

@@ -0,0 +1,56 @@
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local core = require "nvim-tree.core"
local lib = require "nvim-tree.lib"
local explorer_common = require "nvim-tree.explorer.common"
local M = {}
function M.fn(where, what)
return function()
local node_cur = lib.get_node_at_cursor()
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
local cur, first, prev, nex = nil, nil, nil, nil
for line, node in pairs(nodes_by_line) do
local valid = false
if what == "git" then
valid = explorer_common.shows_git_status(node)
elseif what == "diag" then
valid = node.diag_status ~= nil
end
if not first and valid then
first = line
end
if node == node_cur then
cur = line
elseif valid then
if not cur then
prev = line
end
if cur and not nex then
nex = line
break
end
end
end
if where == "prev" then
if prev then
view.set_cursor { prev, 0 }
end
else
if cur then
if nex then
view.set_cursor { nex, 0 }
end
elseif first then
view.set_cursor { first, 0 }
end
end
end
end
return M

View File

@@ -0,0 +1,41 @@
local renderer = require "nvim-tree.renderer"
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {}
function M.fn(should_close)
should_close = should_close or false
return function(node)
if should_close and node.open then
node.open = false
return renderer.draw()
end
local parent = node.parent
if renderer.config.group_empty and parent then
while parent.parent and parent.parent.group_next do
parent = parent.parent
end
end
if not parent or not parent.parent then
return view.set_cursor { 1, 0 }
end
local _, line = utils.find_node(core.get_explorer().nodes, function(n)
return n.absolute_path == parent.absolute_path
end)
view.set_cursor { line + 1, 0 }
if should_close then
parent.open = false
renderer.draw()
end
end
end
return M

View File

@@ -0,0 +1,51 @@
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {}
function M.fn(direction)
return function(node)
if node.name == ".." or not direction then
return
end
local first, last, next, prev = nil, nil, nil, nil
local found = false
local parent = node.parent or core.get_explorer()
Iterator.builder(parent.nodes)
:recursor(function()
return nil
end)
:applier(function(n)
first = first or n
last = n
if n.absolute_path == node.absolute_path then
found = true
return
end
prev = not found and n or prev
if found and not next then
next = n
end
end)
:iterate()
local target_node
if direction == "first" then
target_node = first
elseif direction == "last" then
target_node = last
elseif direction == "next" then
target_node = next or first
else
target_node = prev or last
end
if target_node then
utils.focus_file(target_node.absolute_path)
end
end
end
return M

View File

@@ -1,5 +1,4 @@
local utils = require "nvim-tree.utils"
local a = vim.api
local M = {}
@@ -28,28 +27,25 @@ local function setup_window(node)
local max_width = vim.fn.max(vim.tbl_map(function(n)
return #n
end, lines))
local winnr = a.nvim_open_win(0, false, {
col = 1,
row = 1,
relative = "cursor",
local open_win_config = vim.tbl_extend("force", M.open_win_config, {
width = max_width + 1,
height = #lines,
border = "shadow",
noautocmd = true,
style = "minimal",
zindex = 60,
})
local winnr = vim.api.nvim_open_win(0, false, open_win_config)
current_popup = {
winnr = winnr,
file_path = node.absolute_path,
}
local bufnr = a.nvim_create_buf(false, true)
a.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
a.nvim_win_set_buf(winnr, bufnr)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.api.nvim_win_set_buf(winnr, bufnr)
end
function M.close_popup()
if current_popup ~= nil then
a.nvim_win_close(current_popup.winnr, { force = true })
vim.api.nvim_win_close(current_popup.winnr, { force = true })
vim.cmd "augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END"
current_popup = nil
@@ -72,11 +68,14 @@ function M.toggle_file_info(node)
setup_window(node)
vim.cmd [[
augroup NvimTreeRemoveFilePopup
au CursorMoved * lua require'nvim-tree.actions.file-popup'.close_popup()
augroup END
]]
vim.api.nvim_create_autocmd("CursorMoved", {
group = vim.api.nvim_create_augroup("NvimTreeRemoveFilePopup", {}),
callback = M.close_popup,
})
end
function M.setup(opts)
M.open_win_config = opts.actions.file_popup.open_win_config
end
return M

View File

@@ -0,0 +1,322 @@
-- Copyright 2019 Yazdani Kiyan under MIT License
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local M = {}
local function get_user_input_char()
local c = vim.fn.getchar()
while type(c) ~= "number" do
c = vim.fn.getchar()
end
return vim.fn.nr2char(c)
end
---Get all windows in the current tabpage that aren't NvimTree.
---@return table with valid win_ids
local function usable_win_ids()
local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local tree_winid = view.get_winnr(tabpage)
return vim.tbl_filter(function(id)
local bufid = vim.api.nvim_win_get_buf(id)
for option, v in pairs(M.window_picker.exclude) do
local ok, option_value = pcall(vim.api.nvim_buf_get_option, bufid, option)
if ok and vim.tbl_contains(v, option_value) then
return false
end
end
local win_config = vim.api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external
end, win_ids)
end
---Find the first window in the tab that is not NvimTree.
---@return integer -1 if none available
local function first_win_id()
local selectable = usable_win_ids()
if #selectable > 0 then
return selectable[1]
else
return -1
end
end
---Get user to pick a window in the tab that is not NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are
--- no selectable windows, return -1.
local function pick_win_id()
local selectable = usable_win_ids()
-- If there are no selectable windows: return. If there's only 1, return it without picking.
if #selectable == 0 then
return -1
end
if #selectable == 1 then
return selectable[1]
end
local i = 1
local win_opts = {}
local win_map = {}
local laststatus = vim.o.laststatus
vim.o.laststatus = 2
local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local not_selectable = vim.tbl_filter(function(id)
return not vim.tbl_contains(selectable, id)
end, win_ids)
if laststatus == 3 then
for _, win_id in ipairs(not_selectable) do
local ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline")
local ok_hl, winhl = pcall(vim.api.nvim_win_get_option, win_id, "winhl")
win_opts[win_id] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
-- Clear statusline for windows not selectable
vim.api.nvim_win_set_option(win_id, "statusline", " ")
end
end
-- Setup UI
for _, id in ipairs(selectable) do
local char = M.window_picker.chars:sub(i, i)
local ok_status, statusline = pcall(vim.api.nvim_win_get_option, id, "statusline")
local ok_hl, winhl = pcall(vim.api.nvim_win_get_option, id, "winhl")
win_opts[id] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
win_map[char] = id
vim.api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=")
vim.api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker")
i = i + 1
if i > #M.window_picker.chars then
break
end
end
vim.cmd "redraw"
if vim.opt.cmdheight._value ~= 0 then
print "Pick window: "
end
local _, resp = pcall(get_user_input_char)
resp = (resp or ""):upper()
utils.clear_prompt()
-- Restore window options
for _, id in ipairs(selectable) do
for opt, value in pairs(win_opts[id]) do
vim.api.nvim_win_set_option(id, opt, value)
end
end
if laststatus == 3 then
for _, id in ipairs(not_selectable) do
for opt, value in pairs(win_opts[id]) do
vim.api.nvim_win_set_option(id, opt, value)
end
end
end
vim.o.laststatus = laststatus
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
return
end
return win_map[resp]
end
local function open_file_in_tab(filename)
if M.quit_on_open then
view.close()
end
vim.cmd("tabe " .. vim.fn.fnameescape(filename))
end
local function on_preview(buf_loaded)
if not buf_loaded then
vim.bo.bufhidden = "delete"
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
group = vim.api.nvim_create_augroup("RemoveBufHidden", {}),
buffer = vim.api.nvim_get_current_buf(),
callback = function()
vim.bo.bufhidden = ""
end,
once = true,
})
end
view.focus()
end
local function get_target_winid(mode, win_ids)
local target_winid
if not M.window_picker.enable or mode == "edit_no_picker" then
target_winid = lib.target_winid
-- first available window
if not vim.tbl_contains(win_ids, target_winid) then
target_winid = first_win_id()
end
else
-- pick a window
target_winid = pick_win_id()
if target_winid == nil then
-- pick failed/cancelled
return
end
end
if target_winid == -1 then
target_winid = lib.target_winid
end
return target_winid
end
-- This is only to avoid the BufEnter for nvim-tree to trigger
-- which would cause find-file to run on an invalid file.
local function set_current_win_no_autocmd(winid, autocmd)
local eventignore = vim.opt.eventignore:get()
vim.opt.eventignore:append(autocmd)
vim.api.nvim_set_current_win(winid)
vim.opt.eventignore = eventignore
end
local function open_in_new_window(filename, mode, win_ids)
if type(mode) ~= "string" then
mode = ""
end
local target_winid = get_target_winid(mode, win_ids)
if not target_winid then
return
end
local create_new_window = #vim.api.nvim_list_wins() == 1
local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright"
-- Target is invalid or window does not exist in current tabpage: create new window
if not vim.tbl_contains(win_ids, target_winid) then
vim.cmd(new_window_side .. " vsplit")
target_winid = vim.api.nvim_get_current_win()
lib.target_winid = target_winid
-- No need to split, as we created a new window.
create_new_window = false
if mode:match "split$" then
mode = "edit"
end
elseif not vim.o.hidden then
-- If `hidden` is not enabled, check if buffer in target window is
-- modified, and create new split if it is.
local target_bufid = vim.api.nvim_win_get_buf(target_winid)
if vim.api.nvim_buf_get_option(target_bufid, "modified") then
mode = "vsplit"
end
end
local fname = vim.fn.fnameescape(filename)
local cmd
if create_new_window then
cmd = string.format("%s vsplit %s", new_window_side, fname)
elseif mode:match "split$" then
cmd = string.format("%s %s", mode, fname)
else
cmd = string.format("edit %s", fname)
end
if mode == "preview" and view.View.float.enable then
-- ignore "WinLeave" autocmd on preview
-- because the registered "WinLeave"
-- will kill the floating window immediately
set_current_win_no_autocmd(target_winid, { "WinLeave", "BufEnter" })
else
set_current_win_no_autocmd(target_winid, { "BufEnter" })
end
pcall(vim.cmd, cmd)
lib.set_target_win()
end
local function is_already_loaded(filename)
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf_id) and filename == vim.api.nvim_buf_get_name(buf_id) then
return true
end
end
return false
end
local function edit_in_current_buf(filename)
require("nvim-tree.view").abandon_current_window()
vim.cmd("edit " .. vim.fn.fnameescape(filename))
end
function M.fn(mode, filename)
if type(mode) ~= "string" then
mode = ""
end
if mode == "tabnew" then
return open_file_in_tab(filename)
end
if mode == "edit_in_place" then
return edit_in_current_buf(filename)
end
local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local buf_loaded = is_already_loaded(filename)
local found_win = utils.get_win_buf_from_path(filename)
if found_win and mode == "preview" then
return
end
if not found_win then
open_in_new_window(filename, mode, win_ids)
else
vim.api.nvim_set_current_win(found_win)
vim.bo.bufhidden = ""
end
if M.resize_window then
view.resize()
end
if mode == "preview" then
return on_preview(buf_loaded)
end
if M.quit_on_open then
view.close()
end
end
function M.setup(opts)
M.quit_on_open = opts.actions.open_file.quit_on_open
M.resize_window = opts.actions.open_file.resize_window
if opts.actions.open_file.window_picker.chars then
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()
end
M.window_picker = opts.actions.open_file.window_picker
end
return M

View File

@@ -1,4 +1,4 @@
local uv = vim.loop
local notify = require "nvim-tree.notify"
local M = {
config = {
@@ -10,7 +10,7 @@ local M = {
function M.fn(node)
if #M.config.system_open.cmd == 0 then
require("nvim-tree.utils").warn "Cannot open file with system application. Unrecognized platform."
require("nvim-tree.utils").notify.warn "Cannot open file with system application. Unrecognized platform."
return
end
@@ -18,10 +18,10 @@ function M.fn(node)
cmd = M.config.system_open.cmd,
args = M.config.system_open.args,
errors = "\n",
stderr = uv.new_pipe(false),
stderr = vim.loop.new_pipe(false),
}
table.insert(process.args, node.link_to or node.absolute_path)
process.handle, process.pid = uv.spawn(
process.handle, process.pid = vim.loop.spawn(
process.cmd,
{ args = process.args, stdio = { nil, nil, process.stderr }, detached = true },
function(code)
@@ -29,17 +29,16 @@ function M.fn(node)
process.stderr:close()
process.handle:close()
if code ~= 0 then
process.errors = process.errors .. string.format("NvimTree system_open: return code %d.", code)
error(process.errors)
notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors))
end
end
)
table.remove(process.args)
if not process.handle then
error("\n" .. process.pid .. "\nNvimTree system_open: failed to spawn process using '" .. process.cmd .. "'.")
notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid))
return
end
uv.read_start(process.stderr, function(err, data)
vim.loop.read_start(process.stderr, function(err, data)
if err then
return
end
@@ -47,11 +46,11 @@ function M.fn(node)
process.errors = process.errors .. data
end
end)
uv.unref(process.handle)
vim.loop.unref(process.handle)
end
function M.setup(opts)
M.config.system_open = opts or {}
M.config.system_open = opts.system_open or {}
if #M.config.system_open.cmd == 0 then
if M.config.is_windows then

View File

@@ -1,286 +0,0 @@
-- Copyright 2019 Yazdani Kiyan under MIT License
local api = vim.api
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local M = {}
local function get_split_cmd()
local side = view.View.side
if side == "right" then
return "aboveleft"
end
if side == "left" then
return "belowright"
end
if side == "top" then
return "bot"
end
return "top"
end
---Get user to pick a window. Selectable windows are all windows in the current
---tabpage that aren't NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are
--- no selectable windows, return -1.
local function pick_window()
local tabpage = api.nvim_get_current_tabpage()
local win_ids = api.nvim_tabpage_list_wins(tabpage)
local tree_winid = view.get_winnr(tabpage)
local selectable = vim.tbl_filter(function(id)
local bufid = api.nvim_win_get_buf(id)
for option, v in pairs(M.window_picker.exclude) do
local ok, option_value = pcall(api.nvim_buf_get_option, bufid, option)
if ok and vim.tbl_contains(v, option_value) then
return false
end
end
local win_config = api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external
end, win_ids)
-- If there are no selectable windows: return. If there's only 1, return it without picking.
if #selectable == 0 then
return -1
end
if #selectable == 1 then
return selectable[1]
end
local i = 1
local win_opts = {}
local win_map = {}
local laststatus = vim.o.laststatus
vim.o.laststatus = 2
local not_selectable = vim.tbl_filter(function(id)
return not vim.tbl_contains(selectable, id)
end, win_ids)
if laststatus == 3 then
for _, win_id in ipairs(not_selectable) do
local ok_status, statusline = pcall(api.nvim_win_get_option, win_id, "statusline")
local ok_hl, winhl = pcall(api.nvim_win_get_option, win_id, "winhl")
win_opts[win_id] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
-- Clear statusline for windows not selectable
api.nvim_win_set_option(win_id, "statusline", " ")
end
end
-- Setup UI
for _, id in ipairs(selectable) do
local char = M.window_picker.chars:sub(i, i)
local ok_status, statusline = pcall(api.nvim_win_get_option, id, "statusline")
local ok_hl, winhl = pcall(api.nvim_win_get_option, id, "winhl")
win_opts[id] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
win_map[char] = id
api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=")
api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker")
i = i + 1
if i > #M.window_picker.chars then
break
end
end
vim.cmd "redraw"
print "Pick window: "
local _, resp = pcall(utils.get_user_input_char)
resp = (resp or ""):upper()
utils.clear_prompt()
-- Restore window options
for _, id in ipairs(selectable) do
for opt, value in pairs(win_opts[id]) do
api.nvim_win_set_option(id, opt, value)
end
end
if laststatus == 3 then
for _, id in ipairs(not_selectable) do
for opt, value in pairs(win_opts[id]) do
api.nvim_win_set_option(id, opt, value)
end
end
end
vim.o.laststatus = laststatus
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
return
end
return win_map[resp]
end
local function open_file_in_tab(filename)
if M.quit_on_open then
view.close()
else
-- Switch window first to ensure new window doesn't inherit settings from
-- NvimTree
if lib.target_winid > 0 and api.nvim_win_is_valid(lib.target_winid) then
api.nvim_set_current_win(lib.target_winid)
else
vim.cmd "wincmd p"
end
end
-- This sequence of commands are here to ensure a number of things: the new
-- buffer must be opened in the current tabpage first so that focus can be
-- brought back to the tree if it wasn't quit_on_open. It also ensures that
-- when we open the new tabpage with the file, its window doesn't inherit
-- settings from NvimTree, as it was already loaded.
vim.cmd("edit " .. vim.fn.fnameescape(filename))
local alt_bufid = vim.fn.bufnr "#"
if alt_bufid ~= -1 then
api.nvim_set_current_buf(alt_bufid)
end
if not M.quit_on_open then
vim.cmd "wincmd p"
end
vim.cmd("tabe " .. vim.fn.fnameescape(filename))
end
function M.fn(mode, filename)
if mode == "tabnew" then
open_file_in_tab(filename)
return
end
if mode == "edit_in_place" then
require("nvim-tree.view").abandon_current_window()
vim.cmd("edit " .. vim.fn.fnameescape(filename))
return
end
local tabpage = api.nvim_get_current_tabpage()
local win_ids = api.nvim_tabpage_list_wins(tabpage)
local target_winid
if not M.window_picker.enable or mode == "edit_no_picker" then
target_winid = lib.target_winid
else
local pick_window_id = pick_window()
if pick_window_id == nil then
return
end
target_winid = pick_window_id
end
if target_winid == -1 then
target_winid = lib.target_winid
end
local do_split = mode == "split" or mode == "vsplit"
local vertical = mode ~= "split"
-- Check if file is already loaded in a buffer
local buf_loaded = false
for _, buf_id in ipairs(api.nvim_list_bufs()) do
if api.nvim_buf_is_loaded(buf_id) and filename == api.nvim_buf_get_name(buf_id) then
buf_loaded = true
break
end
end
-- Check if filename is already open in a window
local found = false
for _, id in ipairs(win_ids) do
if filename == api.nvim_buf_get_name(api.nvim_win_get_buf(id)) then
if mode == "preview" then
return
end
found = true
api.nvim_set_current_win(id)
break
end
end
if not found then
if not target_winid or not vim.tbl_contains(win_ids, target_winid) then
-- Target is invalid, or window does not exist in current tabpage: create
-- new window
local split_cmd = get_split_cmd()
local splitside = view.is_vertical() and "vsp" or "sp"
vim.cmd(split_cmd .. " " .. splitside)
target_winid = api.nvim_get_current_win()
lib.target_winid = target_winid
-- No need to split, as we created a new window.
do_split = false
elseif not vim.o.hidden then
-- If `hidden` is not enabled, check if buffer in target window is
-- modified, and create new split if it is.
local target_bufid = api.nvim_win_get_buf(target_winid)
if api.nvim_buf_get_option(target_bufid, "modified") then
do_split = true
end
end
local cmd
if do_split or #api.nvim_list_wins() == 1 then
cmd = string.format("%ssplit ", vertical and "vertical " or "")
else
cmd = "edit "
end
cmd = cmd .. vim.fn.fnameescape(filename)
api.nvim_set_current_win(target_winid)
pcall(vim.cmd, cmd)
lib.set_target_win()
end
if M.resize_window then
view.resize()
end
if mode == "preview" then
if not buf_loaded then
vim.bo.bufhidden = "delete"
vim.cmd [[
augroup RemoveBufHidden
autocmd!
autocmd TextChanged <buffer> setlocal bufhidden= | autocmd! RemoveBufHidden
autocmd TextChangedI <buffer> setlocal bufhidden= | autocmd! RemoveBufHidden
augroup end
]]
end
view.focus()
return
end
if M.quit_on_open then
view.close()
end
end
function M.setup(opts)
M.quit_on_open = opts.actions.open_file.quit_on_open
M.resize_window = opts.actions.open_file.resize_window
if opts.actions.open_file.window_picker.chars then
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()
end
M.window_picker = opts.actions.open_file.window_picker
end
return M

View File

@@ -6,19 +6,19 @@ local core = require "nvim-tree.core"
local M = {}
local function refresh_nodes(node, projects)
local function refresh_nodes(node, projects, unloaded_bufnr)
local cwd = node.cwd or node.link_to or node.absolute_path
local project_root = git.get_project_root(cwd)
explorer_module.reload(node, projects[project_root] or {})
explorer_module.reload(node, projects[project_root] or {}, unloaded_bufnr)
for _, _node in ipairs(node.nodes) do
if _node.nodes and _node.open then
refresh_nodes(_node, projects)
refresh_nodes(_node, projects, unloaded_bufnr)
end
end
end
function M.reload_node_status(parent_node, projects)
local project_root = git.get_project_root(parent_node.absolute_path or parent_node.cwd)
local project_root = git.get_project_root(parent_node.absolute_path)
local status = projects[project_root] or {}
for _, node in ipairs(parent_node.nodes) do
if node.nodes then
@@ -33,14 +33,16 @@ function M.reload_node_status(parent_node, projects)
end
local event_running = false
function M.reload_explorer()
---@param _ table unused node passed by action
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
function M.reload_explorer(_, unloaded_bufnr)
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then
return
end
event_running = true
local projects = git.reload()
refresh_nodes(core.get_explorer(), projects)
refresh_nodes(core.get_explorer(), projects, unloaded_bufnr)
if view.is_visible() then
renderer.draw()
end

View File

@@ -1,91 +0,0 @@
local a = vim.api
local luv = vim.loop
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local M = {}
local function close_windows(windows)
for _, window in ipairs(windows) do
if a.nvim_win_is_valid(window) then
a.nvim_win_close(window, true)
end
end
end
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then
local winnr = a.nvim_get_current_win()
a.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
a.nvim_set_current_win(winnr)
end
a.nvim_buf_delete(buf.bufnr, { force = true })
close_windows(buf.windows)
return
end
end
end
local function remove_dir(cwd)
local handle = luv.fs_scandir(cwd)
if type(handle) == "string" then
return a.nvim_err_writeln(handle)
end
while true do
local name, t = luv.fs_scandir_next(handle)
if not name then
break
end
local new_cwd = utils.path_join { cwd, name }
if t == "directory" then
local success = remove_dir(new_cwd)
if not success then
return false
end
else
local success = luv.fs_unlink(new_cwd)
if not success then
return false
end
clear_buffer(new_cwd)
end
end
return luv.fs_rmdir(cwd)
end
function M.fn(node)
if node.name == ".." then
return
end
print("Remove " .. node.name .. " ? y/n")
local ans = utils.get_user_input_char()
utils.clear_prompt()
if ans:match "^y" then
if node.nodes ~= nil and not node.link_to then
local success = remove_dir(node.absolute_path)
if not success then
return a.nvim_err_writeln("Could not remove " .. node.name)
end
events._dispatch_folder_removed(node.absolute_path)
else
local success = luv.fs_unlink(node.absolute_path)
if not success then
return a.nvim_err_writeln("Could not remove " .. node.name)
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
end
require("nvim-tree.actions.reloaders").reload_explorer()
end
end
return M

View File

@@ -1,45 +0,0 @@
local a = vim.api
local uv = vim.loop
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local M = {}
function M.fn(with_sub)
return function(node)
node = lib.get_last_group_node(node)
if node.name == ".." then
return
end
local namelen = node.name:len()
local abs_path = with_sub and node.absolute_path:sub(0, namelen * -1 - 1) or node.absolute_path
local input_opts = { prompt = "Rename to ", default = abs_path, completion = "file" }
vim.ui.input(input_opts, function(new_file_path)
if not new_file_path then
return
end
if utils.file_exists(new_file_path) then
utils.warn "Cannot rename: file already exists"
return
end
local success = uv.fs_rename(node.absolute_path, new_file_path)
if not success then
return a.nvim_err_writeln("Could not rename " .. node.absolute_path .. " to " .. new_file_path)
end
utils.clear_prompt()
a.nvim_out_write(node.absolute_path .. "" .. new_file_path .. "\n")
utils.rename_loaded_buffers(node.absolute_path, new_file_path)
events._dispatch_node_renamed(abs_path, new_file_path)
require("nvim-tree.actions.reloaders").reload_explorer()
end)
end
end
return M

View File

@@ -0,0 +1,83 @@
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {
current_tab = vim.api.nvim_get_current_tabpage(),
}
local function clean_input_cwd(name)
name = vim.fn.fnameescape(name)
local root_parent_cwd = vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h")
if name == ".." and root_parent_cwd then
return vim.fn.expand(root_parent_cwd)
else
return vim.fn.expand(name)
end
end
local function is_window_event(new_tabpage)
local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window
return is_event_scope_window and new_tabpage == M.current_tab
end
local function prevent_cwd_change(foldername)
local is_same_cwd = foldername == core.get_cwd()
local is_restricted_above = M.options.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1)
return is_same_cwd or is_restricted_above
end
function M.fn(input_cwd, with_open)
if not core.get_explorer() then
return
end
local new_tabpage = vim.api.nvim_get_current_tabpage()
if is_window_event(new_tabpage) then
return
end
local foldername = clean_input_cwd(input_cwd)
if prevent_cwd_change(foldername) then
return
end
M.current_tab = new_tabpage
M.force_dirchange(foldername, with_open)
end
local function cd(global, path)
vim.cmd((global and "cd " or "lcd ") .. vim.fn.fnameescape(path))
end
local function should_change_dir()
return M.options.enable and vim.tbl_isempty(vim.v.event)
end
local function add_profiling_to(f)
return function(foldername, should_open_view)
local ps = log.profile_start("change dir %s", foldername)
f(foldername, should_open_view)
log.profile_end(ps, "change dir %s", foldername)
end
end
M.force_dirchange = add_profiling_to(function(foldername, should_open_view)
if should_change_dir() then
cd(M.options.global, foldername)
end
core.init(foldername)
if should_open_view then
require("nvim-tree.lib").open()
else
require("nvim-tree.renderer").draw()
end
end)
function M.setup(options)
M.options = options.actions.change_dir
end
return M

View File

@@ -5,11 +5,11 @@ local M = {}
function M.fn(node)
if not node or node.name == ".." then
return require("nvim-tree.actions.change-dir").fn ".."
return require("nvim-tree.actions.root.change-dir").fn ".."
else
local newdir = vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h")
require("nvim-tree.actions.change-dir").fn(newdir)
return require("nvim-tree.actions.find-file").fn(node.absolute_path)
require("nvim-tree.actions.root.change-dir").fn(newdir)
return require("nvim-tree.actions.finders.find-file").fn(node.absolute_path)
end
end

View File

@@ -1,79 +0,0 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local filters = require "nvim-tree.explorer.filters"
local find_file = require("nvim-tree.actions.find-file").fn
local M = {}
local function search(dir, input_path)
local path, name, stat, handle, _
if not dir then
return
end
handle, _ = uv.fs_scandir(dir)
if not handle then
return
end
name, _ = uv.fs_scandir_next(handle)
while name do
path = dir .. "/" .. name
stat, _ = uv.fs_stat(path)
if not stat then
break
end
if not filters.should_ignore(path) then
if string.find(path, "/" .. input_path .. "$") then
return path
end
if stat.type == "directory" then
path = search(path, input_path)
if path then
return path
end
end
end
name, _ = uv.fs_scandir_next(handle)
end
end
function M.fn()
if not core.get_explorer() then
return
end
-- temporarily set &path
local bufnr = api.nvim_get_current_buf()
local path_existed, path_opt = pcall(api.nvim_buf_get_option, bufnr, "path")
api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**")
-- completes files/dirs under cwd
local input_path = vim.fn.input("Search: ", "", "file_in_path")
utils.clear_prompt()
-- reset &path
if path_existed then
api.nvim_buf_set_option(bufnr, "path", path_opt)
else
api.nvim_buf_set_option(bufnr, "path", nil)
end
-- strip trailing slash
input_path = string.gsub(input_path, "/$", "")
-- search under cwd
local found = search(core.get_cwd(), input_path)
if found then
find_file(found)
end
end
return M

View File

@@ -1,90 +0,0 @@
local a = vim.api
local M = {
config = {
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
is_unix = vim.fn.has "unix" == 1,
},
}
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then
local winnr = a.nvim_get_current_win()
a.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
a.nvim_set_current_win(winnr)
end
vim.api.nvim_buf_delete(buf.bufnr, {})
return
end
end
end
function M.fn(node)
if node.name == ".." then
return
end
-- configs
if M.config.is_unix then
if M.config.trash.cmd == nil then
M.config.trash.cmd = "trash"
end
if M.config.trash.require_confirm == nil then
M.config.trash.require_confirm = true
end
else
utils.warn "Trash is currently a UNIX only feature!"
return
end
-- trashes a path (file or folder)
local function trash_path(on_exit)
vim.fn.jobstart(M.config.trash.cmd .. ' "' .. node.absolute_path .. '"', {
detach = true,
on_exit = on_exit,
})
end
local is_confirmed = true
-- confirmation prompt
if M.config.trash.require_confirm then
is_confirmed = false
print("Trash " .. node.name .. " ? y/n")
local ans = utils.get_user_input_char()
if ans:match "^y" then
is_confirmed = true
end
utils.clear_prompt()
end
-- trashing
if is_confirmed then
if node.nodes ~= nil and not node.link_to then
trash_path(function()
events._dispatch_folder_removed(node.absolute_path)
require("nvim-tree.actions.reloaders").reload_explorer()
end)
else
trash_path(function()
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
require("nvim-tree.actions.reloaders").reload_explorer()
end)
end
end
end
function M.setup(opts)
M.config.trash = opts or {}
end
return M

View File

@@ -0,0 +1,46 @@
local renderer = require "nvim-tree.renderer"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {}
local function buf_match()
local buffer_paths = vim.tbl_map(function(buffer)
return vim.api.nvim_buf_get_name(buffer)
end, vim.api.nvim_list_bufs())
return function(path)
for _, buffer_path in ipairs(buffer_paths) do
local matches = utils.str_find(buffer_path, path)
if matches then
return true
end
end
return false
end
end
function M.fn(keep_buffers)
if not core.get_explorer() then
return
end
local matches = buf_match()
Iterator.builder(core.get_explorer().nodes)
:hidden()
:applier(function(node)
if node.nodes ~= nil then
node.open = keep_buffers == true and matches(node.absolute_path)
end
end)
:recursor(function(n)
return n.nodes
end)
:iterate()
renderer.draw()
end
return M

View File

@@ -0,0 +1,71 @@
local core = require "nvim-tree.core"
local renderer = require "nvim-tree.renderer"
local Iterator = require "nvim-tree.iterators.node-iterator"
local notify = require "nvim-tree.notify"
local M = {}
local function to_lookup_table(list)
local table = {}
for _, element in ipairs(list) do
table[element] = true
end
return table
end
local function expand(node)
node.open = true
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
end
local function should_expand(expansion_count, node)
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
local should_exclude = M.EXCLUDE[node.name]
return not should_halt and node.nodes and not node.open and not should_exclude
end
local function gen_iterator()
local expansion_count = 0
return function(parent)
if parent.parent and parent.nodes and not parent.open then
expansion_count = expansion_count + 1
expand(parent)
end
Iterator.builder(parent.nodes)
:hidden()
:applier(function(node)
if should_expand(expansion_count, node) then
expansion_count = expansion_count + 1
expand(node)
end
end)
:recursor(function(node)
return expansion_count < M.MAX_FOLDER_DISCOVERY and node.open and node.nodes
end)
:iterate()
if expansion_count >= M.MAX_FOLDER_DISCOVERY then
return true
end
end
end
function M.fn(base_node)
local node = base_node.nodes and base_node or core.get_explorer()
if gen_iterator()(node) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end
renderer.draw()
end
function M.setup(opts)
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)
end
return M

View File

@@ -1,7 +1,7 @@
local view = require "nvim-tree.view"
local filters = require "nvim-tree.explorer.filters"
local renderer = require "nvim-tree.renderer"
local reloaders = require "nvim-tree.actions.reloaders"
local reloaders = require "nvim-tree.actions.reloaders.reloaders"
local M = {}
@@ -15,6 +15,16 @@ function M.git_ignored()
return reloaders.reload_explorer()
end
function M.git_clean()
filters.config.filter_git_clean = not filters.config.filter_git_clean
return reloaders.reload_explorer()
end
function M.no_buffer()
filters.config.filter_no_buffer = not filters.config.filter_no_buffer
return reloaders.reload_explorer()
end
function M.dotfiles()
filters.config.filter_dotfiles = not filters.config.filter_dotfiles
return reloaders.reload_explorer()

130
lua/nvim-tree/api.lua Normal file
View File

@@ -0,0 +1,130 @@
local Api = {
tree = {},
node = { navigate = { sibling = {}, git = {}, diagnostics = {} }, run = {}, open = {} },
events = {},
marks = { bulk = {}, navigate = {} },
fs = { copy = {} },
git = {},
live_filter = {},
}
local function inject_node(f)
return function(node, ...)
node = node or require("nvim-tree.lib").get_node_at_cursor()
f(node, ...)
end
end
Api.tree.open = require("nvim-tree").open
Api.tree.toggle = require("nvim-tree").toggle
Api.tree.close = require("nvim-tree.view").close
Api.tree.close_in_this_tab = require("nvim-tree.view").close_this_tab_only
Api.tree.close_in_all_tabs = require("nvim-tree.view").close_all_tabs
Api.tree.focus = require("nvim-tree").focus
Api.tree.reload = require("nvim-tree.actions.reloaders.reloaders").reload_explorer
Api.tree.change_root = require("nvim-tree").change_dir
Api.tree.change_root_to_node = inject_node(function(node)
if node.name == ".." then
require("nvim-tree.actions.root.change-dir").fn ".."
elseif node.nodes ~= nil then
require("nvim-tree.actions.root.change-dir").fn(require("nvim-tree.lib").get_last_group_node(node).absolute_path)
end
end)
Api.tree.change_root_to_parent = inject_node(require("nvim-tree.actions.root.dir-up").fn)
Api.tree.get_node_under_cursor = require("nvim-tree.lib").get_node_at_cursor
Api.tree.get_nodes = require("nvim-tree.lib").get_nodes
Api.tree.find_file = require("nvim-tree.actions.finders.find-file").fn
Api.tree.search_node = require("nvim-tree.actions.finders.search-node").fn
Api.tree.collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn
Api.tree.expand_all = inject_node(require("nvim-tree.actions.tree-modifiers.expand-all").fn)
Api.tree.toggle_gitignore_filter = require("nvim-tree.actions.tree-modifiers.toggles").git_ignored
Api.tree.toggle_git_clean_filter = require("nvim-tree.actions.tree-modifiers.toggles").git_clean
Api.tree.toggle_no_buffer_filter = require("nvim-tree.actions.tree-modifiers.toggles").no_buffer
Api.tree.toggle_custom_filter = require("nvim-tree.actions.tree-modifiers.toggles").custom
Api.tree.toggle_hidden_filter = require("nvim-tree.actions.tree-modifiers.toggles").dotfiles
Api.tree.toggle_help = require("nvim-tree.actions.tree-modifiers.toggles").help
Api.fs.create = inject_node(require("nvim-tree.actions.fs.create-file").fn)
Api.fs.remove = inject_node(require("nvim-tree.actions.fs.remove-file").fn)
Api.fs.trash = inject_node(require("nvim-tree.actions.fs.trash").fn)
Api.fs.rename_node = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
Api.fs.rename = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
Api.fs.rename_sub = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":p")
Api.fs.rename_basename = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t:r")
Api.fs.cut = inject_node(require("nvim-tree.actions.fs.copy-paste").cut)
Api.fs.paste = inject_node(require("nvim-tree.actions.fs.copy-paste").paste)
Api.fs.clear_clipboard = require("nvim-tree.actions.fs.copy-paste").clear_clipboard
Api.fs.print_clipboard = require("nvim-tree.actions.fs.copy-paste").print_clipboard
Api.fs.copy.node = inject_node(require("nvim-tree.actions.fs.copy-paste").copy)
Api.fs.copy.absolute_path = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_absolute_path)
Api.fs.copy.filename = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_filename)
Api.fs.copy.relative_path = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_path)
local function edit(mode, node)
local path = node.absolute_path
if node.link_to and not node.nodes then
path = node.link_to
end
require("nvim-tree.actions.node.open-file").fn(mode, path)
end
local function open_or_expand_or_dir_up(mode)
return function(node)
if node.name == ".." then
require("nvim-tree.actions.root.change-dir").fn ".."
elseif node.nodes then
require("nvim-tree.lib").expand_or_collapse(node)
else
edit(mode, node)
end
end
end
local function open_preview(node)
if node.nodes or node.name == ".." then
return
end
edit("preview", node)
end
Api.node.open.edit = inject_node(open_or_expand_or_dir_up "edit")
Api.node.open.replace_tree_buffer = inject_node(open_or_expand_or_dir_up "edit_in_place")
Api.node.open.no_window_picker = inject_node(open_or_expand_or_dir_up "edit_no_picker")
Api.node.open.vertical = inject_node(open_or_expand_or_dir_up "vsplit")
Api.node.open.horizontal = inject_node(open_or_expand_or_dir_up "split")
Api.node.open.tab = inject_node(open_or_expand_or_dir_up "tabnew")
Api.node.open.preview = inject_node(open_preview)
Api.node.show_info_popup = inject_node(require("nvim-tree.actions.node.file-popup").toggle_file_info)
Api.node.run.cmd = inject_node(require("nvim-tree.actions.node.run-command").run_file_command)
Api.node.run.system = inject_node(require("nvim-tree.actions.node.system-open").fn)
Api.node.navigate.sibling.next = inject_node(require("nvim-tree.actions.moves.sibling").fn "next")
Api.node.navigate.sibling.prev = inject_node(require("nvim-tree.actions.moves.sibling").fn "prev")
Api.node.navigate.sibling.first = inject_node(require("nvim-tree.actions.moves.sibling").fn "first")
Api.node.navigate.sibling.last = inject_node(require("nvim-tree.actions.moves.sibling").fn "last")
Api.node.navigate.parent = inject_node(require("nvim-tree.actions.moves.parent").fn(false))
Api.node.navigate.parent_close = inject_node(require("nvim-tree.actions.moves.parent").fn(true))
Api.node.navigate.git.next = inject_node(require("nvim-tree.actions.moves.item").fn("next", "git"))
Api.node.navigate.git.prev = inject_node(require("nvim-tree.actions.moves.item").fn("prev", "git"))
Api.node.navigate.diagnostics.next = inject_node(require("nvim-tree.actions.moves.item").fn("next", "diag"))
Api.node.navigate.diagnostics.prev = inject_node(require("nvim-tree.actions.moves.item").fn("prev", "diag"))
Api.git.reload = require("nvim-tree.actions.reloaders.reloaders").reload_git
Api.events.subscribe = require("nvim-tree.events").subscribe
Api.events.Event = require("nvim-tree.events").Event
Api.live_filter.start = require("nvim-tree.live-filter").start_filtering
Api.live_filter.clear = require("nvim-tree.live-filter").clear_filter
Api.marks.get = inject_node(require("nvim-tree.marks").get_mark)
Api.marks.list = require("nvim-tree.marks").get_marks
Api.marks.toggle = inject_node(require("nvim-tree.marks").toggle_mark)
Api.marks.clear = require("nvim-tree.marks").clear_marks
Api.marks.bulk.move = require("nvim-tree.marks.bulk-move").bulk_move
Api.marks.navigate.next = require("nvim-tree.marks.navigation").next
Api.marks.navigate.prev = require("nvim-tree.marks.navigation").prev
Api.marks.navigate.select = require("nvim-tree.marks.navigation").select
return Api

View File

@@ -1,6 +1,3 @@
local api = vim.api
local icons = require "nvim-tree.renderer.icon-config"
local M = {}
local function get_color_from_hl(hl_name, fallback)
@@ -52,6 +49,10 @@ local function get_hl_groups()
GitNew = { fg = colors.yellow },
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
LiveFilterPrefix = { gui = "bold", fg = colors.purple },
LiveFilterValue = { gui = "bold", fg = "#fff" },
Bookmark = { fg = colors.green },
}
end
@@ -60,9 +61,13 @@ local function get_links()
FolderName = "Directory",
EmptyFolderName = "Directory",
OpenedFolderName = "Directory",
OpenedFolderIcon = "NvimTreeFolderIcon",
ClosedFolderIcon = "NvimTreeFolderIcon",
Normal = "Normal",
NormalNC = "NvimTreeNormal",
EndOfBuffer = "EndOfBuffer",
CursorLineNr = "CursorLineNr",
LineNr = "LineNr",
CursorLine = "CursorLine",
VertSplit = "VertSplit",
WinSeparator = "NvimTreeVertSplit",
@@ -73,6 +78,7 @@ local function get_links()
FileMerge = "NvimTreeGitMerge",
FileStaged = "NvimTreeGitStaged",
FileDeleted = "NvimTreeGitDeleted",
FileIgnored = "NvimTreeGitIgnored",
Popup = "Normal",
GitIgnored = "Comment",
StatusLine = "StatusLine",
@@ -82,20 +88,17 @@ local function get_links()
end
function M.setup()
if icons.get_config().show_file_icon and icons.get_config().has_devicons then
require("nvim-web-devicons").setup()
end
local higlight_groups = get_hl_groups()
for k, d in pairs(higlight_groups) do
local gui = d.gui and " gui=" .. d.gui or ""
local fg = d.fg and " guifg=" .. d.fg or ""
local bg = d.bg and " guibg=" .. d.bg or ""
api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg)
vim.api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg)
end
local links = get_links()
for k, d in pairs(links) do
api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
vim.api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
end
end

View File

@@ -4,7 +4,7 @@ local M = {}
-- TODO: remove this once the cb property is not supported in mappings
function M.nvim_tree_callback(callback_name)
return string.format(":lua require'nvim-tree.actions'.on_keypress('%s')<CR>", callback_name)
return string.format("<cmd>lua require'nvim-tree.actions.dispatch'.dispatch('%s')<CR>", callback_name)
end
return M

View File

@@ -1,6 +1,8 @@
local events = require "nvim-tree.events"
local explorer = require "nvim-tree.explorer"
local live_filter = require "nvim-tree.live-filter"
local view = require "nvim-tree.view"
local log = require "nvim-tree.log"
local M = {}
@@ -8,11 +10,18 @@ TreeExplorer = nil
local first_init_done = false
function M.init(foldername)
local pn = string.format("core init %s", foldername)
local ps = log.profile_start(pn)
if TreeExplorer then
TreeExplorer:destroy()
end
TreeExplorer = explorer.Explorer.new(foldername)
if not first_init_done then
events._dispatch_ready()
first_init_done = true
end
log.profile_end(ps, pn)
end
function M.get_explorer()
@@ -20,7 +29,7 @@ function M.get_explorer()
end
function M.get_cwd()
return TreeExplorer.cwd
return TreeExplorer.absolute_path
end
function M.get_nodes_starting_line()
@@ -28,6 +37,9 @@ function M.get_nodes_starting_line()
if view.is_root_folder_visible(M.get_cwd()) then
offset = offset + 1
end
if live_filter.filter then
return offset + 1
end
return offset
end

View File

@@ -1,4 +1,3 @@
local a = vim.api
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local core = require "nvim-tree.core"
@@ -8,16 +7,6 @@ local M = {}
local GROUP = "NvimTreeDiagnosticSigns"
local function get_lowest_severity(diagnostics)
local severity = math.huge
for _, v in ipairs(diagnostics) do
if v.severity < severity then
severity = v.severity
end
end
return severity
end
local severity_levels = { Error = 1, Warning = 2, Information = 3, Hint = 4 }
local sign_names = {
{ "NvimTreeSignError", "NvimTreeLspDiagnosticsError" },
@@ -28,39 +17,23 @@ local sign_names = {
local function add_sign(linenr, severity)
local buf = view.get_bufnr()
if not a.nvim_buf_is_valid(buf) or not a.nvim_buf_is_loaded(buf) then
if not vim.api.nvim_buf_is_valid(buf) or not vim.api.nvim_buf_is_loaded(buf) then
return
end
local sign_name = sign_names[severity][1]
vim.fn.sign_place(1, GROUP, sign_name, buf, { lnum = linenr })
vim.fn.sign_place(0, GROUP, sign_name, buf, { lnum = linenr, priority = 2 })
end
local function from_nvim_lsp()
local buffer_severity = {}
-- vim.lsp.diagnostic.get_all was deprecated in nvim 0.7 and replaced with vim.diagnostic.get
-- This conditional can be removed when the minimum required version of nvim is changed to 0.7.
if vim.diagnostic then
-- nvim version >= 0.7
for _, diagnostic in ipairs(vim.diagnostic.get()) do
local buf = diagnostic.bufnr
if a.nvim_buf_is_valid(buf) then
local bufname = a.nvim_buf_get_name(buf)
local lowest_severity = buffer_severity[bufname]
if not lowest_severity or diagnostic.severity < lowest_severity then
buffer_severity[bufname] = diagnostic.severity
end
end
end
else
-- nvim version < 0.7
for buf, diagnostics in pairs(vim.lsp.diagnostic.get_all()) do
if a.nvim_buf_is_valid(buf) then
local bufname = a.nvim_buf_get_name(buf)
if not buffer_severity[bufname] then
local severity = get_lowest_severity(diagnostics)
buffer_severity[bufname] = severity
end
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do
local buf = diagnostic.bufnr
if vim.api.nvim_buf_is_valid(buf) then
local bufname = vim.api.nvim_buf_get_name(buf)
local lowest_severity = buffer_severity[bufname]
if not lowest_severity or diagnostic.severity < lowest_severity then
buffer_severity[bufname] = diagnostic.severity
end
end
end
@@ -116,36 +89,45 @@ function M.update()
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then
return
end
local ps = log.profile_start "diagnostics update"
log.line("diagnostics", "update")
utils.debounce("diagnostics", M.debounce_delay, function()
local ps = log.profile_start "diagnostics update"
log.line("diagnostics", "update")
local buffer_severity
if is_using_coc() then
buffer_severity = from_coc()
else
buffer_severity = from_nvim_lsp()
end
local buffer_severity
if is_using_coc() then
buffer_severity = from_coc()
else
buffer_severity = from_nvim_lsp()
end
M.clear()
for bufname, severity in pairs(buffer_severity) do
local bufpath = utils.canonical_path(bufname)
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity)
if 0 < severity and severity < 5 then
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
for line, node in pairs(nodes_by_line) do
local nodepath = utils.canonical_path(node.absolute_path)
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath)
if M.show_on_dirs and vim.startswith(bufpath, nodepath) then
log.line("diagnostics", " matched fold node '%s'", node.absolute_path)
add_sign(line, severity)
elseif nodepath == bufpath then
log.line("diagnostics", " matched file node '%s'", node.absolute_path)
add_sign(line, severity)
M.clear()
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
for _, node in pairs(nodes_by_line) do
node.diag_status = nil
end
for bufname, severity in pairs(buffer_severity) do
local bufpath = utils.canonical_path(bufname)
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity)
if 0 < severity and severity < 5 then
for line, node in pairs(nodes_by_line) do
local nodepath = utils.canonical_path(node.absolute_path)
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath)
if M.show_on_dirs and vim.startswith(bufpath, nodepath) and (not node.open or M.show_on_open_dirs) then
log.line("diagnostics", " matched fold node '%s'", node.absolute_path)
node.diag_status = severity
add_sign(line, severity)
elseif nodepath == bufpath then
log.line("diagnostics", " matched file node '%s'", node.absolute_path)
node.diag_status = severity
add_sign(line, severity)
end
end
end
end
end
log.profile_end(ps, "diagnostics update")
log.profile_end(ps, "diagnostics update")
end)
end
local links = {
@@ -157,7 +139,15 @@ local links = {
function M.setup(opts)
M.enable = opts.diagnostics.enable
M.debounce_delay = opts.diagnostics.debounce_delay
M.severity = opts.diagnostics.severity
if M.enable then
log.line("diagnostics", "setup")
end
M.show_on_dirs = opts.diagnostics.show_on_dirs
M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs
vim.fn.sign_define(sign_names[1][1], { text = opts.diagnostics.icons.error, texthl = sign_names[1][2] })
vim.fn.sign_define(sign_names[2][1], { text = opts.diagnostics.icons.warning, texthl = sign_names[2][2] })
vim.fn.sign_define(sign_names[3][1], { text = opts.diagnostics.icons.info, texthl = sign_names[3][2] })
@@ -166,12 +156,6 @@ function M.setup(opts)
for lhs, rhs in pairs(links) do
vim.cmd("hi def link " .. lhs .. " " .. rhs)
end
if M.enable then
log.line("diagnostics", "setup")
vim.cmd "au DiagnosticChanged * lua require'nvim-tree.diagnostics'.update()"
vim.cmd "au User CocDiagnosticChange lua require'nvim-tree.diagnostics'.update()"
end
end
return M

View File

@@ -1,9 +1,12 @@
local notify = require "nvim-tree.notify"
local M = {}
local global_handlers = {}
local Event = {
M.Event = {
Ready = "Ready",
WillRenameNode = "WillRenameNode",
NodeRenamed = "NodeRenamed",
TreeOpen = "TreeOpen",
TreeClose = "TreeClose",
@@ -11,13 +14,14 @@ local Event = {
FileRemoved = "FileRemoved",
FolderCreated = "FolderCreated",
FolderRemoved = "FolderRemoved",
Resize = "Resize",
}
local function get_handlers(event_name)
return global_handlers[event_name] or {}
end
local function register_handler(event_name, handler)
function M.subscribe(event_name, handler)
local handlers = get_handlers(event_name)
table.insert(handlers, handler)
global_handlers[event_name] = handlers
@@ -27,103 +31,104 @@ local function dispatch(event_name, payload)
for _, handler in pairs(get_handlers(event_name)) do
local success, error = pcall(handler, payload)
if not success then
vim.api.nvim_err_writeln("Handler for event " .. event_name .. " errored. " .. vim.inspect(error))
notify.error("Handler for event " .. event_name .. " errored. " .. vim.inspect(error))
end
end
end
--@private
function M._dispatch_ready()
dispatch(Event.Ready)
dispatch(M.Event.Ready)
end
--@private
function M._dispatch_will_rename_node(old_name, new_name)
dispatch(M.Event.WillRenameNode, { old_name = old_name, new_name = new_name })
end
--@private
function M._dispatch_node_renamed(old_name, new_name)
dispatch(Event.NodeRenamed, { old_name = old_name, new_name = new_name })
dispatch(M.Event.NodeRenamed, { old_name = old_name, new_name = new_name })
end
--@private
function M._dispatch_file_removed(fname)
dispatch(Event.FileRemoved, { fname = fname })
dispatch(M.Event.FileRemoved, { fname = fname })
end
--@private
function M._dispatch_file_created(fname)
dispatch(Event.FileCreated, { fname = fname })
dispatch(M.Event.FileCreated, { fname = fname })
end
--@private
function M._dispatch_folder_created(folder_name)
dispatch(Event.FolderCreated, { folder_name = folder_name })
dispatch(M.Event.FolderCreated, { folder_name = folder_name })
end
--@private
function M._dispatch_folder_removed(folder_name)
dispatch(Event.FolderRemoved, { folder_name = folder_name })
dispatch(M.Event.FolderRemoved, { folder_name = folder_name })
end
--@private
function M._dispatch_on_tree_open()
dispatch(Event.TreeOpen, nil)
dispatch(M.Event.TreeOpen, nil)
end
--@private
function M._dispatch_on_tree_close()
dispatch(Event.TreeClose, nil)
dispatch(M.Event.TreeClose, nil)
end
--Registers a handler for the Ready event.
--@param handler (function) Handler with the signature `function()`
--@private
function M._dispatch_on_tree_resize(size)
dispatch(M.Event.Resize, size)
end
--- @deprecated
function M.on_nvim_tree_ready(handler)
register_handler(Event.Ready, handler)
M.subscribe(M.Event.Ready, handler)
end
--Registers a handler for the NodeRenamed event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - old_name (string) Absolute path to the old node location.
-- - new_name (string) Absolute path to the new node location.
--- @deprecated
function M.on_node_renamed(handler)
register_handler(Event.NodeRenamed, handler)
M.subscribe(M.Event.NodeRenamed, handler)
end
--Registers a handler for the FileCreated event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - fname (string) Absolute path to the created file.
--- @deprecated
function M.on_file_created(handler)
register_handler(Event.FileCreated, handler)
M.subscribe(M.Event.FileCreated, handler)
end
--Registers a handler for the FileRemoved event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - fname (string) Absolute path to the removed file.
--- @deprecated
function M.on_file_removed(handler)
register_handler(Event.FileRemoved, handler)
M.subscribe(M.Event.FileRemoved, handler)
end
--Registers a handler for the FolderCreated event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - folder_name (string) Absolute path to the created folder.
--- @deprecated
function M.on_folder_created(handler)
register_handler(Event.FolderCreated, handler)
M.subscribe(M.Event.FolderCreated, handler)
end
--Registers a handler for the FolderRemoved event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - folder_name (string) Absolute path to the removed folder.
--- @deprecated
function M.on_folder_removed(handler)
register_handler(Event.FolderRemoved, handler)
M.subscribe(M.Event.FolderRemoved, handler)
end
--Registers a handler for the TreeOpen event.
--@param handler (function) Handler with the signature function(payload)
--- @deprecated
function M.on_tree_open(handler)
register_handler(Event.TreeOpen, handler)
M.subscribe(M.Event.TreeOpen, handler)
end
--Registers a handler for the TreeClose event.
--@param handler (function) Handler with the signature function(payload)
--- @deprecated
function M.on_tree_close(handler)
register_handler(Event.TreeClose, handler)
M.subscribe(M.Event.TreeClose, handler)
end
--- @deprecated
function M.on_tree_resize(handler)
M.subscribe(M.Event.Resize, handler)
end
return M

View File

@@ -1,14 +1,16 @@
local uv = vim.loop
local M = {}
local function get_dir_git_status(parent_ignored, status, absolute_path)
if parent_ignored then
return "!!"
end
local dir_status = status.dirs and status.dirs[absolute_path]
local file_status = status.files and status.files[absolute_path]
return dir_status or file_status
if file_status then
return file_status
end
return status.dirs and status.dirs[absolute_path]
end
local function get_git_status(parent_ignored, status, absolute_path)
@@ -16,7 +18,7 @@ local function get_git_status(parent_ignored, status, absolute_path)
end
function M.has_one_child_folder(node)
return #node.nodes == 1 and node.nodes[1].nodes and uv.fs_access(node.nodes[1].absolute_path, "R")
return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R")
end
function M.update_git_status(node, parent_ignored, status)
@@ -37,4 +39,36 @@ function M.update_git_status(node, parent_ignored, status)
end
end
function M.shows_git_status(node)
if not node.git_status then
-- status doesn't exist
return false
elseif not node.nodes then
-- status exist and is a file
return true
elseif not node.open then
-- status exist, is a closed dir
return M.config.git.show_on_dirs
else
-- status exist, is a open dir
return M.config.git.show_on_dirs and M.config.git.show_on_open_dirs
end
end
function M.node_destroy(node)
if not node then
return
end
if node.watcher then
node.watcher:destroy()
end
end
function M.setup(opts)
M.config = {
git = opts.git,
}
end
return M

View File

@@ -1,82 +1,92 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common"
local sorters = require "nvim-tree.explorer.sorters"
local filters = require "nvim-tree.explorer.filters"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify"
local log = require "nvim-tree.log"
local M = {}
local function get_type_from(type_, cwd)
return type_ or (uv.fs_stat(cwd) or {}).type
return type_ or (vim.loop.fs_stat(cwd) or {}).type
end
local function populate_children(handle, cwd, node, status)
local function populate_children(handle, cwd, node, git_status)
local node_ignored = node.git_status == "!!"
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
local filter_status = filters.prepare(git_status)
while true do
local name, t = uv.fs_scandir_next(handle)
local name, t = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
local abs = utils.path_join { cwd, name }
t = get_type_from(t, abs)
if
not filters.should_ignore(abs)
and not filters.should_ignore_git(abs, status.files)
and not nodes_by_path[abs]
then
if not filters.should_filter(abs, filter_status) and not nodes_by_path[abs] then
local child = nil
if t == "directory" and uv.fs_access(abs, "R") then
child = builders.folder(node, abs, name, status, node_ignored)
if t == "directory" and vim.loop.fs_access(abs, "R") then
child = builders.folder(node, abs, name)
elseif t == "file" then
child = builders.file(node, abs, name, status, node_ignored)
child = builders.file(node, abs, name)
elseif t == "link" then
local link = builders.link(node, abs, name, status, node_ignored)
local link = builders.link(node, abs, name)
if link.link_to ~= nil then
child = link
end
end
if child then
table.insert(node.nodes, child)
common.update_git_status(child, node_ignored, status)
nodes_by_path[child.absolute_path] = true
common.update_git_status(child, node_ignored, git_status)
end
end
end
end
local function get_dir_handle(cwd)
local handle = uv.fs_scandir(cwd)
local handle = vim.loop.fs_scandir(cwd)
if type(handle) == "string" then
api.nvim_err_writeln(handle)
notify.error(handle)
return
end
return handle
end
function M.explore(node, status)
local cwd = node.cwd or node.link_to or node.absolute_path
local cwd = node.link_to or node.absolute_path
local handle = get_dir_handle(cwd)
if not handle then
return
end
local pn = string.format("explore init %s", node.absolute_path)
local ps = log.profile_start(pn)
populate_children(handle, cwd, node, status)
local is_root = node.cwd ~= nil
local is_root = not node.parent
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
if M.config.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.explore(child_folder_only, status)
node.nodes = ns or {}
log.profile_end(ps, pn)
return ns
end
sorters.merge_sort(node.nodes, sorters.node_comparator)
live_filter.apply_filter(node)
log.profile_end(ps, pn)
return node.nodes
end
function M.setup(opts)
M.config = opts.renderer
end
return M

View File

@@ -14,26 +14,63 @@ local function is_excluded(path)
return false
end
---Check if the given path should be ignored.
---Check if the given path is git clean/ignored
---@param path string Absolute path
---@param git_status table from prepare
---@return boolean
function M.should_ignore(path)
local basename = utils.path_basename(path)
if is_excluded(path) then
local function git(path, git_status)
if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.dirs) ~= "table" then
return false
end
if M.config.filter_dotfiles then
if basename:sub(1, 1) == "." then
return true
-- default status to clean
local status = git_status.files[path] or git_status.dirs[path] or " "
-- filter ignored; overrides clean as they are effectively dirty
if M.config.filter_git_ignored and status == "!!" then
return true
end
-- filter clean
if M.config.filter_git_clean and status == " " then
return true
end
return false
end
---Check if the given path has no listed buffer
---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
---@return boolean
local function buf(path, bufinfo, unloaded_bufnr)
if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then
return false
end
-- filter files with no open buffer and directories containing no open buffers
for _, b in ipairs(bufinfo) do
if b.name == path or b.name:find(path .. "/", 1, true) and b.bufnr ~= unloaded_bufnr then
return false
end
end
return true
end
local function dotfile(path)
return M.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "."
end
local function custom(path)
if not M.config.filter_custom then
return false
end
local basename = utils.path_basename(path)
-- filter custom regexes
local relpath = utils.path_relative(path, vim.loop.cwd())
for pat, _ in pairs(M.ignore_list) do
if vim.fn.match(relpath, pat) ~= -1 or vim.fn.match(basename, pat) ~= -1 then
@@ -51,10 +88,41 @@ function M.should_ignore(path)
return false
end
function M.should_ignore_git(path, status)
return M.config.filter_git_ignored
and (M.config.filter_git_ignored and status and status[path] == "!!")
and not is_excluded(path)
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
---@param git_status table results of git.load_project_status(...)
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
---@return table
--- git_status: reference
--- unloaded_bufnr: copy
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
function M.prepare(git_status, unloaded_bufnr)
local status = {
git_status = git_status or {},
unloaded_bufnr = unloaded_bufnr,
bufinfo = {},
}
if M.config.filter_no_buffer then
status.bufinfo = vim.fn.getbufinfo { buflisted = 1 }
end
return status
end
---Check if the given path should be filtered.
---@param path string Absolute path
---@param status table from prepare
---@return boolean
function M.should_filter(path, status)
-- exclusions override all filters
if is_excluded(path) then
return false
end
return git(path, status.git_status)
or buf(path, status.bufinfo, status.unloaded_bufnr)
or dotfile(path)
or custom(path)
end
function M.setup(opts)
@@ -62,8 +130,11 @@ function M.setup(opts)
filter_custom = true,
filter_dotfiles = opts.filters.dotfiles,
filter_git_ignored = opts.git.ignore,
filter_git_clean = opts.filters.git_clean,
filter_no_buffer = opts.filters.no_buffer,
}
M.ignore_list = {}
M.exclude_list = opts.filters.exclude
local custom_filter = opts.filters.custom

View File

@@ -1,6 +1,6 @@
local uv = vim.loop
local git = require "nvim-tree.git"
local watch = require "nvim-tree.explorer.watch"
local common = require "nvim-tree.explorer.common"
local M = {}
@@ -11,17 +11,19 @@ local Explorer = {}
Explorer.__index = Explorer
function Explorer.new(cwd)
cwd = uv.fs_realpath(cwd or uv.cwd())
cwd = vim.loop.fs_realpath(cwd or vim.loop.cwd())
local explorer = setmetatable({
cwd = cwd,
absolute_path = cwd,
nodes = {},
open = true,
}, Explorer)
explorer.watcher = watch.create_watcher(explorer)
explorer:_load(explorer)
return explorer
end
function Explorer:_load(node)
local cwd = node.cwd or node.link_to or node.absolute_path
local cwd = node.link_to or node.absolute_path
local git_statuses = git.load_project_status(cwd)
M.explore(node, git_statuses)
end
@@ -30,9 +32,25 @@ function Explorer:expand(node)
self:_load(node)
end
function Explorer:destroy()
local function iterate(node)
common.node_destroy(node)
if node.nodes then
for _, child in pairs(node.nodes) do
iterate(child)
end
end
end
iterate(self)
end
function M.setup(opts)
require("nvim-tree.explorer.common").setup(opts)
require("nvim-tree.explorer.explore").setup(opts)
require("nvim-tree.explorer.filters").setup(opts)
require("nvim-tree.explorer.sorters").setup(opts)
require("nvim-tree.explorer.reload").setup(opts)
require("nvim-tree.explorer.watch").setup(opts)
end
M.Explorer = Explorer

View File

@@ -1,17 +1,19 @@
local uv = vim.loop
local utils = require "nvim-tree.utils"
local watch = require "nvim-tree.explorer.watch"
local M = {
is_windows = vim.fn.has "win32" == 1,
is_wsl = vim.fn.has "wsl" == 1,
}
function M.folder(parent, absolute_path, name)
local handle = uv.fs_scandir(absolute_path)
local has_children = handle and uv.fs_scandir_next(handle) ~= nil
local handle = vim.loop.fs_scandir(absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
return {
local node = {
type = "directory",
absolute_path = absolute_path,
fs_stat = uv.fs_stat(absolute_path),
fs_stat = vim.loop.fs_stat(absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
name = name,
@@ -19,23 +21,38 @@ function M.folder(parent, absolute_path, name)
open = false,
parent = parent,
}
node.watcher = watch.create_watcher(node)
return node
end
local function is_executable(absolute_path, ext)
function M.is_executable(parent, absolute_path, ext)
if M.is_windows then
return utils.is_windows_exe(ext)
elseif M.is_wsl then
if parent.is_wsl_windows_fs_path == nil then
-- Evaluate lazily when needed and do so only once for each parent
-- as 'wslpath' calls can get expensive in highly populated directories.
parent.is_wsl_windows_fs_path = utils.is_wsl_windows_fs_path(absolute_path)
end
if parent.is_wsl_windows_fs_path then
return utils.is_wsl_windows_fs_exe(ext)
end
end
return uv.fs_access(absolute_path, "X")
return vim.loop.fs_access(absolute_path, "X")
end
function M.file(parent, absolute_path, name)
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
return {
type = "file",
absolute_path = absolute_path,
executable = is_executable(absolute_path, ext),
executable = M.is_executable(parent, absolute_path, ext),
extension = ext,
fs_stat = uv.fs_stat(absolute_path),
fs_stat = vim.loop.fs_stat(absolute_path),
name = name,
parent = parent,
}
@@ -47,19 +64,23 @@ end
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
-- So we need to check for link_to ~= nil when adding new links to the main tree
function M.link(parent, absolute_path, name)
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it.
local link_to = uv.fs_realpath(absolute_path)
--- I dont know if this is needed, because in my understanding, there isn't hard links in windows, but just to be sure i changed it.
local link_to = vim.loop.fs_realpath(absolute_path)
local open, nodes, has_children
if (link_to ~= nil) and uv.fs_stat(link_to).type == "directory" then
local handle = uv.fs_scandir(link_to)
has_children = handle and uv.fs_scandir_next(handle) ~= nil
local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"
if is_dir_link then
local handle = vim.loop.fs_scandir(link_to)
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
open = false
nodes = {}
end
return {
local node = {
type = "link",
absolute_path = absolute_path,
fs_stat = uv.fs_stat(absolute_path),
fs_stat = vim.loop.fs_stat(absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
link_to = link_to,
@@ -68,6 +89,12 @@ function M.link(parent, absolute_path, name)
open = open,
parent = parent,
}
if is_dir_link then
node.watcher = watch.create_watcher(node)
end
return node
end
return M

View File

@@ -1,11 +1,14 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common"
local filters = require "nvim-tree.explorer.filters"
local sorters = require "nvim-tree.explorer.sorters"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify"
local git = require "nvim-tree.git"
local log = require "nvim-tree.log"
local NodeIterator = require "nvim-tree.iterators.node-iterator"
local M = {}
@@ -18,14 +21,31 @@ local function update_status(nodes_by_path, node_ignored, status)
end
end
function M.reload(node, status)
local cwd = node.cwd or node.link_to or node.absolute_path
local handle = uv.fs_scandir(cwd)
local function reload_and_get_git_project(path)
local project_root = git.get_project_root(path)
git.reload_project(project_root, path)
return project_root, git.get_project(project_root) or {}
end
local function update_parent_statuses(node, project, root)
while project and node and node.absolute_path ~= root do
common.update_git_status(node, false, project)
node = node.parent
end
end
function M.reload(node, git_status, unloaded_bufnr)
local cwd = node.link_to or node.absolute_path
local handle = vim.loop.fs_scandir(cwd)
if type(handle) == "string" then
api.nvim_err_writeln(handle)
notify.error(handle)
return
end
local ps = log.profile_start("reload %s", node.absolute_path)
local filter_status = filters.prepare(git_status, unloaded_bufnr)
if node.group_next then
node.nodes = { node.group_next }
node.group_next = nil
@@ -36,48 +56,142 @@ function M.reload(node, status)
local node_ignored = node.git_status == "!!"
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
while true do
local name, t = uv.fs_scandir_next(handle)
if not name then
local ok, name, t = pcall(vim.loop.fs_scandir_next, handle)
if not ok or not name then
break
end
local stat
local function fs_stat_cached(path)
if stat ~= nil then
return stat
end
stat = vim.loop.fs_stat(path)
return stat
end
local abs = utils.path_join { cwd, name }
t = t or (uv.fs_stat(abs) or {}).type
if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then
t = t or (fs_stat_cached(abs) or {}).type
if not filters.should_filter(abs, filter_status) then
child_names[abs] = true
-- Recreate node if type changes.
if nodes_by_path[abs] then
local n = nodes_by_path[abs]
if n.type ~= t then
utils.array_remove(node.nodes, n)
common.node_destroy(n)
nodes_by_path[abs] = nil
end
end
if not nodes_by_path[abs] then
if t == "directory" and uv.fs_access(abs, "R") then
table.insert(node.nodes, builders.folder(node, abs, name, status, node_ignored))
if t == "directory" and vim.loop.fs_access(abs, "R") then
local folder = builders.folder(node, abs, name)
nodes_by_path[abs] = folder
table.insert(node.nodes, folder)
elseif t == "file" then
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored))
local file = builders.file(node, abs, name)
nodes_by_path[abs] = file
table.insert(node.nodes, file)
elseif t == "link" then
local link = builders.link(node, abs, name, status, node_ignored)
local link = builders.link(node, abs, name)
if link.link_to ~= nil then
nodes_by_path[abs] = link
table.insert(node.nodes, link)
end
end
else
local n = nodes_by_path[abs]
if n then
n.executable = builders.is_executable(n.parent, abs, n.extension or "")
n.fs_stat = fs_stat_cached(abs)
end
end
end
end
node.nodes = vim.tbl_map(
update_status(nodes_by_path, node_ignored, status),
update_status(nodes_by_path, node_ignored, git_status),
vim.tbl_filter(function(n)
return child_names[n.absolute_path]
if child_names[n.absolute_path] then
return child_names[n.absolute_path]
else
common.node_destroy(n)
return nil
end
end, node.nodes)
)
local is_root = node.cwd ~= nil
local is_root = not node.parent
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
if M.config.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.reload(child_folder_only, status)
local ns = M.reload(child_folder_only, git_status)
node.nodes = ns or {}
log.profile_end(ps, "reload %s", node.absolute_path)
return ns
end
sorters.merge_sort(node.nodes, sorters.node_comparator)
live_filter.apply_filter(node)
log.profile_end(ps, "reload %s", node.absolute_path)
return node.nodes
end
---Refresh contents and git status for a single node
---@param node table
function M.refresh_node(node)
if type(node) ~= "table" then
return
end
local parent_node = utils.get_parent_of_group(node)
local project_root, project = reload_and_get_git_project(node.absolute_path)
require("nvim-tree.explorer.reload").reload(parent_node, project)
update_parent_statuses(parent_node, project, project_root)
end
---Refresh contents and git status for all nodes to a path: actual directory and links
---@param path string absolute path
function M.refresh_nodes_for_path(path)
local explorer = require("nvim-tree.core").get_explorer()
if not explorer then
return
end
local pn = string.format("refresh_nodes_for_path %s", path)
local ps = log.profile_start(pn)
NodeIterator.builder({ explorer })
:hidden()
:recursor(function(node)
if node.group_next then
return { node.group_next }
end
if node.nodes then
return node.nodes
end
end)
:applier(function(node)
local abs_contains = node.absolute_path and path:match("^" .. node.absolute_path)
local link_contains = node.link_to and path:match("^" .. node.link_to)
if abs_contains or link_contains then
M.refresh_node(node)
end
end)
:iterate()
log.profile_end(ps, pn)
end
function M.setup(opts)
M.config = opts.renderer
end
return M

View File

@@ -66,13 +66,52 @@ end
---@param t any[]
---@param comparator function|nil
function M.merge_sort(t, comparator)
if not comparator then
comparator = function(left, right)
return left < right
end
end
if type(M.sort_by) == "function" then
local t_user = {}
local origin_index = {}
split_merge(t, 1, #t, comparator)
for _, n in ipairs(t) do
table.insert(t_user, {
absolute_path = n.absolute_path,
executable = n.executable,
extension = n.extension,
link_to = n.link_to,
name = n.name,
type = n.type,
})
table.insert(origin_index, n)
end
M.sort_by(t_user)
-- do merge sort for prevent memory exceed
local user_index = {}
for i, v in ipairs(t_user) do
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
user_index[v.absolute_path] = i
end
end
-- if missing value found, then using origin_index
local mini_comparator = function(a, b)
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
if type(a_index) == "number" and type(b_index) == "number" then
return a_index <= b_index
end
return (a_index or 0) <= (b_index or 0)
end
split_merge(t, 1, #t, mini_comparator) -- sort by user order
else
if not comparator then
comparator = function(left, right)
return left < right
end
end
split_merge(t, 1, #t, comparator)
end
end
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
@@ -124,12 +163,40 @@ function M.node_comparator_modification_time(a, b)
return last_modified_b <= last_modified_a
end
function M.node_comparator_extension(a, b)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
if not (a.extension and b.extension) then
return true
end
if a.extension and not b.extension then
return true
elseif not a.extension and b.extension then
return false
end
return a.extension:lower() <= b.extension:lower()
end
function M.setup(opts)
M.sort_by = opts.sort_by
if M.sort_by == "modification_time" then
if M.sort_by and type(M.sort_by) == "function" then
M.node_comparator = M.sort_by
elseif M.sort_by == "modification_time" then
M.node_comparator = M.node_comparator_modification_time
elseif M.sort_by == "case_sensitive" then
M.node_comparator = M.node_comparator_name_case_sensisive
elseif M.sort_by == "extension" then
M.node_comparator = M.node_comparator_extension
else
M.node_comparator = M.node_comparator_name_ignorecase
end

View File

@@ -0,0 +1,77 @@
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local Watcher = require("nvim-tree.watcher").Watcher
local M = {}
local function is_git(path)
return vim.fn.fnamemodify(path, ":t") == ".git"
end
local IGNORED_PATHS = {
-- disable watchers on kernel filesystems
-- which have a lot of unwanted events
"/sys",
"/proc",
"/dev",
}
local function is_folder_ignored(path)
for _, folder in ipairs(IGNORED_PATHS) do
if vim.startswith(path, folder) then
return true
end
end
for _, ignore_dir in ipairs(M.ignore_dirs) do
if vim.fn.match(path, ignore_dir) ~= -1 then
return true
end
end
return false
end
function M.create_watcher(node)
if not M.enabled or type(node) ~= "table" then
return nil
end
local path
if node.type == "link" then
path = node.link_to
else
path = node.absolute_path
end
if is_git(path) or is_folder_ignored(path) then
return nil
end
local function callback(watcher)
log.line("watcher", "node event scheduled refresh %s", watcher.context)
utils.debounce(watcher.context, M.debounce_delay, function()
if node.link_to then
log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path)
else
log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
end
require("nvim-tree.explorer.reload").refresh_node(node)
require("nvim-tree.renderer").draw()
end)
end
M.uid = M.uid + 1
return Watcher:new(path, nil, callback, {
context = "explorer:watch:" .. path .. ":" .. M.uid,
})
end
function M.setup(opts)
M.enabled = opts.filesystem_watchers.enable
M.debounce_delay = opts.filesystem_watchers.debounce_delay
M.ignore_dirs = opts.filesystem_watchers.ignore_dirs
M.uid = 0
end
return M

View File

@@ -1,35 +1,79 @@
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local git_utils = require "nvim-tree.git.utils"
local Runner = require "nvim-tree.git.runner"
local Watcher = require("nvim-tree.watcher").Watcher
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {
config = nil,
config = {},
projects = {},
cwd_to_project_root = {},
}
-- Files under .git that should result in a reload when changed.
-- Utilities (like watchman) can also write to this directory (often) and aren't useful for us.
local WATCHED_FILES = {
"FETCH_HEAD", -- remote ref
"HEAD", -- local ref
"HEAD.lock", -- HEAD will not always be updated e.g. revert
"config", -- user config
"index", -- staging area
}
function M.reload()
if not M.config.enable then
if not M.config.git.enable then
return {}
end
for project_root in pairs(M.projects) do
M.projects[project_root] = {}
local git_status = Runner.run {
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true,
timeout = M.config.timeout,
}
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
}
M.reload_project(project_root)
end
return M.projects
end
function M.reload_project(project_root, path)
local project = M.projects[project_root]
if not project or not M.config.git.enable then
return
end
if path and path:find(project_root, 1, true) ~= 1 then
return
end
local git_status = Runner.run {
project_root = project_root,
path = path,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true,
timeout = M.config.git.timeout,
}
if path then
for p in pairs(project.files) do
if p:find(path, 1, true) == 1 then
project.files[p] = nil
end
end
project.files = vim.tbl_deep_extend("force", project.files, git_status)
else
project.files = git_status
end
project.dirs = git_utils.file_status_to_dir_status(project.files, project_root)
end
function M.get_project(project_root)
return M.projects[project_root]
end
function M.get_project_root(cwd)
if not M.config.git.enable then
return nil
end
if M.cwd_to_project_root[cwd] then
return M.cwd_to_project_root[cwd]
end
@@ -38,12 +82,51 @@ function M.get_project_root(cwd)
return nil
end
local project_root = git_utils.get_toplevel(cwd)
return project_root
local stat, _ = vim.loop.fs_stat(cwd)
if not stat or stat.type ~= "directory" then
return nil
end
M.cwd_to_project_root[cwd] = git_utils.get_toplevel(cwd)
return M.cwd_to_project_root[cwd]
end
local function reload_tree_at(project_root)
if not M.config.git.enable then
return nil
end
log.line("watcher", "git event executing '%s'", project_root)
local root_node = utils.get_node_from_path(project_root)
if not root_node then
return
end
M.reload_project(project_root)
local project = M.get_project(project_root)
local project_files = project.files and project.files or {}
local project_dirs = project.dirs and project.dirs or {}
Iterator.builder(root_node.nodes)
:hidden()
:applier(function(node)
local parent_ignored = node.parent.git_status == "!!"
node.git_status = project_dirs[node.absolute_path] or project_files[node.absolute_path]
if not node.git_status and parent_ignored then
node.git_status = "!!"
end
end)
:recursor(function(node)
return node.nodes and #node.nodes > 0 and node.nodes
end)
:iterate()
require("nvim-tree.renderer").draw()
end
function M.load_project_status(cwd)
if not M.config.enable then
if not M.config.git.enable then
return {}
end
@@ -62,17 +145,42 @@ function M.load_project_status(cwd)
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true,
timeout = M.config.timeout,
timeout = M.config.git.timeout,
}
local watcher = nil
if M.config.filesystem_watchers.enable then
log.line("watcher", "git start")
local callback = function(w)
log.line("watcher", "git event scheduled '%s'", w.project_root)
utils.debounce("git:watcher:" .. w.project_root, M.config.filesystem_watchers.debounce_delay, function()
reload_tree_at(w.project_root)
end)
end
watcher = Watcher:new(utils.path_join { project_root, ".git" }, WATCHED_FILES, callback, {
project_root = project_root,
})
end
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
watcher = watcher,
}
return M.projects[project_root]
end
function M.purge_state()
log.line("git", "purge_state")
M.projects = {}
M.cwd_to_project_root = {}
end
function M.setup(opts)
M.config = opts.git
M.config.git = opts.git
M.config.filesystem_watchers = opts.filesystem_watchers
end
return M

View File

@@ -1,14 +1,10 @@
local uv = vim.loop
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local Runner = {}
Runner.__index = Runner
function Runner:_parse_status_output(line)
local status = line:sub(1, 2)
-- removing `"` when git is returning special file status containing spaces
local path = line:sub(4, -2):gsub('^"', ""):gsub('"$', "")
function Runner:_parse_status_output(status, path)
-- replacing slashes if on windows
if vim.fn.has "win32" == 1 then
path = path:gsub("/", "\\")
@@ -16,15 +12,26 @@ function Runner:_parse_status_output(line)
if #status > 0 and #path > 0 then
self.output[utils.path_remove_trailing(utils.path_join { self.project_root, path })] = status
end
return #line
end
function Runner:_handle_incoming_data(prev_output, incoming)
if incoming and utils.str_find(incoming, "\n") then
local prev = prev_output .. incoming
local i = 1
local skip_next_line = false
for line in prev:gmatch "[^\n]*\n" do
i = i + self:_parse_status_output(line)
if skip_next_line then
skip_next_line = false
else
local status = line:sub(1, 2)
local path = line:sub(4, -2)
if utils.str_find(status, "R") then
-- skip next line if it is a rename entry
skip_next_line = true
end
self:_parse_status_output(status, path)
end
i = i + #line
end
return prev:sub(i, -1)
@@ -45,7 +52,7 @@ function Runner:_getopts(stdout_handle, stderr_handle)
local untracked = self.list_untracked and "-u" or nil
local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no"
return {
args = { "--no-optional-locks", "status", "--porcelain=v1", ignored, untracked },
args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path },
cwd = self.project_root,
stdio = { nil, stdout_handle, stderr_handle },
}
@@ -54,14 +61,15 @@ end
function Runner:_log_raw_output(output)
if output and type(output) == "string" then
log.raw("git", "%s", output)
log.line("git", "done")
end
end
function Runner:_run_git_job()
local handle, pid
local stdout = uv.new_pipe(false)
local stderr = uv.new_pipe(false)
local timer = uv.new_timer()
local stdout = vim.loop.new_pipe(false)
local stderr = vim.loop.new_pipe(false)
local timer = vim.loop.new_timer()
local function on_finish(rc)
self.rc = rc or 0
@@ -78,14 +86,14 @@ function Runner:_run_git_job()
handle:close()
end
pcall(uv.kill, pid)
pcall(vim.loop.kill, pid)
end
local opts = self:_getopts(stdout, stderr)
log.line("git", "running job with timeout %dms", self.timeout)
log.line("git", "git %s", table.concat(opts.args, " "))
log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " "))
handle, pid = uv.spawn(
handle, pid = vim.loop.spawn(
"git",
opts,
vim.schedule_wrap(function(rc)
@@ -106,6 +114,9 @@ function Runner:_run_git_job()
if err then
return
end
if data then
data = data:gsub("%z", "\n")
end
self:_log_raw_output(data)
output_leftover = self:_handle_incoming_data(output_leftover, data)
end
@@ -114,24 +125,26 @@ function Runner:_run_git_job()
self:_log_raw_output(data)
end
uv.read_start(stdout, vim.schedule_wrap(manage_stdout))
uv.read_start(stderr, vim.schedule_wrap(manage_stderr))
vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout))
vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr))
end
function Runner:_wait()
local function is_done()
return self.rc ~= nil
end
while not vim.wait(30, is_done) do
end
end
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
function Runner.run(opts)
local ps = log.profile_start("git job %s", opts.project_root)
local ps = log.profile_start("git job %s %s", opts.project_root, opts.path)
local self = setmetatable({
project_root = opts.project_root,
path = opts.path,
list_untracked = opts.list_untracked,
list_ignored = opts.list_ignored,
timeout = opts.timeout or 400,
@@ -142,14 +155,14 @@ function Runner.run(opts)
self:_run_git_job()
self:_wait()
log.profile_end(ps, "git job %s", opts.project_root)
log.profile_end(ps, "git job %s %s", opts.project_root, opts.path)
if self.rc == -1 then
log.line("git", "job timed out")
log.line("git", "job timed out %s %s", opts.project_root, opts.path)
elseif self.rc ~= 0 then
log.line("git", "job failed with return code %d", self.rc)
log.line("git", "job fail rc %d %s %s", self.rc, opts.project_root, opts.path)
else
log.line("git", "job success")
log.line("git", "job success %s %s", opts.project_root, opts.path)
end
return self.output

View File

@@ -1,15 +1,32 @@
local M = {}
local log = require "nvim-tree.log"
local has_cygpath = vim.fn.executable "cygpath" == 1
function M.get_toplevel(cwd)
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel"
local ps = log.profile_start("git toplevel %s", cwd)
local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel" }
log.line("git", "%s", vim.inspect(cmd))
local toplevel = vim.fn.system(cmd)
if not toplevel or #toplevel == 0 or toplevel:match "fatal" then
log.raw("git", toplevel)
log.profile_end(ps, "git toplevel %s", cwd)
if vim.v.shell_error ~= 0 or not toplevel or #toplevel == 0 or toplevel:match "fatal" then
return nil
end
-- git always returns path with forward slashes
if vim.fn.has "win32" == 1 then
-- msys2 git support
if has_cygpath then
toplevel = vim.fn.system("cygpath -w " .. vim.fn.shellescape(toplevel))
if vim.v.shell_error ~= 0 then
return nil
end
end
toplevel = toplevel:gsub("/", "\\")
end
@@ -24,9 +41,17 @@ function M.should_show_untracked(cwd)
return untracked[cwd]
end
local cmd = "git -C " .. cwd .. " config --type=bool status.showUntrackedFiles"
local ps = log.profile_start("git untracked %s", cwd)
local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" }
log.line("git", vim.inspect(cmd))
local has_untracked = vim.fn.system(cmd)
untracked[cwd] = vim.trim(has_untracked) ~= "false"
log.raw("git", has_untracked)
log.profile_end(ps, "git untracked %s", cwd)
untracked[cwd] = vim.trim(has_untracked) ~= "no"
return untracked[cwd]
end

View File

@@ -0,0 +1,65 @@
local NodeIterator = {}
NodeIterator.__index = NodeIterator
function NodeIterator.builder(nodes)
return setmetatable({
nodes = nodes,
_filter_hidden = function(node)
return not node.hidden
end,
_apply_fn_on_node = function(_) end,
_match = function(_) end,
_recurse_with = function(node)
return node.nodes
end,
}, NodeIterator)
end
function NodeIterator:hidden()
self._filter_hidden = function(_)
return true
end
return self
end
function NodeIterator:matcher(f)
self._match = f
return self
end
function NodeIterator:applier(f)
self._apply_fn_on_node = f
return self
end
function NodeIterator:recursor(f)
self._recurse_with = f
return self
end
function NodeIterator:iterate()
local iteration_count = 0
local function iter(nodes)
for _, node in ipairs(nodes) do
if self._filter_hidden(node) then
iteration_count = iteration_count + 1
if self._match(node) then
return node, iteration_count
end
self._apply_fn_on_node(node, iteration_count)
local children = self._recurse_with(node)
if children then
local n = iter(children)
if n then
return n, iteration_count
end
end
end
end
return nil, 0
end
return iter(self.nodes)
end
return NodeIterator

290
lua/nvim-tree/keymap.lua Normal file
View File

@@ -0,0 +1,290 @@
local Api = require "nvim-tree.api"
local M = {}
local DEFAULT_KEYMAPS = {
{
key = { "<CR>", "o", "<2-LeftMouse>" },
callback = Api.node.open.edit,
desc = "open a file or folder; root will cd to the above directory",
},
{
key = "<C-e>",
callback = Api.node.open.replace_tree_buffer,
desc = "edit the file in place, effectively replacing the tree explorer",
},
{
key = "O",
callback = Api.node.open.no_window_picker,
desc = "same as (edit) with no window picker",
},
{
key = { "<C-]>", "<2-RightMouse>" },
callback = Api.tree.change_root_to_node,
desc = "cd in the directory under the cursor",
},
{
key = "<C-v>",
callback = Api.node.open.vertical,
desc = "open the file in a vertical split",
},
{
key = "<C-x>",
callback = Api.node.open.horizontal,
desc = "open the file in a horizontal split",
},
{
key = "<C-t>",
callback = Api.node.open.tab,
desc = "open the file in a new tab",
},
{
key = "<",
callback = Api.node.navigate.sibling.prev,
desc = "navigate to the previous sibling of current file/directory",
},
{
key = ">",
callback = Api.node.navigate.sibling.next,
desc = "navigate to the next sibling of current file/directory",
},
{
key = "P",
callback = Api.node.navigate.parent,
desc = "move cursor to the parent directory",
},
{
key = "<BS>",
callback = Api.node.navigate.parent_close,
desc = "close current opened directory or parent",
},
{
key = "<Tab>",
callback = Api.node.open.preview,
desc = "open the file as a preview (keeps the cursor in the tree)",
},
{
key = "K",
callback = Api.node.navigate.sibling.first,
desc = "navigate to the first sibling of current file/directory",
},
{
key = "J",
callback = Api.node.navigate.sibling.last,
desc = "navigate to the last sibling of current file/directory",
},
{
key = "I",
callback = Api.tree.toggle_gitignore_filter,
desc = "toggle visibility of files/folders hidden via |git.ignore| option",
},
{
key = "H",
callback = Api.tree.toggle_hidden_filter,
desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
},
{
key = "U",
callback = Api.tree.toggle_custom_filter,
desc = "toggle visibility of files/folders hidden via |filters.custom| option",
},
{
key = "R",
callback = Api.tree.reload,
desc = "refresh the tree",
},
{
key = "a",
callback = Api.fs.create,
desc = "add a file; leaving a trailing `/` will add a directory",
},
{
key = "d",
callback = Api.fs.remove,
desc = "delete a file (will prompt for confirmation)",
},
{
key = "D",
callback = Api.fs.trash,
desc = "trash a file via |trash| option",
},
{
key = "r",
callback = Api.fs.rename,
desc = "rename a file",
},
{
key = "<C-r>",
callback = Api.fs.rename_sub,
desc = "rename a file and omit the filename on input",
},
{
key = "x",
callback = Api.fs.cut,
desc = "add/remove file/directory to cut clipboard",
},
{
key = "c",
callback = Api.fs.copy.node,
desc = "add/remove file/directory to copy clipboard",
},
{
key = "p",
callback = Api.fs.paste,
desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
},
{
key = "y",
callback = Api.fs.copy.filename,
desc = "copy name to system clipboard",
},
{
key = "Y",
callback = Api.fs.copy.relative_path,
desc = "copy relative path to system clipboard",
},
{
key = "gy",
callback = Api.fs.copy.absolute_path,
desc = "copy absolute path to system clipboard",
},
{
key = "]e",
callback = Api.node.navigate.diagnostics.next,
desc = "go to next diagnostic item",
},
{
key = "]c",
callback = Api.node.navigate.git.next,
desc = "go to next git item",
},
{
key = "[e",
callback = Api.node.navigate.diagnostics.prev,
desc = "go to prev diagnostic item",
},
{
key = "[c",
callback = Api.node.navigate.git.prev,
desc = "go to prev git item",
},
{
key = "-",
callback = Api.tree.change_root_to_parent,
desc = "navigate up to the parent directory of the current file/directory",
},
{
key = "s",
callback = Api.node.run.system,
desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
},
{
key = "f",
callback = Api.live_filter.start,
desc = "live filter nodes dynamically based on regex matching.",
},
{
key = "F",
callback = Api.live_filter.clear,
desc = "clear live filter",
},
{
key = "q",
callback = Api.tree.close,
desc = "close tree window",
},
{
key = "W",
callback = Api.tree.collapse_all,
desc = "collapse the whole tree",
},
{
key = "E",
callback = Api.tree.expand_all,
desc = "expand the whole tree, stopping after expanding |callbacks.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
},
{
key = "S",
callback = Api.tree.search_node,
desc = "prompt the user to enter a path and then expands the tree to match the path",
},
{
key = ".",
callback = Api.node.run.cmd,
desc = "enter vim command mode with the file the cursor is on",
},
{
key = "<C-k>",
callback = Api.node.show_info_popup,
desc = "toggle a popup with file infos about the file under the cursor",
},
{
key = "g?",
callback = Api.tree.toggle_help,
desc = "toggle help",
},
{
key = "m",
callback = Api.marks.toggle,
desc = "Toggle node in bookmarks",
},
{
key = "bmv",
callback = Api.marks.bulk.move,
desc = "Move all bookmarked nodes into specified location",
},
}
function M.set_keymaps(bufnr)
local opts = { noremap = true, silent = true, nowait = true, buffer = bufnr }
for _, km in ipairs(M.keymaps) do
local keys = type(km.key) == "table" and km.key or { km.key }
for _, key in ipairs(keys) do
vim.keymap.set("n", key, km.callback, opts)
end
end
end
local function filter_default_mappings(keys_to_disable)
local new_map = {}
for _, m in pairs(DEFAULT_KEYMAPS) do
local keys = type(m.key) == "table" and m.key or { m.key }
local reminding_keys = {}
for _, key in pairs(keys) do
local found = false
for _, key_to_disable in pairs(keys_to_disable) do
if key_to_disable == key then
found = true
break
end
end
if not found then
table.insert(reminding_keys, key)
end
end
if #reminding_keys > 0 then
local map = vim.deepcopy(m)
map.key = reminding_keys
table.insert(new_map, map)
end
end
return new_map
end
local function get_keymaps(keys_to_disable)
if keys_to_disable == true then
return {}
end
if type(keys_to_disable) == "table" and #keys_to_disable > 0 then
return filter_default_mappings(keys_to_disable)
end
return DEFAULT_KEYMAPS
end
function M.setup(opts)
M.keymaps = get_keymaps(opts.remove_keymaps)
end
return M

View File

@@ -1,183 +1,8 @@
local utils = require "nvim-tree.utils"
local notify = require "nvim-tree.notify"
local M = {}
-- TODO update bit.ly/3vIpEOJ when adding a migration
-- migrate the g: to o if the user has not specified that when calling setup
local g_migrations = {
nvim_tree_disable_netrw = function(o)
if o.disable_netrw == nil then
o.disable_netrw = vim.g.nvim_tree_disable_netrw ~= 0
end
end,
nvim_tree_hijack_netrw = function(o)
if o.hijack_netrw == nil then
o.hijack_netrw = vim.g.nvim_tree_hijack_netrw ~= 0
end
end,
nvim_tree_auto_open = function(o)
if o.open_on_setup == nil then
o.open_on_setup = vim.g.nvim_tree_auto_open ~= 0
end
end,
nvim_tree_tab_open = function(o)
if o.open_on_tab == nil then
o.open_on_tab = vim.g.nvim_tree_tab_open ~= 0
end
end,
nvim_tree_update_cwd = function(o)
if o.update_cwd == nil then
o.update_cwd = vim.g.nvim_tree_update_cwd ~= 0
end
end,
nvim_tree_hijack_cursor = function(o)
if o.hijack_cursor == nil then
o.hijack_cursor = vim.g.nvim_tree_hijack_cursor ~= 0
end
end,
nvim_tree_system_open_command = function(o)
utils.table_create_missing(o, "system_open")
if o.system_open.cmd == nil then
o.system_open.cmd = vim.g.nvim_tree_system_open_command
end
end,
nvim_tree_system_open_command_args = function(o)
utils.table_create_missing(o, "system_open")
if o.system_open.args == nil then
o.system_open.args = vim.g.nvim_tree_system_open_command_args
end
end,
nvim_tree_follow = function(o)
utils.table_create_missing(o, "update_focused_file")
if o.update_focused_file.enable == nil then
o.update_focused_file.enable = vim.g.nvim_tree_follow ~= 0
end
end,
nvim_tree_follow_update_path = function(o)
utils.table_create_missing(o, "update_focused_file")
if o.update_focused_file.update_cwd == nil then
o.update_focused_file.update_cwd = vim.g.nvim_tree_follow_update_path ~= 0
end
end,
nvim_tree_lsp_diagnostics = function(o)
utils.table_create_missing(o, "diagnostics")
if o.diagnostics.enable == nil then
o.diagnostics.enable = vim.g.nvim_tree_lsp_diagnostics ~= 0
if o.diagnostics.show_on_dirs == nil then
o.diagnostics.show_on_dirs = vim.g.nvim_tree_lsp_diagnostics ~= 0
end
end
end,
nvim_tree_auto_resize = function(o)
utils.table_create_missing(o, "actions.open_file")
if o.actions.open_file.resize_window == nil then
o.actions.open_file.resize_window = vim.g.nvim_tree_auto_resize ~= 0
end
end,
nvim_tree_bindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.list == nil then
o.view.mappings.list = vim.g.nvim_tree_bindings
end
end,
nvim_tree_disable_keybindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.custom_only == nil then
if vim.g.nvim_tree_disable_keybindings ~= 0 then
o.view.mappings.custom_only = true
-- specify one mapping so that defaults do not apply
o.view.mappings.list = {
{ key = "g?", action = "" },
}
end
end
end,
nvim_tree_disable_default_keybindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.custom_only == nil then
o.view.mappings.custom_only = vim.g.nvim_tree_disable_default_keybindings ~= 0
end
end,
nvim_tree_hide_dotfiles = function(o)
utils.table_create_missing(o, "filters")
if o.filters.dotfiles == nil then
o.filters.dotfiles = vim.g.nvim_tree_hide_dotfiles ~= 0
end
end,
nvim_tree_ignore = function(o)
utils.table_create_missing(o, "filters")
if o.filters.custom == nil then
o.filters.custom = vim.g.nvim_tree_ignore
end
end,
nvim_tree_gitignore = function(o)
utils.table_create_missing(o, "git")
if o.git.ignore == nil then
o.git.ignore = vim.g.nvim_tree_gitignore ~= 0
end
end,
nvim_tree_disable_window_picker = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.enable == nil then
o.actions.open_file.window_picker.enable = vim.g.nvim_tree_disable_window_picker == 0
end
end,
nvim_tree_window_picker_chars = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.chars == nil then
o.actions.open_file.window_picker.chars = vim.g.nvim_tree_window_picker_chars
end
end,
nvim_tree_window_picker_exclude = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.exclude == nil then
o.actions.open_file.window_picker.exclude = vim.g.nvim_tree_window_picker_exclude
end
end,
nvim_tree_quit_on_open = function(o)
utils.table_create_missing(o, "actions.open_file")
if o.actions.open_file.quit_on_open == nil then
o.actions.open_file.quit_on_open = vim.g.nvim_tree_quit_on_open == 1
end
end,
nvim_tree_change_dir_global = function(o)
utils.table_create_missing(o, "actions.change_dir")
if o.actions.change_dir.global == nil then
o.actions.change_dir.global = vim.g.nvim_tree_change_dir_global == 1
end
end,
nvim_tree_indent_markers = function(o)
utils.table_create_missing(o, "renderer.indent_markers")
if o.renderer.indent_markers.enable == nil then
o.renderer.indent_markers.enable = vim.g.nvim_tree_indent_markers == 1
end
end,
}
local function refactored(opts)
-- mapping actions
if opts.view and opts.view.mappings and opts.view.mappings.list then
@@ -188,48 +13,37 @@ local function refactored(opts)
end
end
-- update_to_buf_dir -> hijack_directories
if opts.update_to_buf_dir ~= nil then
utils.table_create_missing(opts, "hijack_directories")
if opts.hijack_directories.enable == nil then
opts.hijack_directories.enable = opts.update_to_buf_dir.enable
end
if opts.hijack_directories.auto_open == nil then
opts.hijack_directories.auto_open = opts.update_to_buf_dir.auto_open
end
opts.update_to_buf_dir = nil
end
-- 2022/06/20
utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root")
utils.move_missing_val(opts, "", "update_cwd", opts, "", "sync_root_with_cwd")
-- view.auto_resize -> actions.open_file.resize_window
if opts.view and opts.view.auto_resize ~= nil then
utils.table_create_missing(opts, "actions.open_file")
if opts.actions.open_file.resize_window == nil then
opts.actions.open_file.resize_window = opts.view.auto_resize
end
opts.view.auto_resize = nil
end
-- 2022/11/07
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "open", false)
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "close")
utils.move_missing_val(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore")
-- 2022/11/22
utils.move_missing_val(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label")
end
local function removed(opts)
if opts.auto_close then
utils.warn "auto close feature has been removed, see note in the README (tips & reminder section)"
notify.warn "auto close feature has been removed, see note in the README (tips & reminder section)"
opts.auto_close = nil
end
if opts.focus_empty_on_setup then
notify.warn "focus_empty_on_setup has been removed and will be replaced by a new startup configuration. Please remove this option. See https://bit.ly/3yJch2T"
opts.focus_empty_on_setup = nil
end
if opts.create_in_closed_folder then
notify.warn "create_in_closed_folder has been removed and is now the default behaviour. You may use api.fs.create to add a file under your desired node."
end
opts.create_in_closed_folder = nil
end
function M.migrate_legacy_options(opts)
-- g: options
local msg
for g, m in pairs(g_migrations) do
if vim.fn.exists("g:" .. g) ~= 0 then
m(opts)
msg = (msg and msg .. ", " or "Following options were moved to setup, see bit.ly/3vIpEOJ: ") .. g
end
end
if msg then
utils.warn(msg)
end
-- silently move
refactored(opts)

View File

@@ -1,5 +1,3 @@
local api = vim.api
local renderer = require "nvim-tree.renderer"
local view = require "nvim-tree.view"
local core = require "nvim-tree.core"
@@ -13,23 +11,64 @@ function M.get_node_at_cursor()
if not core.get_explorer() then
return
end
local winnr = view.get_winnr()
if not winnr then
return
end
local cursor = api.nvim_win_get_cursor(view.get_winnr())
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr())
local line = cursor[1]
if view.is_help_ui() then
local help_lines = require("nvim-tree.renderer.help").compute_lines()
local help_text = utils.get_nodes_by_line(help_lines, 1)[line]
return { name = help_text }
else
if line == 1 and core.get_explorer().cwd ~= "/" and view.is_root_folder_visible() then
return { name = ".." }
end
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
end
if line == 1 and view.is_root_folder_visible(core.get_cwd()) then
return { name = ".." }
end
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
end
---Create a sanitized partial copy of a node, populating children recursively.
---@param node table
---@return table|nil cloned node
local function clone_node(node)
if not node then
node = core.get_explorer()
if not node then
return nil
end
end
local n = {
absolute_path = node.absolute_path,
executable = node.executable,
extension = node.extension,
git_status = node.git_status,
has_children = node.has_children,
hidden = node.hidden,
link_to = node.link_to,
name = node.name,
open = node.open,
type = node.type,
}
if type(node.nodes) == "table" then
n.nodes = {}
for _, child in ipairs(node.nodes) do
table.insert(n.nodes, clone_node(child))
end
end
return n
end
---Api.tree.get_nodes
function M.get_nodes()
return clone_node(core.get_explorer())
end
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
@@ -55,7 +94,7 @@ function M.expand_or_collapse(node)
end
function M.set_target_win()
local id = api.nvim_get_current_win()
local id = vim.api.nvim_get_current_win()
local tree_id = view.get_winnr()
if tree_id and id == tree_id then
M.target_winid = 0
@@ -66,9 +105,8 @@ function M.set_target_win()
end
local function handle_buf_cwd(cwd)
local respect_buf_cwd = vim.g.nvim_tree_respect_buf_cwd or 0
if respect_buf_cwd == 1 and cwd ~= core.get_explorer().cwd then
require("nvim-tree.actions.change-dir").fn(cwd)
if M.respect_buf_cwd and cwd ~= core.get_cwd() then
require("nvim-tree.actions.root.change-dir").fn(cwd)
end
end
@@ -80,10 +118,10 @@ local function open_view_and_draw()
end
local function should_hijack_current_buf()
local bufnr = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
local bufmodified = api.nvim_buf_get_option(bufnr, "modified")
local ft = api.nvim_buf_get_option(bufnr, "ft")
local bufnr = vim.api.nvim_get_current_buf()
local bufname = vim.api.nvim_buf_get_name(bufnr)
local bufmodified = vim.api.nvim_buf_get_option(bufnr, "modified")
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
local should_hijack_unnamed = M.hijack_unnamed_buffer_when_opening and bufname == "" and not bufmodified and ft == ""
local should_hijack_dir = bufname ~= "" and vim.fn.isdirectory(bufname) == 1 and M.hijack_directories.enable
@@ -91,12 +129,34 @@ local function should_hijack_current_buf()
return should_hijack_dir or should_hijack_unnamed
end
function M.prompt(prompt_input, prompt_select, items_short, items_long, callback)
local function format_item(short)
for i, s in ipairs(items_short) do
if short == s then
return items_long[i]
end
end
return ""
end
if M.select_prompts then
vim.ui.select(items_short, { prompt = prompt_select, format_item = format_item }, function(item_short)
callback(item_short)
end)
else
vim.ui.input({ prompt = prompt_input }, function(item_short)
callback(item_short)
end)
end
end
function M.open(cwd)
M.set_target_win()
if not core.get_explorer() or cwd then
core.init(cwd or vim.loop.cwd())
end
if should_hijack_current_buf() then
view.close_this_tab_only()
view.open_in_current_win()
renderer.draw()
else
@@ -105,22 +165,24 @@ function M.open(cwd)
view.restore_tab_state()
end
-- @deprecated: use nvim-tree.actions.collapse-all.fn
M.collapse_all = require("nvim-tree.actions.collapse-all").fn
-- @deprecated: use nvim-tree.actions.dir-up.fn
M.dir_up = require("nvim-tree.actions.dir-up").fn
-- @deprecated: use nvim-tree.actions.change-dir.fn
M.change_dir = require("nvim-tree.actions.change-dir").fn
-- @deprecated: use nvim-tree.actions.reloaders.reload_explorer
M.refresh_tree = require("nvim-tree.actions.reloaders").reload_explorer
-- @deprecated: use nvim-tree.actions.reloaders.reload_git
M.reload_git = require("nvim-tree.actions.reloaders").reload_git
-- @deprecated: use nvim-tree.actions.find-file.fn
M.set_index_and_redraw = require("nvim-tree.actions.find-file").fn
-- @deprecated: use nvim-tree.actions.tree-modifiers.collapse-all.fn
M.collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn
-- @deprecated: use nvim-tree.actions.root.dir-up.fn
M.dir_up = require("nvim-tree.actions.root.dir-up").fn
-- @deprecated: use nvim-tree.actions.root.change-dir.fn
M.change_dir = require("nvim-tree.actions.root.change-dir").fn
-- @deprecated: use nvim-tree.actions.reloaders.reloaders.reload_explorer
M.refresh_tree = require("nvim-tree.actions.reloaders.reloaders").reload_explorer
-- @deprecated: use nvim-tree.actions.reloaders.reloaders.reload_git
M.reload_git = require("nvim-tree.actions.reloaders.reloaders").reload_git
-- @deprecated: use nvim-tree.actions.finders.find-file.fn
M.set_index_and_redraw = require("nvim-tree.actions.finders.find-file").fn
function M.setup(opts)
M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening
M.hijack_directories = opts.hijack_directories
M.respect_buf_cwd = opts.respect_buf_cwd
M.select_prompts = opts.select_prompts
end
return M

View File

@@ -0,0 +1,158 @@
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {
filter = nil,
}
local function redraw()
require("nvim-tree.renderer").draw()
end
local function reset_filter(node_)
node_ = node_ or TreeExplorer
Iterator.builder(node_.nodes)
:hidden()
:applier(function(node)
node.hidden = false
end)
:iterate()
end
local overlay_bufnr = nil
local overlay_winnr = nil
local function remove_overlay()
if view.View.float.enable and view.View.float.quit_on_focus_loss then
-- return to normal nvim-tree float behaviour when filter window is closed
vim.api.nvim_create_autocmd("WinLeave", {
pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
callback = function()
if utils.is_nvim_tree_buf(0) then
view.close()
end
end,
})
end
vim.api.nvim_win_close(overlay_winnr, { force = true })
overlay_bufnr = nil
overlay_winnr = nil
if M.filter == "" then
M.clear_filter()
end
end
local function matches(node)
local path = node.absolute_path
local name = vim.fn.fnamemodify(path, ":t")
return vim.regex(M.filter):match_str(name) ~= nil
end
function M.apply_filter(node_)
if not M.filter or M.filter == "" then
reset_filter(node_)
return
end
-- TODO(kiyan): this iterator cannot yet be refactored with the Iterator module
-- since the node mapper is based on its children
local function iterate(node)
local filtered_nodes = 0
local nodes = node.group_next and { node.group_next } or node.nodes
if nodes then
for _, n in pairs(nodes) do
iterate(n)
if n.hidden then
filtered_nodes = filtered_nodes + 1
end
end
end
local has_nodes = nodes and (M.always_show_folders or #nodes > filtered_nodes)
local ok, is_match = pcall(matches, node)
node.hidden = not (has_nodes or (ok and is_match))
end
iterate(node_ or TreeExplorer)
end
local function record_char()
vim.schedule(function()
M.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
M.apply_filter()
redraw()
end)
end
local function configure_buffer_overlay()
overlay_bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_attach(overlay_bufnr, true, {
on_lines = record_char,
})
vim.api.nvim_create_autocmd("InsertLeave", {
callback = remove_overlay,
once = true,
})
vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
end
local function create_overlay()
local min_width = 20
if view.View.float.enable then
-- don't close nvim-tree float when focus is changed to filter window
vim.api.nvim_clear_autocmds {
event = "WinLeave",
pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
}
min_width = min_width - 2
end
configure_buffer_overlay()
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
col = 1,
row = 0,
relative = "cursor",
width = math.max(min_width, vim.api.nvim_win_get_width(view.get_winnr()) - #M.prefix - 2),
height = 1,
border = "none",
style = "minimal",
})
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true)
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { M.filter })
vim.cmd "startinsert"
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #M.filter + 1 })
end
function M.start_filtering()
M.filter = M.filter or ""
redraw()
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
local col = #M.prefix > 0 and #M.prefix - 1 or 1
view.set_cursor { row, col }
-- needs scheduling to let the cursor move before initializing the window
vim.schedule(create_overlay)
end
function M.clear_filter()
M.filter = nil
reset_filter()
redraw()
end
function M.setup(opts)
M.prefix = opts.live_filter.prefix
M.always_show_folders = opts.live_filter.always_show_folders
end
return M

View File

@@ -1,5 +1,3 @@
local uv = vim.loop
local M = {
config = nil,
path = nil,
@@ -29,7 +27,7 @@ function M.profile_start(fmt, ...)
return
end
M.line("profile", "START " .. (fmt or "???"), ...)
return uv.hrtime()
return vim.loop.hrtime()
end
--- Write to log file via M.line
@@ -39,7 +37,7 @@ function M.profile_end(start, fmt, ...)
if not M.path or not M.config.types.profile and not M.config.types.all then
return
end
local millis = start and math.modf((uv.hrtime() - start) / 1000000) or -1
local millis = start and math.modf((vim.loop.hrtime() - start) / 1000000) or -1
M.line("profile", "END " .. (fmt or "???") .. " " .. millis .. "ms", ...)
end
@@ -56,7 +54,7 @@ function M.setup(opts)
if M.config.truncate then
os.remove(M.path)
end
print("nvim-tree.lua logging to " .. M.path)
require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. M.path)
end
end

View File

@@ -0,0 +1,42 @@
local Marks = require "nvim-tree.marks"
local Core = require "nvim-tree.core"
local utils = require "nvim-tree.utils"
local FsRename = require "nvim-tree.actions.fs.rename-file"
local notify = require "nvim-tree.notify"
local M = {}
function M.bulk_move()
if #Marks.get_marks() == 0 then
notify.warn "no bookmark to perform bulk move on, aborting."
return
end
vim.ui.input({ prompt = "Move to: ", default = Core.get_cwd(), completion = "dir" }, function(location)
utils.clear_prompt()
if not location or location == "" then
return
end
if vim.fn.filewritable(location) ~= 2 then
notify.warn(location .. " is not writable, cannot move.")
return
end
local marks = Marks.get_marks()
for _, node in pairs(marks) do
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
local to = utils.path_join { location, head }
FsRename.rename(node, to)
end
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
end
function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -0,0 +1,81 @@
local view = require "nvim-tree.view"
local Iterator = require "nvim-tree.iterators.node-iterator"
local core = require "nvim-tree.core"
local NvimTreeMarks = {}
local M = {}
local function add_mark(node)
NvimTreeMarks[node.absolute_path] = node
M.draw()
end
local function remove_mark(node)
NvimTreeMarks[node.absolute_path] = nil
M.draw()
end
function M.toggle_mark(node)
if node.absolute_path == nil then
return
end
if M.get_mark(node) then
remove_mark(node)
else
add_mark(node)
end
end
function M.clear_marks()
NvimTreeMarks = {}
M.draw()
end
function M.get_mark(node)
return NvimTreeMarks[node.absolute_path]
end
function M.get_marks()
local list = {}
for _, node in pairs(NvimTreeMarks) do
table.insert(list, node)
end
return list
end
local GROUP = "NvimTreeMarkSigns"
local SIGN_NAME = "NvimTreeMark"
function M.clear()
vim.fn.sign_unplace(GROUP)
end
function M.draw()
if not view.is_visible() then
return
end
M.clear()
local buf = view.get_bufnr()
local add = core.get_nodes_starting_line() - 1
Iterator.builder(core.get_explorer().nodes)
:recursor(function(node)
return node.open and node.nodes
end)
:applier(function(node, idx)
if M.get_mark(node) then
vim.fn.sign_place(0, GROUP, SIGN_NAME, buf, { lnum = idx + add, priority = 3 })
end
end)
:iterate()
end
function M.setup(opts)
vim.fn.sign_define(SIGN_NAME, { text = opts.renderer.icons.glyphs.bookmark, texthl = "NvimTreeBookmark" })
require("nvim-tree.marks.bulk-move").setup(opts)
end
return M

View File

@@ -0,0 +1,93 @@
local Iterator = require "nvim-tree.iterators.node-iterator"
local core = require "nvim-tree.core"
local Marks = require "nvim-tree.marks"
local open_file = require "nvim-tree.actions.node.open-file"
local utils = require "nvim-tree.utils"
local lib = require "nvim-tree.lib"
local function get_nearest(node, where)
local first, prev, next, last = nil, nil, nil, nil
local found = false
Iterator.builder(core.get_explorer().nodes)
:recursor(function(n)
return n.open and n.nodes
end)
:applier(function(n)
if n.absolute_path == node.absolute_path then
found = true
return
end
if not Marks.get_mark(n) then
return
end
last = n
first = first or n
if found and not next then
next = n
end
if not found then
prev = n
end
end)
:iterate()
if not found then
return
end
if where == "next" then
return next or first
else
return prev or last
end
end
local function get(where, node)
if node then
return get_nearest(node, where)
end
end
local function open_or_focus(node)
if node and not node.nodes and not utils.get_win_buf_from_path(node.absolute_path) then
open_file.fn("edit", node.absolute_path)
elseif node then
utils.focus_file(node.absolute_path)
end
end
local function navigate_to(where)
return function()
local node = lib.get_node_at_cursor()
local next = get(where, node)
open_or_focus(next)
end
end
local M = {}
M.next = navigate_to "next"
M.prev = navigate_to "prev"
function M.select()
local list = vim.tbl_map(function(n)
return n.absolute_path
end, Marks.get_marks())
vim.ui.select(list, {
prompt = "Select a file to open or a folder to focus",
}, function(choice)
if not choice or choice == "" then
return
end
local node = Marks.get_mark { absolute_path = choice }
open_or_focus(node)
end)
end
return M

44
lua/nvim-tree/notify.lua Normal file
View File

@@ -0,0 +1,44 @@
local M = {}
local config = {
threshold = vim.log.levels.INFO,
}
local modes = {
{ name = "trace", level = vim.log.levels.TRACE },
{ name = "debug", level = vim.log.levels.DEBUG },
{ name = "info", level = vim.log.levels.INFO },
{ name = "warn", level = vim.log.levels.WARN },
{ name = "error", level = vim.log.levels.ERROR },
}
do
local has_notify, notify_plugin = pcall(require, "notify")
local dispatch = function(level, msg)
if level < config.threshold then
return
end
vim.schedule(function()
if has_notify and notify_plugin then
notify_plugin(msg, level, { title = "NvimTree" })
else
vim.notify("[NvimTree] " .. msg, level)
end
end)
end
for _, x in ipairs(modes) do
M[x.name] = function(msg)
return dispatch(x.level, msg)
end
end
end
function M.setup(opts)
opts = opts or {}
config.threshold = opts.notify.threshold or vim.log.levels.INFO
end
return M

View File

@@ -1,4 +1,5 @@
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local git = require "nvim-tree.renderer.components.git"
local pad = require "nvim-tree.renderer.components.padding"
@@ -7,24 +8,22 @@ local icons = require "nvim-tree.renderer.components.icons"
local Builder = {}
Builder.__index = Builder
local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?"
function Builder.new(root_cwd)
return setmetatable({
index = 0,
depth = nil,
depth = 0,
highlights = {},
lines = {},
markers = {},
signs = {},
root_cwd = root_cwd,
}, Builder)
end
function Builder:configure_initial_depth(show_arrows)
self.depth = show_arrows and 2 or 0
return self
end
function Builder:configure_root_modifier(root_folder_modifier)
self.root_folder_modifier = root_folder_modifier or ":~"
function Builder:configure_root_label(root_folder_label)
self.root_folder_label = root_folder_label or DEFAULT_ROOT_FOLDER_LABEL
return self
end
@@ -33,8 +32,8 @@ function Builder:configure_trailing_slash(with_trailing)
return self
end
function Builder:configure_special_map(special_map)
self.special_map = special_map
function Builder:configure_special_files(special_files)
self.special_files = special_files
return self
end
@@ -43,14 +42,14 @@ function Builder:configure_picture_map(picture_map)
return self
end
function Builder:configure_opened_file_highlighting(level)
if level == 1 then
self.open_file_highlight = "icon"
elseif level == 2 then
self.open_file_highlight = "name"
elseif level == 3 then
self.open_file_highlight = "all"
end
function Builder:configure_filter(filter, prefix)
self.filter_prefix = prefix
self.filter = filter
return self
end
function Builder:configure_opened_file_highlighting(highlight_opened_files)
self.highlight_opened_files = highlight_opened_files
return self
end
@@ -61,9 +60,16 @@ function Builder:configure_git_icons_padding(padding)
end
function Builder:configure_git_icons_placement(where)
where = where or "before"
self.is_git_before = where == "before"
self.is_git_after = not self.is_git_before
if where == "signcolumn" then
vim.fn.sign_unplace(git.SIGN_GROUP)
self.is_git_sign = true
end
self.is_git_after = where == "after" and not self.is_git_sign
return self
end
function Builder:configure_symlink_destination(show)
self.symlink_destination = show
return self
end
@@ -91,10 +97,11 @@ function Builder:_unwrap_git_data(git_icons_and_hl_groups, offset)
end
local icon = ""
for _, v in ipairs(git_icons_and_hl_groups) do
for i, v in ipairs(git_icons_and_hl_groups) do
if #v.icon > 0 then
self:_insert_highlight(v.hl, offset + #icon, offset + #icon + #v.icon)
icon = icon .. v.icon
local remove_padding = self.is_git_after and i == #git_icons_and_hl_groups
icon = icon .. v.icon .. (remove_padding and "" or self.git_icon_padding)
end
end
return icon
@@ -108,17 +115,27 @@ function Builder:_build_folder(node, padding, git_hl, git_icons_tbl)
local icon = icons.get_folder_icon(node.open, node.link_to ~= nil, has_children)
local foldername = name .. self.trailing_slash
if node.link_to and self.symlink_destination then
local arrow = icons.i.symlink_arrow
local link_to = utils.path_relative(node.link_to, core.get_cwd())
foldername = foldername .. arrow .. link_to
end
local git_icons = self:_unwrap_git_data(git_icons_tbl, offset + #icon + (self.is_git_after and #foldername + 1 or 0))
local fname_starts_at = offset + #icon + (self.is_git_before and #git_icons or 0)
local fname_starts_at = offset + #icon + (self.is_git_after and 0 or #git_icons)
local line = self:_format_line(padding .. icon, foldername, git_icons)
self:_insert_line(line)
if #icon > 0 then
self:_insert_highlight("NvimTreeFolderIcon", offset, offset + #icon)
if node.open then
self:_insert_highlight("NvimTreeOpenedFolderIcon", offset, offset + #icon)
else
self:_insert_highlight("NvimTreeClosedFolderIcon", offset, offset + #icon)
end
end
local foldername_hl = "NvimTreeFolderName"
if self.special_map[node.absolute_path] then
if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then
foldername_hl = "NvimTreeSpecialFolderName"
elseif node.open then
foldername_hl = "NvimTreeOpenedFolderName"
@@ -134,12 +151,13 @@ function Builder:_build_folder(node, padding, git_hl, git_icons_tbl)
end
function Builder:_format_line(before, after, git_icons)
git_icons = self.is_git_after and git_icons and " " .. git_icons or git_icons
return string.format(
"%s%s%s%s",
before,
#git_icons > 0 and not self.is_git_after and git_icons .. self.git_icon_padding or "",
self.is_git_after and "" or git_icons,
after,
#git_icons > 0 and self.is_git_after and self.git_icon_padding .. git_icons or ""
self.is_git_after and git_icons or ""
)
end
@@ -148,7 +166,11 @@ function Builder:_build_symlink(node, padding, git_highlight, git_icons_tbl)
local icon = icons.i.symlink
local arrow = icons.i.symlink_arrow
local symlink_formatted = node.name .. arrow .. node.link_to
local symlink_formatted = node.name
if self.symlink_destination then
local link_to = utils.path_relative(node.link_to, core.get_cwd())
symlink_formatted = symlink_formatted .. arrow .. link_to
end
local link_highlight = git_highlight or "NvimTreeSymlink"
@@ -172,12 +194,12 @@ function Builder:_highlight_opened_files(node, offset, icon_length, git_icons_le
local from = offset
local to = offset
if self.open_file_highlight == "icon" then
if self.highlight_opened_files == "icon" then
to = from + icon_length
elseif self.open_file_highlight == "name" then
elseif self.highlight_opened_files == "name" then
from = offset + icon_length + git_icons_length
to = from + #node.name
elseif self.open_file_highlight == "all" then
elseif self.highlight_opened_files == "all" then
to = from + icon_length + git_icons_length + #node.name
end
@@ -198,7 +220,7 @@ function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
local col_start = offset + #icon + git_icons_length
local col_end = col_start + #node.name
if self.special_map[node.absolute_path] or self.special_map[node.name] then
if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then
self:_insert_highlight("NvimTreeSpecialFile", col_start, col_end)
elseif node.executable then
self:_insert_highlight("NvimTreeExecFile", col_start, col_end)
@@ -206,7 +228,7 @@ function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
self:_insert_highlight("NvimTreeImageFile", col_start, col_end)
end
local should_highlight_opened_files = self.open_file_highlight and vim.fn.bufloaded(node.absolute_path) > 0
local should_highlight_opened_files = self.highlight_opened_files and vim.fn.bufloaded(node.absolute_path) > 0
if should_highlight_opened_files then
self:_highlight_opened_files(node, offset, #icon, git_icons_length)
end
@@ -216,16 +238,22 @@ function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
end
end
function Builder:_build_line(tree, node, idx)
local padding = pad.get_padding(self.depth, idx, tree, node, self.markers)
function Builder:_build_line(node, idx, num_children)
local padding = pad.get_padding(self.depth, idx, num_children, node, self.markers)
if self.depth > 0 then
if string.len(padding) > 0 then
self:_insert_highlight("NvimTreeIndentMarker", 0, string.len(padding))
end
local git_highlight = git.get_highlight(node)
local git_icons_tbl = git.get_icons(node)
if self.is_git_sign and git_icons_tbl and #git_icons_tbl > 0 then
local git_info = git_icons_tbl[1]
table.insert(self.signs, { sign = git_info.hl, lnum = self.index + 1 })
git_icons_tbl = {}
end
local is_folder = node.nodes ~= nil
local is_symlink = node.link_to ~= nil
@@ -239,38 +267,73 @@ function Builder:_build_line(tree, node, idx)
self.index = self.index + 1
if node.open then
self.depth = self.depth + 2
self.depth = self.depth + 1
self:build(node)
self.depth = self.depth - 2
self.depth = self.depth - 1
end
end
function Builder:_get_nodes_number(nodes)
if not self.filter then
return #nodes
end
local i = 0
for _, n in pairs(nodes) do
if not n.hidden then
i = i + 1
end
end
return i
end
function Builder:build(tree)
for idx, node in ipairs(tree.nodes) do
self:_build_line(tree, node, idx)
local num_children = self:_get_nodes_number(tree.nodes)
local idx = 1
for _, node in ipairs(tree.nodes) do
if not node.hidden then
self:_build_line(node, idx, num_children)
idx = idx + 1
end
end
return self
end
local function format_root_name(root_cwd, modifier)
local base_root = utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, modifier))
return utils.path_join { base_root, ".." }
local function format_root_name(root_cwd, root_label)
if type(root_label) == "function" then
local label = root_label(root_cwd)
if type(label) == "string" then
return label
else
root_label = DEFAULT_ROOT_FOLDER_LABEL
end
end
return utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, root_label))
end
function Builder:build_header(show_header)
if show_header then
local root_name = format_root_name(self.root_cwd, self.root_folder_modifier)
local root_name = format_root_name(self.root_cwd, self.root_folder_label)
self:_insert_line(root_name)
self:_insert_highlight("NvimTreeRootFolder", 0, string.len(root_name))
self.index = 1
end
if self.filter then
local filter_line = self.filter_prefix .. "/" .. self.filter .. "/"
self:_insert_line(filter_line)
local prefix_length = string.len(self.filter_prefix)
self:_insert_highlight("NvimTreeLiveFilterPrefix", 0, prefix_length)
self:_insert_highlight("NvimTreeLiveFilterValue", prefix_length, string.len(filter_line))
self.index = self.index + 1
end
return self
end
function Builder:unwrap()
return self.lines, self.highlights
return self.lines, self.highlights, self.signs
end
return Builder

View File

@@ -0,0 +1,111 @@
local M = {}
local utils = require "nvim-tree.utils"
local function hide(win)
if win then
if vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_close(win, true)
end
end
end
-- reduce signcolumn/foldcolumn from window width
local function effective_win_width()
local win_width = vim.fn.winwidth(0)
-- return zero if the window cannot be found
local win_id = vim.fn.win_getid()
if win_id == 0 then
return win_width
end
-- if the window does not exist the result is an empty list
local win_info = vim.fn.getwininfo(win_id)
-- check if result table is empty
if next(win_info) == nil then
return win_width
end
return win_width - win_info[1].textoff
end
local function show()
local line_nr = vim.api.nvim_win_get_cursor(0)[1]
if line_nr == 1 and require("nvim-tree.view").is_root_folder_visible() then
return
end
if vim.wo.wrap then
return
end
-- only work for left tree
if vim.api.nvim_win_get_position(0)[2] ~= 0 then
return
end
local line = vim.fn.getline "."
local leftcol = vim.fn.winsaveview().leftcol
-- hide full name if left column of node in nvim-tree win is not zero
if leftcol ~= 0 then
return
end
local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g"))
local win_width = effective_win_width()
if text_width < win_width then
return
end
M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, {
relative = "win",
bufpos = { vim.fn.line "." - 2, 0 },
width = math.min(text_width, vim.o.columns - 2),
height = 1,
noautocmd = true,
style = "minimal",
})
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"]
local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 })
vim.api.nvim_win_call(M.popup_win, function()
vim.fn.setbufline("%", 1, line)
for _, extmark in ipairs(extmarks) do
local hl = extmark[4]
vim.api.nvim_buf_add_highlight(0, ns_id, hl.hl_group, 0, extmark[3], hl.end_col)
end
vim.cmd [[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]]
end)
end
M.setup = function(opts)
M.config = opts.renderer
if not M.config.full_name then
return
end
local group = vim.api.nvim_create_augroup("nvim_tree_floating_node", { clear = true })
vim.api.nvim_create_autocmd({ "BufLeave", "CursorMoved" }, {
group = group,
pattern = { "NvimTree_*" },
callback = function()
if utils.is_nvim_tree_buf(0) then
hide(M.popup_win)
end
end,
})
vim.api.nvim_create_autocmd({ "CursorMoved" }, {
group = group,
pattern = { "NvimTree_*" },
callback = function()
if utils.is_nvim_tree_buf(0) then
show()
end
end,
})
end
return M

View File

@@ -1,10 +1,11 @@
local _icons = require "nvim-tree.renderer.icon-config"
local utils = require "nvim-tree.utils"
local notify = require "nvim-tree.notify"
local explorer_common = require "nvim-tree.explorer.common"
local M = {}
local M = {
SIGN_GROUP = "NvimTreeGitSigns",
}
local function build_icons_table()
local i = M.icon_state.icons.git_icons
local function build_icons_table(i)
return {
["M "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } },
[" M"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
@@ -68,22 +69,22 @@ end
local function nil_() end
local function warn_status(git_status)
utils.warn(
notify.warn(
'Unrecognized git state "'
.. git_status
.. '". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.'
.. '". Please open up an issue on https://github.com/nvim-tree/nvim-tree.lua/issues with this message.'
)
end
local function get_icons_(node)
local git_status = node.git_status
if not git_status then
if not explorer_common.shows_git_status(node) then
return nil
end
local git_status = node.git_status
local icons = M.git_icons[git_status]
if not icons then
if vim.g.nvim_tree_git_hl ~= 1 then
if not M.config.highlight_git then
warn_status(git_status)
end
return nil
@@ -99,6 +100,7 @@ local git_hl = {
["AD"] = "NvimTreeFileStaged",
["MD"] = "NvimTreeFileStaged",
["T "] = "NvimTreeFileStaged",
["TT"] = "NvimTreeFileStaged",
[" M"] = "NvimTreeFileDirty",
["CM"] = "NvimTreeFileDirty",
[" C"] = "NvimTreeFileDirty",
@@ -120,39 +122,49 @@ local git_hl = {
["R "] = "NvimTreeFileRenamed",
["RM"] = "NvimTreeFileRenamed",
[" R"] = "NvimTreeFileRenamed",
["!!"] = "NvimTreeGitIgnored",
["!!"] = "NvimTreeFileIgnored",
[" A"] = "none",
}
function M.setup_signs(i)
vim.fn.sign_define("NvimTreeGitDirty", { text = i.unstaged, texthl = "NvimTreeGitDirty" })
vim.fn.sign_define("NvimTreeGitStaged", { text = i.staged, texthl = "NvimTreeGitStaged" })
vim.fn.sign_define("NvimTreeGitMerge", { text = i.unmerged, texthl = "NvimTreeGitMerge" })
vim.fn.sign_define("NvimTreeGitRenamed", { text = i.renamed, texthl = "NvimTreeGitRenamed" })
vim.fn.sign_define("NvimTreeGitNew", { text = i.untracked, texthl = "NvimTreeGitNew" })
vim.fn.sign_define("NvimTreeGitDeleted", { text = i.deleted, texthl = "NvimTreeGitDeleted" })
vim.fn.sign_define("NvimTreeGitIgnored", { text = i.ignored, texthl = "NvimTreeGitIgnored" })
end
local function get_highlight_(node)
local git_status = node.git_status
if not git_status then
if not explorer_common.shows_git_status(node) then
return
end
return git_hl[git_status]
end
M.get_icons = nil_
M.get_highlight = nil_
function M.setup(opts)
M.config = opts.renderer
M.icon_state = _icons.get_config()
M.git_icons = build_icons_table()
M.git_icons = build_icons_table(opts.renderer.icons.glyphs.git)
function M.reload()
M.icon_state = _icons.get_config()
M.git_icons = build_icons_table()
M.setup_signs(opts.renderer.icons.glyphs.git)
if M.icon_state.show_git_icon then
if opts.renderer.icons.show.git then
M.get_icons = get_icons_
else
M.get_icons = nil_
end
if vim.g.nvim_tree_git_hl == 1 then
if opts.renderer.highlight_git then
M.get_highlight = get_highlight_
else
M.get_highlight = nil_
end
M.git_show_on_open_dirs = opts.git.show_on_open_dirs
end
return M

View File

@@ -1,10 +1,8 @@
local icon_config = require "nvim-tree.renderer.icon-config"
local M = { i = {} }
local function config_symlinks()
M.i.symlink = #M.icons.symlink > 0 and M.icons.symlink .. M.padding or ""
M.i.symlink_arrow = vim.g.nvim_tree_symlink_arrow or ""
M.i.symlink = #M.config.glyphs.symlink > 0 and M.config.glyphs.symlink .. M.config.padding or ""
M.i.symlink_arrow = M.config.symlink_arrow
end
local function empty()
@@ -14,30 +12,30 @@ end
local function get_folder_icon(open, is_symlink, has_children)
local n
if is_symlink and open then
n = M.icons.folder_icons.symlink_open
n = M.config.glyphs.folder.symlink_open
elseif is_symlink then
n = M.icons.folder_icons.symlink
n = M.config.glyphs.folder.symlink
elseif open then
if has_children then
n = M.icons.folder_icons.open
n = M.config.glyphs.folder.open
else
n = M.icons.folder_icons.empty_open
n = M.config.glyphs.folder.empty_open
end
else
if has_children then
n = M.icons.folder_icons.default
n = M.config.glyphs.folder.default
else
n = M.icons.folder_icons.empty
n = M.config.glyphs.folder.empty
end
end
return n .. M.padding
return n .. M.config.padding
end
local function get_file_icon_default()
local hl_group = "NvimTreeFileIcon"
local icon = M.icons.default
local icon = M.config.glyphs.default
if #icon > 0 then
return icon .. M.padding, hl_group
return icon .. M.config.padding, hl_group
else
return ""
end
@@ -45,11 +43,11 @@ end
local function get_file_icon_webdev(fname, extension)
local icon, hl_group = M.devicons.get_icon(fname, extension)
if not M.webdev_colors then
if not M.config.webdev_colors then
hl_group = "NvimTreeFileIcon"
end
if icon and hl_group ~= "DevIconDefault" then
return icon .. M.padding, hl_group
return icon .. M.config.padding, hl_group
elseif string.match(extension, "%.(.*)") then
-- If there are more extensions to the file, try to grab the icon for them recursively
return get_file_icon_webdev(fname, string.match(extension, "%.(.*)"))
@@ -59,7 +57,7 @@ local function get_file_icon_webdev(fname, extension)
end
local function config_file_icon()
if M.configs.show_file_icon then
if M.config.show.file then
if M.devicons then
M.get_file_icon = get_file_icon_webdev
else
@@ -71,23 +69,23 @@ local function config_file_icon()
end
local function config_folder_icon()
if M.configs.show_folder_icon then
if M.config.show.folder then
M.get_folder_icon = get_folder_icon
else
M.get_folder_icon = empty
end
end
function M.reset_config(webdev_colors)
M.configs = icon_config.get_config()
M.icons = M.configs.icons
M.padding = vim.g.nvim_tree_icon_padding or " "
M.devicons = M.configs.has_devicons and require "nvim-web-devicons" or nil
M.webdev_colors = webdev_colors
function M.reset_config()
config_symlinks()
config_file_icon()
config_folder_icon()
end
function M.setup(opts)
M.config = opts.renderer.icons
M.devicons = pcall(require, "nvim-web-devicons") and require "nvim-web-devicons" or nil
end
return M

View File

@@ -1,53 +1,112 @@
local M = {}
function M.get_padding(depth)
return string.rep(" ", depth)
end
local function get_padding_arrows(icon_state)
return function(depth, _, _, node)
if node.nodes then
local icon = icon_state.icons.folder_icons[node.open and "arrow_open" or "arrow_closed"]
return string.rep(" ", depth - 2) .. icon .. " "
local function check_siblings_for_folder(node, with_arrows)
if with_arrows then
local has_files = false
local has_folders = false
for _, n in pairs(node.parent.nodes) do
if n.nodes and node.absolute_path ~= n.absolute_path then
has_folders = true
end
if not n.nodes then
has_files = true
end
if has_files and has_folders then
return true
end
end
return string.rep(" ", depth)
end
return false
end
local function get_padding_indent_markers(depth, idx, tree, _, markers)
local padding = ""
if depth ~= 0 then
local rdepth = depth / 2
markers[rdepth] = idx ~= #tree.nodes
for i = 1, rdepth do
if idx == #tree.nodes and i == rdepth then
padding = padding .. M.config.indent_markers.icons.corner
local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node)
local base_padding = with_arrows and (not node.nodes or depth > 0) and " " or ""
local padding = (inline_arrows or depth == 0) and base_padding or ""
if depth > 0 then
local has_folder_sibling = check_siblings_for_folder(node, with_arrows)
local indent = string.rep(" ", M.config.indent_width - 1)
markers[depth] = idx ~= nodes_number
for i = 1, depth do
local glyph
if idx == nodes_number and i == depth then
local bottom_width = M.config.indent_width
- 2
+ (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0)
glyph = M.config.indent_markers.icons.corner
.. string.rep(M.config.indent_markers.icons.bottom, bottom_width)
.. (M.config.indent_width > 1 and " " or "")
elseif markers[i] and i == depth then
glyph = M.config.indent_markers.icons.item .. indent
elseif markers[i] then
padding = padding .. M.config.indent_markers.icons.edge
glyph = M.config.indent_markers.icons.edge .. indent
else
padding = padding .. M.config.indent_markers.icons.none
glyph = M.config.indent_markers.icons.none .. indent
end
if not with_arrows or (inline_arrows and (depth ~= i or not node.nodes)) then
padding = padding .. glyph
elseif inline_arrows then
padding = padding
elseif idx ~= nodes_number and depth == i and not node.nodes and has_folder_sibling then
padding = padding .. base_padding .. glyph .. base_padding
else
padding = padding .. base_padding .. glyph
end
end
end
return padding
end
function M.reload_padding_function()
local icon_state = require("nvim-tree.renderer.icon-config").get_config()
local function get_padding_arrows(node, indent)
if node.nodes then
return M.config.icons.glyphs.folder[node.open and "arrow_open" or "arrow_closed"] .. " "
elseif indent then
return " "
else
return ""
end
end
if icon_state.show_folder_icon and icon_state.show_folder_arrows then
M.get_padding = get_padding_arrows(icon_state)
function M.get_padding(depth, idx, nodes_number, node, markers)
local padding = ""
local show_arrows = M.config.icons.show.folder_arrow
local show_markers = M.config.indent_markers.enable
local inline_arrows = M.config.indent_markers.inline_arrows
local indent_width = M.config.indent_width
if show_markers then
padding = padding .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node)
else
padding = padding .. string.rep(" ", depth * indent_width)
end
if M.config.indent_markers.enable then
M.get_padding = get_padding_indent_markers
if show_arrows then
padding = padding .. get_padding_arrows(node, not show_markers)
end
return padding
end
function M.setup(opts)
M.config = {
indent_markers = opts.renderer.indent_markers,
}
M.config = opts.renderer
if M.config.indent_width < 1 then
M.config.indent_width = 1
end
local function check_marker(symbol)
if #symbol == 0 then
return " "
end
-- return the first character from the UTF-8 encoded string; we may use utf8.codes from Lua 5.3 when available
return symbol:match "[%z\1-\127\194-\244][\128-\191]*"
end
for k, v in pairs(M.config.indent_markers.icons) do
M.config.indent_markers.icons[k] = check_marker(v)
end
end
return M

View File

@@ -1,61 +0,0 @@
local M = {}
function M.get_config()
local show_icons = vim.g.nvim_tree_show_icons or { git = 1, folders = 1, files = 1, folder_arrows = 1 }
local icons = {
default = "",
symlink = "",
git_icons = {
unstaged = "",
staged = "",
unmerged = "",
renamed = "",
untracked = "",
deleted = "",
ignored = "",
},
folder_icons = {
arrow_closed = "",
arrow_open = "",
default = "",
open = "",
empty = "",
empty_open = "",
symlink = "",
symlink_open = "",
},
}
local user_icons = vim.g.nvim_tree_icons
if user_icons then
if user_icons.default then
icons.default = user_icons.default
icons.symlink = user_icons.default
end
if user_icons.symlink then
icons.symlink = user_icons.symlink
end
for key, val in pairs(user_icons.git or {}) do
if icons.git_icons[key] then
icons.git_icons[key] = val
end
end
for key, val in pairs(user_icons.folder or {}) do
if icons.folder_icons[key] then
icons.folder_icons[key] = val
end
end
end
local has_devicons = pcall(require, "nvim-web-devicons")
return {
show_file_icon = show_icons.files == 1,
show_folder_icon = show_icons.folders == 1,
show_git_icon = show_icons.git == 1,
show_folder_arrows = show_icons.folder_arrows == 1,
has_devicons = has_devicons,
icons = icons,
}
end
return M

View File

@@ -5,41 +5,40 @@ local view = require "nvim-tree.view"
local _padding = require "nvim-tree.renderer.components.padding"
local icon_component = require "nvim-tree.renderer.components.icons"
local full_name = require "nvim-tree.renderer.components.full-name"
local help = require "nvim-tree.renderer.help"
local git = require "nvim-tree.renderer.components.git"
local Builder = require "nvim-tree.renderer.builder"
local api = vim.api
local live_filter = require "nvim-tree.live-filter"
local marks = require "nvim-tree.marks"
local M = {
last_highlights = {},
}
local namespace_id = api.nvim_create_namespace "NvimTreeHighlights"
local namespace_id = vim.api.nvim_create_namespace "NvimTreeHighlights"
local function _draw(bufnr, lines, hl)
api.nvim_buf_set_option(bufnr, "modifiable", true)
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local function _draw(bufnr, lines, hl, signs)
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
M.render_hl(bufnr, hl)
api.nvim_buf_set_option(bufnr, "modifiable", false)
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
vim.fn.sign_unplace(git.SIGN_GROUP)
for _, sign in pairs(signs) do
vim.fn.sign_place(0, git.SIGN_GROUP, sign.sign, bufnr, { lnum = sign.lnum, priority = 1 })
end
end
function M.render_hl(bufnr, hl)
if not bufnr or not api.nvim_buf_is_loaded(bufnr) then
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
return
end
api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)
vim.api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)
for _, data in ipairs(hl or M.last_highlights) do
api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4])
vim.api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4])
end
end
local function should_show_arrows()
return not M.config.indent_markers.enable
and icon_component.configs.show_folder_icon
and icon_component.configs.show_folder_arrows
end
local picture_map = {
jpg = true,
jpeg = true,
@@ -47,70 +46,65 @@ local picture_map = {
gif = true,
}
local function get_special_files_map()
return vim.g.nvim_tree_special_files
or {
["Cargo.toml"] = true,
Makefile = true,
["README.md"] = true,
["readme.md"] = true,
}
end
function M.draw()
local bufnr = view.get_bufnr()
if not core.get_explorer() or not bufnr or not api.nvim_buf_is_loaded(bufnr) then
if not core.get_explorer() or not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
return
end
local ps = log.profile_start "draw"
local cursor = api.nvim_win_get_cursor(view.get_winnr())
_padding.reload_padding_function()
icon_component.reset_config(M.config.icons.webdev_colors)
git.reload()
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr())
icon_component.reset_config()
local lines, hl
local signs = {}
if view.is_help_ui() then
lines, hl = help.compute_lines()
else
lines, hl = Builder.new(core.get_cwd())
:configure_initial_depth(should_show_arrows())
:configure_root_modifier(vim.g.nvim_tree_root_folder_modifier)
:configure_trailing_slash(vim.g.nvim_tree_add_trailing == 1)
:configure_special_map(get_special_files_map())
lines, hl, signs = Builder.new(core.get_cwd())
:configure_root_label(M.config.root_folder_label)
:configure_trailing_slash(M.config.add_trailing)
:configure_special_files(M.config.special_files)
:configure_picture_map(picture_map)
:configure_opened_file_highlighting(vim.g.nvim_tree_highlight_opened_files)
:configure_git_icons_padding(vim.g.nvim_tree_icon_padding)
:configure_opened_file_highlighting(M.config.highlight_opened_files)
:configure_git_icons_padding(M.config.icons.padding)
:configure_git_icons_placement(M.config.icons.git_placement)
:configure_symlink_destination(M.config.symlink_destination)
:configure_filter(live_filter.filter, live_filter.prefix)
:build_header(view.is_root_folder_visible(core.get_cwd()))
:build(core.get_explorer())
:unwrap()
end
_draw(bufnr, lines, hl)
_draw(bufnr, lines, hl, signs)
M.last_highlights = hl
if cursor and #lines >= cursor[1] then
api.nvim_win_set_cursor(view.get_winnr(), cursor)
vim.api.nvim_win_set_cursor(view.get_winnr(), cursor)
end
if view.is_help_ui() then
diagnostics.clear()
marks.clear()
else
diagnostics.update()
marks.draw()
end
view.grow_from_content()
log.profile_end(ps, "draw")
end
function M.setup(opts)
M.config = {
indent_markers = opts.renderer.indent_markers,
icons = opts.renderer.icons,
}
M.config = opts.renderer
_padding.setup(opts)
full_name.setup(opts)
git.setup(opts)
icon_component.setup(opts)
end
return M

View File

@@ -1,41 +1,32 @@
local has_notify, notify = pcall(require, "notify")
local Iterator = require "nvim-tree.iterators.node-iterator"
local notify = require "nvim-tree.notify"
local a = vim.api
local uv = vim.loop
local M = {}
local M = {
debouncers = {},
}
M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1
M.is_wsl = vim.fn.has "wsl" == 1
function M.path_to_matching_str(path)
return path:gsub("(%-)", "(%%-)"):gsub("(%.)", "(%%.)"):gsub("(%_)", "(%%_)")
end
function M.warn(msg)
vim.schedule(function()
if has_notify then
notify(msg, vim.log.levels.WARN, { title = "NvimTree" })
else
vim.notify("[NvimTree] " .. msg, vim.log.levels.WARN)
end
end)
end
function M.str_find(haystack, needle)
return vim.fn.stridx(haystack, needle) ~= -1
end
function M.read_file(path)
local fd = uv.fs_open(path, "r", 438)
local fd = vim.loop.fs_open(path, "r", 438)
if not fd then
return ""
end
local stat = uv.fs_fstat(fd)
local stat = vim.loop.fs_fstat(fd)
if not stat then
return ""
end
local data = uv.fs_read(fd, stat.size, 0)
uv.fs_close(fd)
local data = vim.loop.fs_read(fd, stat.size, 0)
vim.loop.fs_close(fd)
return data or ""
end
@@ -84,45 +75,61 @@ end
M.path_separator = path_separator
function M.clear_prompt()
vim.api.nvim_command "normal! :"
end
function M.get_user_input_char()
local c = vim.fn.getchar()
while type(c) ~= "number" do
c = vim.fn.getchar()
end
return vim.fn.nr2char(c)
end
-- get the node from the tree that matches the predicate
-- get the node and index of the node from the tree that matches the predicate.
-- The explored nodes are those displayed on the view.
-- @param nodes list of node
-- @param fn function(node): boolean
function M.find_node(nodes, fn)
local function iter(nodes_, fn_)
local i = 1
for _, node in ipairs(nodes_) do
if fn_(node) then
return node, i
end
if node.open and #node.nodes > 0 then
local n, idx = iter(node.nodes, fn_)
i = i + idx
if n then
return n, i
end
else
i = i + 1
end
end
return nil, i
end
local node, i = iter(nodes, fn)
i = require("nvim-tree.view").View.hide_root_folder and i - 1 or i
local node, i = Iterator.builder(nodes)
:matcher(fn)
:recursor(function(node)
return node.open and #node.nodes > 0 and node.nodes
end)
:iterate()
i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1
i = require("nvim-tree.live-filter").filter and i + 1 or i
return node, i
end
-- get the node in the tree state depending on the absolute path of the node
-- (grouped or hidden too)
function M.get_node_from_path(path)
local explorer = require("nvim-tree.core").get_explorer()
-- tree may not yet be loaded
if not explorer then
return
end
if explorer.absolute_path == path then
return explorer
end
return Iterator.builder(explorer.nodes)
:hidden()
:matcher(function(node)
return node.absolute_path == path or node.link_to == path
end)
:recursor(function(node)
if node.group_next then
return { node.group_next }
end
if node.nodes then
return node.nodes
end
end)
:iterate()
end
-- get the highest parent of grouped nodes
function M.get_parent_of_group(node_)
local node = node_
while node.parent and node.parent.group_next do
node = node.parent
end
return node
end
-- return visible nodes indexed by line
-- @param nodes_all list of node
-- @param line_start first index
@@ -130,51 +137,93 @@ end
function M.get_nodes_by_line(nodes_all, line_start)
local nodes_by_line = {}
local line = line_start
local function iter(nodes)
for _, node in ipairs(nodes) do
Iterator.builder(nodes_all)
:applier(function(node)
nodes_by_line[line] = node
line = line + 1
if node.open == true then
local child = iter(node.nodes)
if child ~= nil then
return child
end
end
end
end
iter(nodes_all)
end)
:recursor(function(node)
return node.open == true and node.nodes
end)
:iterate()
return nodes_by_line
end
---Matching executable files in Windows.
---@param ext string
---@return boolean
local PATHEXT = vim.env.PATHEXT or ""
local wexe = vim.split(PATHEXT:gsub("%.", ""), ";")
local pathexts = {}
for _, v in pairs(wexe) do
pathexts[v] = true
function M.is_windows_exe(ext)
if not M.pathexts then
if not vim.env.PATHEXT then
return false
end
local wexe = vim.split(vim.env.PATHEXT:gsub("%.", ""), ";")
M.pathexts = {}
for _, v in pairs(wexe) do
M.pathexts[v] = true
end
end
return M.pathexts[ext:upper()]
end
function M.is_windows_exe(ext)
return pathexts[ext:upper()]
--- Check whether path maps to Windows filesystem mounted by WSL
-- @param path string
-- @return boolean
function M.is_wsl_windows_fs_path(path)
-- Run 'wslpath' command to try translating WSL path to Windows path.
-- Consume stderr output as well because 'wslpath' can produce permission
-- errors on some files (e.g. temporary files in root of system drive).
local handle = io.popen('wslpath -w "' .. path .. '" 2>/dev/null')
if handle then
local output = handle:read "*a"
handle:close()
return string.find(output, "^\\\\wsl$\\") == nil
end
return false
end
--- Check whether extension is Windows executable under WSL
-- @param ext string
-- @return boolean
function M.is_wsl_windows_fs_exe(ext)
if not vim.env.PATHEXT then
-- Extract executable extensions from within WSL.
-- Redirect stderr to null to silence warnings when
-- Windows command is executed from Linux filesystem:
-- > CMD.EXE was started with the above path as the current directory.
-- > UNC paths are not supported. Defaulting to Windows directory.
local handle = io.popen 'cmd.exe /c "echo %PATHEXT%" 2>/dev/null'
if handle then
vim.env.PATHEXT = handle:read "*a"
handle:close()
end
end
return M.is_windows_exe(ext)
end
function M.rename_loaded_buffers(old_path, new_path)
for _, buf in pairs(a.nvim_list_bufs()) do
if a.nvim_buf_is_loaded(buf) then
local buf_name = a.nvim_buf_get_name(buf)
for _, buf in pairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf) then
local buf_name = vim.api.nvim_buf_get_name(buf)
local exact_match = buf_name == old_path
local child_match = (
buf_name:sub(1, #old_path) == old_path and buf_name:sub(#old_path + 1, #old_path + 1) == path_separator
)
if exact_match or child_match then
a.nvim_buf_set_name(buf, new_path .. buf_name:sub(#old_path + 1))
vim.api.nvim_buf_set_name(buf, new_path .. buf_name:sub(#old_path + 1))
-- to avoid the 'overwrite existing file' error message on write for
-- normal files
if a.nvim_buf_get_option(buf, "buftype") == "" then
a.nvim_buf_call(buf, function()
if vim.api.nvim_buf_get_option(buf, "buftype") == "" then
vim.api.nvim_buf_call(buf, function()
vim.cmd "silent! write!"
vim.cmd "edit"
end)
end
end
@@ -200,15 +249,15 @@ end
-- Create empty sub-tables if not present
-- @param tbl to create empty inside of
-- @param sub dot separated string of sub-tables
-- @param path dot separated string of sub-tables
-- @return deepest sub-table
function M.table_create_missing(tbl, sub)
function M.table_create_missing(tbl, path)
if tbl == nil then
return nil
end
local t = tbl
for s in string.gmatch(sub, "([^%.]+)%.*") do
for s in string.gmatch(path, "([^%.]+)%.*") do
if t[s] == nil then
t[s] = {}
end
@@ -218,6 +267,57 @@ function M.table_create_missing(tbl, sub)
return t
end
--- Move a value from src to dst if value is nil on dst.
--- Remove value from src
--- @param src table to copy from
--- @param src_path string dot separated string of sub-tables
--- @param src_pos string value pos
--- @param dst table to copy to
--- @param dst_path string dot separated string of sub-tables, created when missing
--- @param dst_pos string value pos
--- @param remove boolean default true
function M.move_missing_val(src, src_path, src_pos, dst, dst_path, dst_pos, remove)
if remove == nil then
remove = true
end
local ok, err = pcall(vim.validate, {
src = { src, "table" },
src_path = { src_path, "string" },
src_pos = { src_pos, "string" },
dst = { dst, "table" },
dst_path = { dst_path, "string" },
dst_pos = { dst_pos, "string" },
remove = { remove, "boolean" },
})
if not ok then
notify.warn("move_missing_val: " .. (err or "invalid arguments"))
return
end
for pos in string.gmatch(src_path, "([^%.]+)%.*") do
if src[pos] and type(src[pos]) == "table" then
src = src[pos]
else
src = nil
break
end
end
local src_val = src and src[src_pos]
if src_val == nil then
return
end
dst = M.table_create_missing(dst, dst_path)
if dst[dst_pos] == nil then
dst[dst_pos] = src_val
end
if remove then
src[src_pos] = nil
end
end
function M.format_bytes(bytes)
local units = { "B", "K", "M", "G", "T" }
@@ -241,4 +341,140 @@ function M.key_by(tbl, key)
return keyed
end
function M.bool_record(tbl, key)
local keyed = {}
for _, val in ipairs(tbl) do
keyed[val[key]] = true
end
return keyed
end
local function timer_stop_close(timer)
if timer:is_active() then
timer:stop()
end
if not timer:is_closing() then
timer:close()
end
end
---Execute callback timeout ms after the latest invocation with context.
---Waiting invocations for that context will be discarded.
---Invocation will be rescheduled while a callback is being executed.
---Caller must ensure that callback performs the same or functionally equivalent actions.
---
---@param context string identifies the callback to debounce
---@param timeout number ms to wait
---@param callback function to execute on completion
function M.debounce(context, timeout, callback)
-- all execution here is done in a synchronous context; no thread safety required
M.debouncers[context] = M.debouncers[context] or {}
local debouncer = M.debouncers[context]
-- cancel waiting or executing timer
if debouncer.timer then
timer_stop_close(debouncer.timer)
end
local timer = vim.loop.new_timer()
debouncer.timer = timer
timer:start(timeout, 0, function()
timer_stop_close(timer)
-- reschedule when callback is running
if debouncer.executing then
M.debounce(context, timeout, callback)
return
end
-- call back at a safe time
debouncer.executing = true
vim.schedule(function()
callback()
debouncer.executing = false
-- no other timer waiting
if debouncer.timer == timer then
M.debouncers[context] = nil
end
end)
end)
end
function M.focus_file(path)
local _, i = M.find_node(require("nvim-tree.core").get_explorer().nodes, function(node)
return node.absolute_path == path
end)
require("nvim-tree.view").set_cursor { i + 1, 1 }
end
function M.get_win_buf_from_path(path)
for _, w in pairs(vim.api.nvim_tabpage_list_wins(0)) do
local b = vim.api.nvim_win_get_buf(w)
if vim.api.nvim_buf_get_name(b) == path then
return w, b
end
end
return nil, nil
end
function M.clear_prompt()
if vim.opt.cmdheight._value ~= 0 then
vim.cmd "normal! :"
end
end
-- return a new table with values from array
function M.array_shallow_clone(array)
local to = {}
for _, v in ipairs(array) do
table.insert(to, v)
end
return to
end
-- remove item from array if it exists
function M.array_remove(array, item)
for i, v in ipairs(array) do
if v == item then
table.remove(array, i)
break
end
end
end
function M.array_remove_nils(array)
return vim.tbl_filter(function(v)
return v ~= nil
end, array)
end
function M.inject_node(f)
return function()
f(require("nvim-tree.lib").get_node_at_cursor())
end
end
---Is the buffer named NvimTree_[0-9]+ a tree? filetype is "NvimTree" or not readable file.
---This is cheap, as the readable test should only ever be needed when resuming a vim session.
---@param bufnr number may be 0 or nil for current
---@return boolean
function M.is_nvim_tree_buf(bufnr)
if bufnr == nil then
bufnr = 0
end
if vim.fn.bufexists(bufnr) then
local bufname = vim.api.nvim_buf_get_name(bufnr)
if vim.fn.fnamemodify(bufname, ":t"):match "^NvimTree_[0-9]+$" then
if vim.bo[bufnr].filetype == "NvimTree" then
return true
elseif vim.fn.filereadable(bufname) == 0 then
return true
end
end
end
return false
end
return M

View File

@@ -1,10 +1,18 @@
local a = vim.api
local M = {}
local events = require "nvim-tree.events"
local utils = require "nvim-tree.utils"
local log = require "nvim-tree.log"
local function get_win_sep_hl()
-- #1221 WinSeparator not present in nvim 0.6.1 and some builds of 0.7.0
local has_win_sep = pcall(vim.cmd, "silent hi WinSeparator")
return has_win_sep and "WinSeparator:NvimTreeWinSeparator" or "VertSplit:NvimTreeWinSeparator"
end
M.View = {
adaptive_size = false,
centralize_selection = false,
tabpages = {},
cursors = {},
hide_root_folder = false,
@@ -20,15 +28,17 @@ M.View = {
foldmethod = "manual",
foldcolumn = "0",
cursorcolumn = false,
cursorlineopt = "line",
cursorline = true,
cursorlineopt = "both",
colorcolumn = "0",
wrap = false,
winhl = table.concat({
"EndOfBuffer:NvimTreeEndOfBuffer",
"Normal:NvimTreeNormal",
"CursorLine:NvimTreeCursorLine",
-- #1221 WinSeparator not present in nvim 0.6.1 and some builds of 0.7.0
pcall(vim.cmd, "silent hi WinSeparator") and "WinSeparator:NvimTreeWinSeparator" or "VertSplit:NvimTreeWinSeparator",
"CursorLineNr:NvimTreeCursorLineNr",
"LineNr:NvimTreeLineNr",
get_win_sep_hl(),
"StatusLine:NvimTreeStatusLine",
"StatusLineNC:NvimTreeStatuslineNC",
"SignColumn:NvimTreeSignColumn",
@@ -67,9 +77,9 @@ local function matches_bufnr(bufnr)
end
local function wipe_rogue_buffer()
for _, bufnr in ipairs(a.nvim_list_bufs()) do
if not matches_bufnr(bufnr) and a.nvim_buf_get_name(bufnr):match "NvimTree" ~= nil then
return pcall(a.nvim_buf_delete, bufnr, { force = true })
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
end
end
end
@@ -77,20 +87,24 @@ end
local function create_buffer(bufnr)
wipe_rogue_buffer()
local tab = a.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = bufnr or a.nvim_create_buf(false, false)
a.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab)
local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false)
vim.api.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab)
for option, value in pairs(BUFFER_OPTIONS) do
vim.bo[M.get_bufnr()][option] = value
end
require("nvim-tree.actions").apply_mappings(M.get_bufnr())
if type(M.on_attach) == "function" then
require("nvim-tree.keymap").set_keymaps(M.get_bufnr())
M.on_attach(M.get_bufnr())
else
require("nvim-tree.actions").apply_mappings(M.get_bufnr())
end
end
local function get_size()
local width_or_height = M.is_vertical() and "width" or "height"
local size = M.View[width_or_height]
local size = M.View.width
if type(size) == "number" then
return size
elseif type(size) == "function" then
@@ -104,52 +118,62 @@ end
local move_tbl = {
left = "H",
right = "L",
bottom = "J",
top = "K",
}
-- TODO: remove this once they fix https://github.com/neovim/neovim/issues/14670
local function set_local(opt, value)
local cmd
if value == true then
cmd = string.format("setlocal %s", opt)
elseif value == false then
cmd = string.format("setlocal no%s", opt)
else
cmd = string.format("setlocal %s=%s", opt, value)
end
vim.cmd(cmd)
end
-- setup_tabpage sets up the initial state of a tab
local function setup_tabpage(tabpage)
local winnr = a.nvim_get_current_win()
local winnr = vim.api.nvim_get_current_win()
M.View.tabpages[tabpage] = vim.tbl_extend("force", M.View.tabpages[tabpage] or tabinitial, { winnr = winnr })
end
local function open_window()
a.nvim_command "vsp"
M.reposition_window()
setup_tabpage(a.nvim_get_current_tabpage())
end
local function set_window_options_and_buffer()
pcall(vim.cmd, "buffer " .. M.get_bufnr())
for k, v in pairs(M.View.winopts) do
set_local(k, v)
vim.opt_local[k] = v
end
end
local function get_existing_buffers()
return vim.tbl_filter(function(buf)
return a.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1
end, a.nvim_list_bufs())
local function open_win_config()
if type(M.View.float.open_win_config) == "function" then
return M.View.float.open_win_config()
else
return M.View.float.open_win_config
end
end
local function open_window()
if M.View.float.enable then
vim.api.nvim_open_win(0, true, open_win_config())
else
vim.api.nvim_command "vsp"
M.reposition_window()
end
setup_tabpage(vim.api.nvim_get_current_tabpage())
set_window_options_and_buffer()
end
local function is_buf_displayed(buf)
return vim.api.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1
end
local function get_alt_or_next_buf()
local alt_buf = vim.fn.bufnr "#"
if is_buf_displayed(alt_buf) then
return alt_buf
end
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if is_buf_displayed(buf) then
return buf
end
end
end
local function switch_buf_if_last_buf()
if #a.nvim_list_wins() == 1 then
if #get_existing_buffers() > 0 then
vim.cmd "sbnext"
if #vim.api.nvim_list_wins() == 1 then
local buf = get_alt_or_next_buf()
if buf then
vim.cmd("sb" .. buf)
else
vim.cmd "new"
end
@@ -157,25 +181,27 @@ local function switch_buf_if_last_buf()
end
-- save_tab_state saves any state that should be preserved across redraws.
local function save_tab_state()
local tabpage = a.nvim_get_current_tabpage()
M.View.cursors[tabpage] = a.nvim_win_get_cursor(M.get_winnr())
local function save_tab_state(tabnr)
local tabpage = tabnr or vim.api.nvim_get_current_tabpage()
M.View.cursors[tabpage] = vim.api.nvim_win_get_cursor(M.get_winnr(tabpage))
end
function M.close()
if not M.is_visible() then
local function close(tabpage)
if not M.is_visible { tabpage = tabpage } then
return
end
save_tab_state()
save_tab_state(tabpage)
switch_buf_if_last_buf()
local tree_win = M.get_winnr()
local current_win = a.nvim_get_current_win()
for _, win in pairs(a.nvim_list_wins()) do
if tree_win ~= win and a.nvim_win_get_config(win).relative == "" then
a.nvim_win_close(tree_win, true)
local tree_win = M.get_winnr(tabpage)
local current_win = vim.api.nvim_get_current_win()
for _, win in pairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
if vim.api.nvim_win_get_config(win).relative == "" then
local prev_win = vim.fn.winnr "#" -- this tab only
if tree_win == current_win and prev_win > 0 then
a.nvim_set_current_win(vim.fn.win_getid(prev_win))
vim.api.nvim_set_current_win(vim.fn.win_getid(prev_win))
end
if vim.api.nvim_win_is_valid(tree_win) then
vim.api.nvim_win_close(tree_win, true)
end
events._dispatch_on_tree_close()
return
@@ -183,14 +209,34 @@ function M.close()
end
end
function M.close_this_tab_only()
close(vim.api.nvim_get_current_tabpage())
end
function M.close_all_tabs()
for tabpage, _ in pairs(M.View.tabpages) do
close(tabpage)
end
end
function M.close()
if M.View.tab.sync.close then
M.close_all_tabs()
else
M.close_this_tab_only()
end
end
function M.open(options)
if M.is_visible() then
return
end
local pn = string.format "view open"
local ps = log.profile_start(pn)
create_buffer()
open_window()
set_window_options_and_buffer()
M.resize()
local opts = options or { focus_tree = true }
@@ -198,9 +244,36 @@ function M.open(options)
vim.cmd "wincmd p"
end
events._dispatch_on_tree_open()
log.profile_end(ps, pn)
end
local function grow()
local starts_at = M.is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0
local lines = vim.api.nvim_buf_get_lines(M.get_bufnr(), starts_at, -1, false)
local max_length = M.View.initial_width
for _, l in pairs(lines) do
local count = vim.fn.strchars(l) + 3 -- plus some padding
if max_length < count then
max_length = count
end
end
M.resize(max_length)
end
function M.grow_from_content()
if M.View.adaptive_size then
grow()
end
end
function M.resize(size)
if M.View.float.enable and not M.View.adaptive_size then
-- if the floating windows's adaptive size is not desired, then the
-- float size should be defined in view.float.open_win_config
return
end
if type(size) == "string" then
size = vim.trim(size)
local first_char = size:sub(1, 1)
@@ -224,11 +297,10 @@ function M.resize(size)
return
end
if M.is_vertical() then
a.nvim_win_set_width(M.get_winnr(), get_size())
else
a.nvim_win_set_height(M.get_winnr(), get_size())
end
local new_size = get_size()
vim.api.nvim_win_set_width(M.get_winnr(), new_size)
events._dispatch_on_tree_resize(new_size)
if not M.View.preserve_window_proportions then
vim.cmd ":wincmd ="
@@ -237,19 +309,19 @@ end
function M.reposition_window()
local move_to = move_tbl[M.View.side]
a.nvim_command("wincmd " .. move_to)
vim.api.nvim_command("wincmd " .. move_to)
M.resize()
end
local function set_current_win()
local current_tab = a.nvim_get_current_tabpage()
M.View.tabpages[current_tab].winnr = a.nvim_get_current_win()
local current_tab = vim.api.nvim_get_current_tabpage()
M.View.tabpages[current_tab].winnr = vim.api.nvim_get_current_win()
end
function M.open_in_current_win(opts)
opts = opts or { hijack_current_buf = true, resize = true }
create_buffer(opts.hijack_current_buf and a.nvim_get_current_buf())
setup_tabpage(a.nvim_get_current_tabpage())
create_buffer(opts.hijack_current_buf and vim.api.nvim_get_current_buf())
setup_tabpage(vim.api.nvim_get_current_tabpage())
set_current_win()
set_window_options_and_buffer()
if opts.resize then
@@ -259,36 +331,53 @@ function M.open_in_current_win(opts)
end
function M.abandon_current_window()
local tab = a.nvim_get_current_tabpage()
local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = nil
M.View.tabpages[tab].winnr = nil
if M.View.tabpages[tab] then
M.View.tabpages[tab].winnr = nil
end
end
function M.abandon_all_windows()
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
BUFNR_PER_TAB[tab] = nil
if M.View.tabpages[tab] then
M.View.tabpages[tab].winnr = nil
end
end
end
function M.is_visible(opts)
if opts and opts.tabpage then
if M.View.tabpages[opts.tabpage] == nil then
return false
end
local winnr = M.View.tabpages[opts.tabpage].winnr
return winnr and vim.api.nvim_win_is_valid(winnr)
end
if opts and opts.any_tabpage then
for _, v in pairs(M.View.tabpages) do
if a.nvim_win_is_valid(v.winnr) then
if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then
return true
end
end
return false
end
return M.get_winnr() ~= nil and a.nvim_win_is_valid(M.get_winnr())
return M.get_winnr() ~= nil and vim.api.nvim_win_is_valid(M.get_winnr())
end
function M.set_cursor(opts)
if M.is_visible() then
pcall(a.nvim_win_set_cursor, M.get_winnr(), opts)
-- patch until https://github.com/neovim/neovim/issues/17395 is fixed
require("nvim-tree.renderer").draw()
pcall(vim.api.nvim_win_set_cursor, M.get_winnr(), opts)
end
end
function M.focus(winnr, open_if_closed)
local wnr = winnr or M.get_winnr()
if a.nvim_win_get_tabpage(wnr or 0) ~= a.nvim_win_get_tabpage(0) then
if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then
M.close()
M.open()
wnr = M.get_winnr()
@@ -296,16 +385,12 @@ function M.focus(winnr, open_if_closed)
M.open()
end
a.nvim_set_current_win(wnr)
end
function M.is_vertical()
return M.View.side == "left" or M.View.side == "right"
vim.api.nvim_set_current_win(wnr)
end
--- Restores the state of a NvimTree window if it was initialized before.
function M.restore_tab_state()
local tabpage = a.nvim_get_current_tabpage()
local tabpage = vim.api.nvim_get_current_tabpage()
M.set_cursor(M.View.cursors[tabpage])
end
@@ -313,7 +398,7 @@ end
---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return number
function M.get_winnr(tabpage)
tabpage = tabpage or a.nvim_get_current_tabpage()
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = M.View.tabpages[tabpage]
if tabinfo ~= nil then
return tabinfo.winnr
@@ -323,14 +408,14 @@ end
--- Returns the current nvim tree bufnr
---@return number
function M.get_bufnr()
return BUFNR_PER_TAB[a.nvim_get_current_tabpage()]
return BUFNR_PER_TAB[vim.api.nvim_get_current_tabpage()]
end
--- Checks if nvim-tree is displaying the help ui within the tabpage specified
---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return number
function M.is_help_ui(tabpage)
tabpage = tabpage or a.nvim_get_current_tabpage()
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = M.View.tabpages[tabpage]
if tabinfo ~= nil then
return tabinfo.help
@@ -338,12 +423,12 @@ function M.is_help_ui(tabpage)
end
function M.toggle_help(tabpage)
tabpage = tabpage or a.nvim_get_current_tabpage()
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
M.View.tabpages[tabpage].help = not M.View.tabpages[tabpage].help
end
function M.is_buf_valid(bufnr)
return bufnr and a.nvim_buf_is_valid(bufnr) and a.nvim_buf_is_loaded(bufnr)
return bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr)
end
function M._prevent_buffer_override()
@@ -354,9 +439,11 @@ function M._prevent_buffer_override()
-- because this event needs to be run on bufWipeout.
-- Otherwise the curwin/curbuf would match the view buffer and the view window.
vim.schedule(function()
local curwin = a.nvim_get_current_win()
local curbuf = a.nvim_win_get_buf(curwin)
local bufname = a.nvim_buf_get_name(curbuf)
local curwin = vim.api.nvim_get_current_win()
local curwinconfig = vim.api.nvim_win_get_config(curwin)
local curbuf = vim.api.nvim_win_get_buf(curwin)
local bufname = vim.api.nvim_buf_get_name(curbuf)
if not bufname:match "NvimTree" then
for i, tabpage in ipairs(M.View.tabpages) do
if tabpage.winnr == view_winnr then
@@ -375,8 +462,15 @@ function M._prevent_buffer_override()
vim.cmd "setlocal nowinfixheight"
M.open { focus_tree = false }
require("nvim-tree.renderer").draw()
a.nvim_win_close(curwin, { force = true })
require("nvim-tree.actions.open-file").fn("edit", bufname)
pcall(vim.api.nvim_win_close, curwin, { force = true })
-- to handle opening a file using :e when nvim-tree is on floating mode
-- falling back to the current window instead of creating a new one
if curwinconfig.relative ~= "" then
require("nvim-tree.actions.node.open-file").fn("edit_in_place", bufname)
else
require("nvim-tree.actions.node.open-file").fn("edit", bufname)
end
end)
end
@@ -384,16 +478,29 @@ function M.is_root_folder_visible(cwd)
return cwd ~= "/" and not M.View.hide_root_folder
end
-- used on ColorScheme event
function M.reset_winhl()
if M.get_winnr() and vim.api.nvim_win_is_valid(M.get_winnr()) then
vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl
end
end
function M.setup(opts)
local options = opts.view or {}
M.View.side = options.side
M.View.adaptive_size = options.adaptive_size
M.View.centralize_selection = options.centralize_selection
M.View.side = (options.side == "right") and "right" or "left"
M.View.width = options.width
M.View.height = options.height
M.View.initial_width = get_size()
M.View.hide_root_folder = options.hide_root_folder
M.View.tab = opts.tab
M.View.preserve_window_proportions = options.preserve_window_proportions
M.View.winopts.number = options.number
M.View.winopts.relativenumber = options.relativenumber
M.View.winopts.signcolumn = options.signcolumn
M.View.float = options.float
M.on_attach = opts.on_attach
end
return M

158
lua/nvim-tree/watcher.lua Normal file
View File

@@ -0,0 +1,158 @@
local notify = require "nvim-tree.notify"
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local M = {}
local Event = {
_events = {},
}
Event.__index = Event
local Watcher = {
_watchers = {},
}
Watcher.__index = Watcher
local FS_EVENT_FLAGS = {
-- inotify or equivalent will be used; fallback to stat has not yet been implemented
stat = false,
-- recursive is not functional in neovim's libuv implementation
recursive = false,
}
function Event:new(path)
log.line("watcher", "Event:new '%s'", path)
local e = setmetatable({
_path = path,
_fs_event = nil,
_listeners = {},
}, Event)
if e:start() then
Event._events[path] = e
return e
else
return nil
end
end
function Event:start()
log.line("watcher", "Event:start '%s'", self._path)
local rc, _, name
self._fs_event, _, name = vim.loop.new_fs_event()
if not self._fs_event then
self._fs_event = nil
notify.warn(string.format("Could not initialize an fs_event watcher for path %s : %s", self._path, name))
return false
end
local event_cb = vim.schedule_wrap(function(err, filename)
if err then
log.line("watcher", "event_cb '%s' '%s' FAIL : %s", self._path, filename, err)
self:destroy(string.format("File system watcher failed (%s) for path %s, halting watcher.", err, self._path))
else
log.line("watcher", "event_cb '%s' '%s'", self._path, filename)
for _, listener in ipairs(self._listeners) do
listener(filename)
end
end
end)
rc, _, name = self._fs_event:start(self._path, FS_EVENT_FLAGS, event_cb)
if rc ~= 0 then
notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self._path, name))
return false
end
return true
end
function Event:add(listener)
table.insert(self._listeners, listener)
end
function Event:remove(listener)
utils.array_remove(self._listeners, listener)
if #self._listeners == 0 then
self:destroy()
end
end
function Event:destroy(message)
log.line("watcher", "Event:destroy '%s'", self._path)
if self._fs_event then
if message then
notify.warn(message)
end
local rc, _, name = self._fs_event:stop()
if rc ~= 0 then
notify.warn(string.format("Could not stop the fs_event watcher for path %s : %s", self._path, name))
end
self._fs_event = nil
end
Event._events[self._path] = nil
end
function Watcher:new(path, files, callback, data)
log.line("watcher", "Watcher:new '%s' %s", path, vim.inspect(files))
local w = setmetatable(data, Watcher)
w._event = Event._events[path] or Event:new(path)
w._listener = nil
w._path = path
w._files = files
w._callback = callback
if not w._event then
return nil
end
w:start()
table.insert(Watcher._watchers, w)
return w
end
function Watcher:start()
self._listener = function(filename)
if not self._files or vim.tbl_contains(self._files, filename) then
self._callback(self)
end
end
self._event:add(self._listener)
end
function Watcher:destroy()
log.line("watcher", "Watcher:destroy '%s'", self._path)
self._event:remove(self._listener)
utils.array_remove(Watcher._watchers, self)
end
M.Watcher = Watcher
function M.purge_watchers()
log.line("watcher", "purge_watchers")
for _, w in ipairs(utils.array_shallow_clone(Watcher._watchers)) do
w:destroy()
end
for _, e in pairs(Event._events) do
e:destroy()
end
end
return M

View File

@@ -0,0 +1,84 @@
-- luacheck:ignore 113
---@diagnostic disable: undefined-global
-- write DEFAULT_MAPPINGS in various formats
local max_key_help = 0
local max_key_lua = 0
local max_action_help = 0
local outs_help = {}
local outs_lua = {}
for _, m in pairs(DEFAULT_MAPPINGS) do
local out
if type(m.key) == "table" then
local first = true
local keys_lua = "key = {"
for _, sub_key in pairs(m.key) do
-- lua
keys_lua = string.format('%s%s "%s"', keys_lua, first and "" or ",", sub_key)
-- help
out = {}
if first then
out.action = m.action
out.desc = m.desc
first = false
else
out.action = ""
out.desc = ""
end
out.key = string.format("`%s`", sub_key)
max_action_help = math.max(#out.action, max_action_help)
max_key_help = math.max(#out.key, max_key_help)
table.insert(outs_help, out)
end
-- lua
out = {}
out.key = string.format("%s },", keys_lua)
table.insert(outs_lua, out)
else
-- help
out = {}
out.action = m.action
out.desc = m.desc
out.key = string.format("`%s`", m.key)
table.insert(outs_help, out)
max_action_help = math.max(#out.action, max_action_help)
max_key_help = math.max(#out.key, max_key_help)
-- lua
out = {}
out.key = string.format('key = "%s",', m.key)
table.insert(outs_lua, out)
end
--lua
out.action = string.format('action = "%s"', m.action)
max_key_lua = math.max(#out.key, max_key_lua)
end
-- help
local file = io.open("/tmp/DEFAULT_MAPPINGS.help", "w")
io.output(file)
io.write "\n"
local fmt = string.format("%%-%d.%ds %%-%d.%ds %%s\n", max_key_help, max_key_help, max_action_help, max_action_help)
for _, m in pairs(outs_help) do
if m.action == "" then
io.write(string.format("%s\n", m.key))
else
io.write(string.format(fmt, m.key, m.action, m.desc))
end
end
io.write "\n"
io.close(file)
-- lua
file = io.open("/tmp/DEFAULT_MAPPINGS.lua", "w")
io.output(file)
fmt = string.format(" { %%-%d.%ds %%s },\n", max_key_lua, max_key_lua)
for _, m in pairs(outs_lua) do
io.write(string.format(fmt, m.key, m.action))
end
io.close(file)

33
scripts/update-help.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/sh
# run after changing nvim-tree.lua DEFAULT_OPTS or nvim-tree/actions/init.lua M.mappings
# scrapes and updates nvim-tree-lua.txt
# run from repository root: scripts/update-default-opts.sh
begin="BEGIN_DEFAULT_OPTS"
end="END_DEFAULT_OPTS"
# scrape DEFAULT_OPTS, indented at 2
sed -n -e "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree.lua > /tmp/DEFAULT_OPTS.2.lua
# indent some more
sed -e "s/^ / /" /tmp/DEFAULT_OPTS.2.lua > /tmp/DEFAULT_OPTS.6.lua
# help, indented at 6
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_OPTS.6.lua
}; /${end}/p; d; }" doc/nvim-tree-lua.txt
begin="BEGIN_DEFAULT_MAPPINGS"
end="END_DEFAULT_MAPPINGS"
# generate various DEFAULT_MAPPINGS
sed -n -e "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree/actions/init.lua > /tmp/DEFAULT_MAPPINGS.M.lua
cat /tmp/DEFAULT_MAPPINGS.M.lua scripts/generate_default_mappings.lua | lua
# help
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_MAPPINGS.lua
}; /${end}/p; d }" doc/nvim-tree-lua.txt
sed -i -e "/^DEFAULT MAPPINGS/,/^>$/{ /^DEFAULT MAPPINGS/{p; r /tmp/DEFAULT_MAPPINGS.help
}; /^>$/p; d }" doc/nvim-tree-lua.txt

View File

@@ -1,21 +0,0 @@
#!/bin/sh
# run after changing nvim-tree.lua DEFAULT_OPTS: scrapes and updates README.md, nvim-tree-lua.txt
begin="BEGIN_DEFAULT_OPTS"
end="END_DEFAULT_OPTS"
# scrape, indented at 2
sed -n -e "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree.lua > /tmp/DEFAULT_OPTS.2.lua
# indent some more
sed -e "s/^ / /" /tmp/DEFAULT_OPTS.2.lua > /tmp/DEFAULT_OPTS.6.lua
# README.md indented at 2
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_OPTS.2.lua
}; /${end}/p; d }" README.md
# help, indented at 6
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_OPTS.6.lua
}; /${end}/p; d }" doc/nvim-tree-lua.txt