Skip to content
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

add 'setlocal' for directory specific options #1381

Merged
merged 15 commits into from Sep 2, 2023
Merged

add 'setlocal' for directory specific options #1381

merged 15 commits into from Sep 2, 2023

Conversation

gokcehan
Copy link
Owner

cc #117 #171

This has been on the back of my mind for quite a while as sorting the downloads directory by time automatically is quite common in other file managers, so I decided to give this a try today. I'm pushing this as a PR to benefit from our usual review mechanism, so discussion is welcomed.

You can try this with the following example:

setlocal ~/Downloads sortby time

This is still a draft. Some things that are worth mentioning are as follows:

  1. There were two approaches in my mind before I started working on this, keep the local option values in dir or in a global map. First approach seemed a little complicated as we want to be able to set local options even if the corresponding directories are not loaded yet. On the other hand, the second approach requires checking the global map each time an option is used, though I don't expect it to create a performance bottleneck.

  2. Adding a new keyword (i.e. setlocal) seemed inevitable. I also thought about reusing the set keyword with an optional directory value, but set already have an optional field for boolean options, so setlocal seemed like the safer choice. It is possible to shorten setlocal to setl but I think it is better to be explicit in this case.

  3. I have only added sortby so far for testing. In general, there are many options that are not applicable as a local option or they don't make much sense even if they are applicable. So I thought it would be better to add options selectively whenever they make sense. Currently, I'm thinking of only adding options related to sorting and maybe also things like info initially. I expect these options to cover most use-cases that users have in mind.

  4. There is currently no way to remove a local option once it is set. If there is a need for it, I'm not sure how it should work yet.

  5. There could be some issues related to sortType. If a local option in this struct is set, then I think other options in this struct will not be updated when the corresponding global option value is changed. I'm not yet sure if this is a niche case or it is worth considering. Also, I think we have some inconsistencies about the included options in sortType. For example, I think dironly could have been added to sortOption as well. I think the reason ignorecase and ignoredia is excluded is due to them being used in places other than sorting. I'm not sure about hiddenfiles but maybe it could have been added as a separate field to sortType. I don't remember much about why we introduced sortType but one possibility is to get rid of it altogether to fix the issue with setlocal.

@joelim-work
Copy link
Collaborator

I have never thought of this before. I guess the idea is to allow directories to actually have different settings instead of the hack mentioned in the wiki which changes the global settings whenever a certain directory is visited.

I think the biggest aspect to consider is the interaction between global vs local settings. From a quick look at the patch, it looks like you just simply make a clone of the global settings when setlocal is called, but never forward updates from the global settings to the local settings afterwards (as mentioned in point 5). One other idea is to have the local settings allow for some kind of nil value for each setting, in which case it will fall back to the global settings. This is more intuitive to me, but possibly harder to implement. Maybe you need something like map[{path, option}]value, or even a separate map for each option, but I haven't thought this through properly.

Regarding the syntax, setlocal sounds fine to me. I think abbreviating it to setl is not worth it because for config files the extra letters won't matter, and for the command line there is tab completion. As for unsetting an option, I guess it's possible to follow Vim syntax and use setlocal <path> option<.

I guess dironly can be moved to sortOption as well, if you regard sorting and filtering to be the same operation, which is pretty much the case for dir.sort. TBH this can be addressed separately outside of this PR, and in any case the storage of these values is internal so I don't see much issue moving them around.

@gokcehan
Copy link
Owner Author

@joelim-work Thank you for the feedback. I wasn't aware of the example in the wiki page. I have tried it today and it seem to work well, though it is worth explicitly mentioning that its behavior is slightly different than this patch. Since it changes the global option, it effects each directory in the ui, which can result in a disorienting flicker. It also does not work if the corresponding directory is in the preview pane. So I still think a separate setlocal feature is beneficial, though it can still be argued whether the additional complexity is worth it or not. I'm slightly worried that we haven't seen much interest in the patch so far. I'm planning to use this feature myself, but we can also consider disregarding it if there aren't much interest in this. In any case, I will keep working on this for a while.

It is nice to have the on-cd example for this purpose, since we can use it to argue against adding options with niche usecases as local options. I have now tried adding the rest of the options in sortType. I changed the data structure to separate maps as you suggested. It should now fall back to global options as expected. There are still some other options that I'm planning to add.

