-
-
Notifications
You must be signed in to change notification settings - Fork 610
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Allow to expand until certain condition is met #2790
base: master
Are you sure you want to change the base?
Conversation
I love the concept and it is something we should do. Tried your example after a lot of hacking and it looks to work nicely.
Use of private functions in your example aside, this is far too complex and not approachable for users. Having to populate the children is the key problem. Suggestion: come up with a simple use case and example - one that would go in the documentation. We can start simplifying from there. Perhaps I'm misunderstanding: why does this need to be an iterator? I was imagining the user would supply a simple boolean returning function to test against a node. |
Any updates on this one @ghostbuster91 ? |
8cdcde4
to
9f0a224
Compare
Sorry, I was quite busy. I am going to take a look at it.
I am glad to hear that!
Because the simple
When expanding In my case the condition is following: Perhaps this could be expressed in simpler way for example by using a flag -
update: I would see it as: local function expand_one_more(node)
return {expand = true, fn_next = stop_expansion}
end
local function expand_until_non_single(node)
local has_one_child_folder = explorer_node.has_one_child_folder(node)
if has_one_child_folder then
return {expand = true, fn_next = expand_until_non_single}
else
return {expand = false, fn_next = expand_one_more}
end
local f = function(node)
if node.open then
lib.expand_or_collapse(node, nil)
else
if node.nodes then
api.tree.expand_all(node, expand_until_non_single)
else
edit("edit", node)
end
end
end
map('n', '<CR>', wrap_node(f), opts("Expand until not single or collapse")) |
In the end I was able to simply replace the iterator with accessing node's siblings via local function expand_until_non_single(count, node, populate_node)
populate_node(node)
if node.nodes == nil or not node.parent.open then
return false
end
local has_one_child_folder = explorer_node.has_one_child_folder(node)
local has_no_siblings = explorer_node.has_one_child_folder(node.parent)
if has_one_child_folder and count == 0 then
return true
elseif has_no_siblings then
return true
else
return false
end
end The CI keeps failing with following error:
Any idea how to fix it? |
@@ -36,7 +36,7 @@ end | |||
---@param node Node | |||
---@return boolean | |||
function M.has_one_child_folder(node) | |||
return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R") or false | |||
return node.nodes ~= nil and #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R") or false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: guarding against calling this function with a file node
This minimal setup worked nicely. I've changed things a bit to use more api and less internals. However, it's still using The user must be able to write their function via API only. vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
vim.cmd([[set runtimepath=$VIMRUNTIME]])
vim.cmd([[set packpath=/tmp/nvt-min/site]])
local package_root = "/tmp/nvt-min/site/pack"
local install_path = package_root .. "/packer/start/packer.nvim"
local function load_plugins()
require("packer").startup({
{
"wbthomason/packer.nvim",
"nvim-tree/nvim-tree.lua",
"nvim-tree/nvim-web-devicons",
-- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
},
config = {
package_root = package_root,
compile_path = install_path .. "/plugin/packer_compiled.lua",
display = { non_interactive = true },
},
})
end
if vim.fn.isdirectory(install_path) == 0 then
print("Installing nvim-tree and dependencies.")
vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path })
end
load_plugins()
require("packer").sync()
vim.cmd([[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]])
vim.opt.termguicolors = true
vim.opt.cursorline = true
local api = require("nvim-tree.api")
local lib = require("nvim-tree.lib")
local explorer_node = require("nvim-tree.explorer.node")
local function expand_until_non_single(count, node, populate_node)
populate_node(node)
if node.nodes == nil or not node.parent.open then
return false
end
local has_one_child_folder = explorer_node.has_one_child_folder(node)
local has_no_siblings = explorer_node.has_one_child_folder(node.parent)
if has_one_child_folder and count == 0 then
return true
elseif has_no_siblings then
return true
else
return false
end
end
local function expand_until()
local node = api.tree.get_node_under_cursor()
if node.open then
lib.expand_or_collapse(node, nil)
else
if node.nodes then
api.tree.expand_all(node, expand_until_non_single)
else
api.node.open.edit(node)
end
end
end
local function on_attach(bufnr)
local function opts(desc)
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
end
api.config.mappings.default_on_attach(bufnr)
vim.keymap.set("n", "O", expand_until, opts("Expand until"))
end
_G.setup = function()
require("nvim-tree").setup({
on_attach = on_attach,
})
end |
Suggestions: lib.expand_or_collapse(node, nil) Replace with local has_one_child_folder = explorer_node.has_one_child_folder(node)
local has_no_siblings = explorer_node.has_one_child_folder(node.parent) Write your own test in this case. It will be a lot simpler as you have tested most of the conditions already. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good, we'll get there:
- simplify
expand-all.lua
preserving existing structure - remove unused variables etc. as per luals warnings
- remove any usages of internals like
lib
- add options to
api.tree.expand_all
, documenting in help and api.lua - add an example to help, the absolute bare minimum, try to keep to 10 lines. If not, we can add to the wiki.
expansion_count = expansion_count + 1 | ||
expand(node) | ||
populate_node(node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite following the logic here, but it looks like we're not actually making many changes e.g.
expand
was renamed topopulate_node
- unnecessary
should_expand
boolean was extracted get_last_group_node
was moved out ofexpand
Please can you refactor this down to the minimum number of changes necessary. Integration with the existing structure will result in simpler code that is more easily reviewable and maintainable.
I appreciate that we've been through many revisions to get to this point, with complexity from earlier commits removed, however we need to revert some structural changes. Perhaps consider how you'd make these changes from scratch, knowing what you have learned over the course of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please can you refactor this down to the minimum number of changes necessary. Integration with the existing structure will result in simpler code that is more easily reviewable and maintainable.
I totally agree with such approach. I think I was able to cut down amount of changes to the bare minimum.
There is one thing that I would like to discuss - changes in the recursor
function. Before, when the logic was only to expand everything recursively, the condition was to check if node.open==true
(putting expansion_count
aside). In my case that is invalid as some nodes have been opened but they shouldn't be traversed further.
In both cases reusing should_expand
function results in correct behavior. First of all, this doesn't have to be true in general. Maybe sometimes the logic for should_recurse
will be different than the logic for should_expand
.
Second. So, for each node we call should_expand
twice, once to check if we should expand and second time to check if we should recurse. Between these calls, if should should_expand
returns true
, we call expand
that changes the state of a given node. This means that users must not relay on node.open
in their should_expand
implementation as this would result in incorrect behavior.
I don't have enough experience in developing nvim plugins to judge if this is a problem, but wanted to bring this to your attention. Maybe this should at least be mentioned in the documentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In both cases reusing should_expand function results in correct behavior. First of all, this doesn't have to be true in general. Maybe sometimes the logic for should_recurse will be different than the logic for should_expand.
I like the consistency, it's more reason-able and will make maintenance easier.
This means that users must not relay on
node.open
in theirshould_expand
implementation as this would result in incorrect behavior.
So long as we document that we are providing a consistent user experience.
Is node.nodes
correct; can they test that?
With all these changes my custom should expand function looks as follow: local function has_one_child_folder(node)
return #node.nodes == 1 and node.nodes[1].nodes and
vim.loop.fs_access(node.nodes[1].absolute_path, "R") or false
end
local function expand_until_non_single(_, node, populate_node)
populate_node(node)
if node.nodes == nil or not node.parent.open then
return false
end
return has_one_child_folder(node.parent)
end Thank you for helping me getting this PR across the finish line. I truly appreciate your time and attitude 🙇♂️ |
Sorry about that; we are doing some significant refactoring of explorer/reload etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking incredible - the sample gives the user exactly what they need without overwhelming them.
I hope you don't mind: in the interest of speed I've pushed a couple of doc / example tweaks.
-
Address possibly unused param: feat: Allow to expand until certain condition is met #2790 (comment)
-
Issue: Sibling directories are not expanded:
mkdir -p foo1/bar/baz/foo/bar/baz
mkdir -p foo2/bar/baz/foo/bar/baz
doc/nvim-tree-lua.txt
Outdated
end | ||
|
||
-- on_attach | ||
vim.keymap.set('n', 'Z', api.tree.expand_all(node, { expand_until = my_expand })) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I needed to wrap this in a function:
vim.keymap.set("n", "Z", function()
api.tree.expand_all(nil, { expand_until = my_expand_until })
end, opts("My Expand All"))
or
local function my_expand_all()
api.tree.expand_all(nil, { expand_until = my_expand_until })
end
---
vim.keymap.set("n", "Z", my_expand_all, opts("My Expand All"))
We've hit two edges in our documentation here:
- We don't specify how to inject a node when there are
{opts}
- this is the first one. - We don't give instructions on the need to use a function when parameters are needed.
That's OK - we can add those in a follow up PR to keep this one clear: #2899
Pushed minimal doc update.
@@ -47,13 +59,14 @@ local function gen_iterator() | |||
Iterator.builder(parent.nodes) | |||
:hidden() | |||
:applier(function(node) | |||
if should_expand(expansion_count, node) then | |||
if should_expand(expansion_count, node, populate_node) then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alex-courtis just fyi: I didn't forget about this. Just trying to finish my new apartment, so I can finally move in. I will try to finish this soon. |
This is my attempt to solve #2789
With this change I can now define the following mapping:
This works as expected however it has some downsides:
expand_all
function will not expand the last directory. It stops as soon asshould_expand
returnsfalse
but I wanted the last directory to be expanded.I had to copyreplaced withpopulate_children
method which is not a part of public apiexplorer.explore(node, status)
Btw if this got accepted in some form I am going to replace the default expand behavior in my config with this, since I find it so useful.