For the boolean options, I haven't added the set option! syntax as I wasn't sure how it should work. If the option is not set, we could set it to the opposite of the global value. However, if the option is set, then there are two possibilities, setting it to the opposite of global value or the local value. In any case, option toggling is mostly intended for keybindings so I don't see much benefit in adding it to local options.

It sounds like a good idea to follow vim convention syntax to reset an option (I think maybe you changed & to < in your example since & is used for the async shell commands). I was thinking, such an additional syntax can be considered for global options as well, so I haven't added anything to this patch so far.

It seems that sortType was added in a0e1d4d so that we could have a simple comparison as if d.sortType != gOpts.sortType { ... } while checking the directory. But we have added many other options afterwards to this check so there isn't much benefit in this anymore. So I'm thinking of converting these to regular booleans, either in this patch or separately afterwards.

@joelim-work
Copy link
Collaborator

To me, the on-cd example and setlocal are fundamentally different in that:

  1. on-cd involves global options and will apply to all panes, not just the pane showing the current directory.
  2. setlocal affects only the current directory, and the effect is applied regardless of which pane it shows up in.

TBH I don't think the on-cd example would have been created if setlocal had existed as a feature in the first place - I would actually remove the on-cd example from the wiki once this feature gets merged because I don't think it offers any benefit over the setlocal approach.

Regarding the toggling of boolean options, I would expect setlocal to toggle the local value. If the local value doesn't exist, then first copy from the global value before toggling (i.e. set to the opposite of the global value). I'm not sure if there's a need to consider toggling now or leave it until later, though I can see a use case for adding it as a keybinding like map zH &lf -remote "send $id setlocal $PWD hidden!".

Despite suggesting it, I'm not 100% convinced on using the Vim syntax for resetting an option since it may not be intuitive to users. Using ! for toggling is fine because it typically means not in programming languages but I'm not so sure about & or <. Also, correct me if I'm wrong, but set & is used to set a global option back to its default value, whereas setlocal < is used to set a local option to the global value, which sounds similar but is technically different. I am probably still leaning towards the Vim syntax though, and it's probably fine to change later on because I suspect this feature will end up being quite niche and not impact a lot of users.

Anyway I don't have much more to add, the PR in general looks fine to me and I would probably approve it once it's polished (e.g. documentation and unit tests added). But it's also OK to leave it open to see if anyone else has feedback.

@gokcehan
Copy link
Owner Author

@joelim-work I wasn't aware of setlocal < in vim. I don't think I have ever used it before. Anyway, I haven't added anything about it to this patch.

@gokcehan gokcehan marked this pull request as ready for review August 20, 2023 17:12
@MahouShoujoMivutilde
Copy link

Hi, author of the original example from the wiki here.

Nice to have a native way to apply local options. Also, there is nothing wrong with implementing some options just for yourself (considering you are the author in first place), especially if it doesn't introduce a breaking change.

Is it meant to only apply to a given directory, no recursion?

Because, in my example recursion is the point - you have some directories that have things you want sorted.

Like

/mnt/movies/one_movie/ep1.mkv
/mnt/movies/one_movie/ep2.mkv
/mnt/movies/one_movie/ep3.mkv
...
/mnt/movies/another_move/ep1.mkv
/mnt/movies/another_move/ep2.mkv
/mnt/movies/another_move/ep3.mkv
...

and you wouldn't want to write

setlocal /mnt/movies/one_movie sortby natural
setlocal /mnt/movies/another_movie sortby natural
...

for every single one of them, right?

Because sorting episodes right is the point, not alphabetically sorting movies by name.

@gokcehan
Copy link
Owner Author

gokcehan commented Aug 20, 2023

@MahouShoujoMivutilde Thank you for the feedback. To be honest, I haven't even considered recursion so far. Now I can see how the on-cd example works recursively.

One possible workaround is to recursively set local options at launch with something like:

&find /mnt/movies -type d -exec lf -remote "send $id setlocal {} sortby natural" \;

I think this is much simpler than the current on-cd example. Also, it does not have the mentioned issues related to changing the global option values. However, it does not automatically update if the directory structure is changed after launch (e.g. create/rename directory).

If we want to add a builtin support for this, then I guess the usual way would be to allow glob patterns in setlocal. But then I don't think we can simply lookup a local option value with a given path anymore. We might have to try matching the given path with all existing set local options, which can be relevant if there are too many set local options. We had a similar issue when we implemented globs for our colors and icons, and we solved it by only allowing glob star patterns at specific places (e.g. before/after filename/extension). In this case, maybe we can try matching at every parent directory. For example, given a path /mnt/movies/one_movie, we can try looking up the following patterns:

  1. /mnt/movies/one_movie
  2. /mnt/movies/one_movie*
  3. /mnt/movies*
  4. /mnt*
  5. /*

There could be some issues with trailing / in patterns (e.g. /mnt* is different than /mnt/*). In general, I'm not yet sure if it is worth supporting globs. The given workaround might be good enough for most cases.

@MahouShoujoMivutilde
Copy link

MahouShoujoMivutilde commented Aug 20, 2023

I don't like this workaround because it is static for a session and is IO-intensive (even when you don't plan to visit those directories).

I can sort-of convert the on-cd example to make it more dynamic

cmd on-cd &{{
    case "$PWD" in
        /mnt/movies*)
            lf -remote "send $id setlocal '$PWD' sortby natural"
            lf -remote "send $id setlocal '$PWD' noreverse"

            lf -remote "send $id echomsg setlocal: sort = natural"
            ;;
    esac
}}

But it has predictable flicker issues.

This can be avoided with checks even more complex than original wiki example and more dynamic find, at which point it doesn't really make sense to do.

I am okay with limited globs, even just prefix string matching tbh.

@joelim-work
Copy link
Collaborator

I had not considered recursive directories either, but I think it makes sense to support this functionality. I agree with @MahouShoujoMivutilde that the find approach isn't suitable since it is evaluated only once on startup and doesn't consider directories that are created afterwards.

Regarding the syntax, I would prefer to have it referred to as a 'glob-like syntax' rather than actually implementing globs. As an alternate suggestion, given the example current directory of /mnt/movies/one_movie, the lookup order could be:

  1. /mnt/movies/one_movie
  2. /mnt/movies/*
  3. /mnt/*
  4. /*

The lookup logic will have to be applied for each option, but I guess this can be extracted out into a separate function which takes a string path and returns a []string list of keys to lookup.

I suppose this also means that multiple options can be applied even if they come from different keys. For example given the following settings:

setlocal /mnt/movies/one_movie hidden
setlocal /mnt/movies/* reverse

Then both the hidden and reverse options will apply when displaying /mnt/movies/one_movie.

Copy link
Collaborator

@joelim-work joelim-work left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch in its current state looks fine.

I guess it's up to you whether you want to implement additional changes like recursive directory options here or separately.

@gokcehan
Copy link
Owner Author

@joelim-work Is there an advantage for keeping the trailing / when matching? Also, shouldn't there be a matching for /mnt/movies/one_movie and its subdirectories in your example (e.g. /mnt/movies/one_movie/*)?

I agree this shouldn't be referred as globs. I think maybe we can call it subdirectory matching or something similar. With this in mind, I also tried using the trailing / instead of * as follows:

setlocal /mnt/movies/one_movie  ... -> only 'one_movie' directory
setlocal /mnt/movies/one_movie/ ... -> 'one_movie' directory and its subdirectories

I'm not yet sure if this is a good idea. It avoids the use of a special character for this purpose (e.g. *), but it is also a little unusual. It can be relevant if someone had a directory with a trailing * in its name, though I don't expect this to be common case.

@joelim-work
Copy link
Collaborator

@gokcehan Apologies I missed the fact setlocal /mnt/movies/one_movie<modifier> ... was intended to affect the directory itself in addition to any subdirectories, ignore my example above.

I don't know whether a trailing * or / is more intuitive to users. Actually I think either is fine, so long as it is mentioned in the documentation.

@MahouShoujoMivutilde
Copy link

Maybe / is better because it avoids giving the false impression that it is a glob (and thus avoiding users trying complex patterns like /mnt/*/some*thing which won't work).

But both are fine, as long as it is explicitly stated in the docs that it only works at the end of the path.

@gokcehan gokcehan merged commit 8d013c9 into master Sep 2, 2023
4 checks passed
@gokcehan gokcehan deleted the setlocal branch September 2, 2023 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants