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 support for displaying lf options in the ruler #1257

Merged
merged 8 commits into from
May 24, 2023
Merged

Add support for displaying lf options in the ruler #1257

merged 8 commits into from
May 24, 2023

Conversation

joelim-work
Copy link
Collaborator

@joelim-work joelim-work commented May 19, 2023

Summary

This change enhances the ruler option to allow fields starting with lf_, which will resolve to the corresponding environment variables exported by lf. Built-in options are displayed in the format <name>=<value>, for example hidden=true, while user-defined options (added in #865) are displayed as is.

Examples

Built-in options

Display the current value of the selmode option (requested in #1174):

set ruler acc:lf_selmode:ind

User-defined options

User-defined options can be used to store arbitrary data in lf to be displayed in the ruler. This can be done by creating a script that calls something like lf -remote "send set user_test 'hello world'" and then running that script periodically (e.g. using cron).

Display the amount of free disk space remaining (requested in #1103):

#!/bin/sh

diskfree=$(df -h --out=avail . | awk 'NR == 2 { print $1 }')
lf -remote "send set user_df $diskfree"

Display the time with a blue background (styling is no longer supported, see #1257 (comment)):

#!/bin/sh

lf -remote "send set user_time \"$(date +%R)\""

Replacing the builtin df functionality

Because this change can now handle calculating free disk space externally, it leaves the question of whether it's worth keeping the df functionality added in #1168. On one hand, it does provide convenience for users, and removing it would be a breaking change, but on the other hand it adds more maintenance burden to the project (all the df_<platform>.go files), and contradicts with the minimalistic design of lf. I am leaning towards removing the df functionality since it is still relatively new, but I can understand if others prefer to keep it.

@gokcehan
Copy link
Owner

@joelim-work Thank you very much for investigating various ways to implement this feature. I really appreciate the simplicity of this approach. I have tested it briefly and it seems to work as intended. I was slightly worried that we now need to export all variables while drawing, but I have tried profiling a simple session and exporting does not seem to show up at all, so I guess it is not really a big deal.

Leaving the job to external scripts running with cron sounds reasonable. I'm not sure yet what the equivalent would look like on Windows. I also feel like there might be a way to make this work with standalone shell commands. For example, something along the following seems to work, though I'm not sure if it is a good idea or not:

&{{
    while true; do
        diskfree=$(df -h --out=avail . | awk 'NR == 2 { print $1 }')
        lf -remote "send set user_df $diskfree"
        lf -remote "send set user_time \"\033[44m $(date +%R) \033[0m\""
        sleep 10
    done
}}
set ruler lf_user_df:lf_user_time

The above currently leaves the process behind after quitting on my machine but I think it could also be handled somehow (either using on-quit or maybe some temporary files).

I'm ok with either keeping or removing the builtin diskfree, so I will leave the decision to the contributors (cc @rrveex ).

Copy link
Collaborator

@ilyagr ilyagr left a comment

Choose a reason for hiding this comment

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

Overall, this seems like a great idea.

Thinking out loud, I am also wondering if there's a better solution than suggesting cron.

The most obvious solution is to have an on-reload command that lf runs on reload. One problem with that is that it might slow down lf a lot if a user configures this variable unwisely. However, we can forcefully suggest people use & commands in on-reload. Also, if we don't introduce on-reload, people might use on-select for this, which would be even worse.

Another issue is what the on-reload command should do if the previous on-reload is still running.

Perhaps a better on-reload command would send something to a pipe, and we could suggest a script that runs something when it sees something in the pipe. This approach is inspired by how lf works; we could even build a little simple client that runs a command when the lf server sends something to its named pipe into lf.

Another good solution might be to suggest some tool that is simpler than cron and would only run while lf runs. Something like entr/watchexec, but ideally simpler and without the file watching. I don't have one in mind right now and don't know if it exists, but I might look.

@joelim-work
Copy link
Collaborator Author

joelim-work commented May 21, 2023

@gokcehan @ilyagr thanks for the review and feedback. To answer your questions.

I was slightly worried that we now need to export all variables while drawing, but I have tried profiling a simple session and exporting does not seem to show up at all, so I guess it is not really a big deal.

I agree this is a possible concern, this is being discussed in #1257 (comment).

Leaving the job to external scripts running with cron sounds reasonable. I'm not sure yet what the equivalent would look like on Windows. I also feel like there might be a way to make this work with standalone shell commands. For example, something along the following seems to work, though I'm not sure if it is a good idea or not:

&{{
    while true; do
        diskfree=$(df -h --out=avail . | awk 'NR == 2 { print $1 }')
        lf -remote "send set user_df $diskfree"
        lf -remote "send set user_time \"\033[44m $(date +%R) \033[0m\""
        sleep 10
    done
}}
set ruler lf_user_df:lf_user_time

I did not consider spawning a new process like this, thanks for the idea! I think it's possible to just have the while loop check if the parent lf is still running. Also I have found that if you source the config file again while lf is running, it will create multiple processes, so perhaps we can have a simple lockfile implementation to ensure there is only one instance. I edited your config slightly as follows:

&{{
    # prevent more than one instance if lfrc is sourced again
    lockfile=/tmp/lf_ruler.$PPID.lock
    if [ -f "$lockfile" ]; then
        exit
    fi

    touch "$lockfile"

    # quit if parent lf process no longer exists (alternatively use ps here)
    while [ -d "/proc/$PPID" ]; do
        diskfree=$(df -h --out=avail . | awk 'NR == 2 { print $1 }')
        lf -remote "send $id set user_df $diskfree"
        lf -remote "send $id set user_time \"$(date +%r)\""
        sleep 1
    done

    rm -f "$lockfile"
}}

set ruler lf_user_df:lf_user_time

I'm ok with either keeping or removing the builtin diskfree, so I will leave the decision to the contributors (cc @rrveex ).

Although I lean slightly towards removing df, I do not mind too much either way. To me this feels like one of those features that doesn't get added in if it didn't already exist, but also doesn't get removed if it already existed. @rrveex I'll leave the decision up to you.

Thinking out loud, I am also wondering if there's a better solution than suggesting cron.

The intent of this PR is to simply provide a basic mechanism for allowing the user to display options in the ruler, and it is up to users to decide how to make use of them. I only mentioned cron because it is rather well-known, but it doesn't solve every use case (e.g. it only has minute granularity, and you might want something to update every second). I am thinking that we could add some examples in the wiki Tips though.

The most obvious solution is to have an on-reload command that lf runs on reload. One problem with that is that it might slow down lf a lot if a user configures this variable unwisely. However, we can forcefully suggest people use & commands in on-reload. Also, if we don't introduce on-reload, people might use on-select for this, which would be even worse.

I thought about providing such a hook too, but I didn't really investigate it because I had some concerns:

  • If the external command is run synchronously then this will just slow down the UI drawing so isn't really a consideration.
  • If the external command is run asynchronously but takes a while to run, doesn't that mean the user won't be able to see the update until the next reload?
  • If the external command is run asynchronously and triggers a reload after finishing, is there a possibilty where you have an infinite loop where the UI drawing keeps triggering itself?

Again I haven't investigated this in detail, but I think this is also a question that can be addressed outside of this PR.

@ilyagr
Copy link
Collaborator

ilyagr commented May 21, 2023

The intent of this PR is to simply provide a basic mechanism for allowing the user to display options in the ruler, and it is up to users to decide how to make use of them. I only mentioned cron because it is rather well-known ...

That's totally fine. I think the PR will be quite useful independently of solving that problem.

I think that maybe we shouldn't "officially" recommend cron or say that this PR solves the issues that would depend on something like cron yet, at least not before checking if people can reliably get it to work for themselves. (But really, I'm hoping we'll come up with a better solution eventually)

@ilyagr
Copy link
Collaborator

ilyagr commented May 21, 2023

I thought about providing such a hook too, but I didn't really investigate it because I had some concerns:

  • If the external command is run synchronously then this will just slow down the UI drawing so isn't really a consideration.

Agreed.

  • If the external command is run asynchronously but takes a while to run, doesn't that mean the user won't be able to see the update until the next reload?

I don't think this is a problem: the command will update the lf option values, and lf will show them whenever it updates the status bar after that. (I think this would happen on any lf command, including cursor movement, right?)

  • If the external command is run asynchronously and triggers a reload after finishing, is there a possibility where you have an infinite loop where the UI drawing keeps triggering itself?

This is a very good point! I think this could be addressed, maybe, but we should certainly worry about it.

Again I haven't investigated this in detail, but I think this is also a question that can be addressed outside of this PR.

Certainly.

@joelim-work
Copy link
Collaborator Author

I think that maybe we shouldn't "officially" recommend cron or say that this PR solves the issues that would depend on something like cron yet, at least not before checking if people can reliably get it to work for themselves. (But really, I'm hoping we'll come up with a better solution eventually)

Regarding cron, we may not even have to officially mention it at all. At the time I submitted this PR, I did not realise that it was possible to just simply launch a background process from the config file as suggested by @gokcehan. Do you think those config file snippets are adequate to add to the wiki, or do you have something else in mind?

@DusanLesan
Copy link

I believe that removing the df built-in command would be acceptable at this point. It would be preferable to address it now rather than waiting for a larger number of users to adopt it. However, it would be beneficial to include the code snippet in the documentation to ensure a smooth transition for those who rely on the df command.

These changes are fantastic and align better with the app's extensibility, focusing on its core functionality rather than trying to handle every possible use case internally.

@joelim-work
Copy link
Collaborator Author

I believe that removing the df built-in command would be acceptable at this point. It would be preferable to address it now rather than waiting for a larger number of users to adopt it. However, it would be beneficial to include the code snippet in the documentation to ensure a smooth transition for those who rely on the df command.

These changes are fantastic and align better with the app's extensibility, focusing on its core functionality rather than trying to handle every possible use case internally.

Thanks @DusanLesan, I will take your opinion into consideration. I think this PR won't get merged this weekend anyway (maybe there's still some wrinkles to iron out), so in the meantime you're welcome to try the config in #1257 (comment) and see if it works for you.

Although I think the script does work nicely, there are probably some limitations. For instance I have found that the background process doesn't change it's CWD even after you cd around with lf. I believe this means if you change to a different disk then it will still show the free space on the original disk, and this effect could be confusing for users anyway.

Maybe we do need a separate timer, or some kind of on-draw hook as mentioned by @ilyagr. Also if you have any potential applications for setting user options (maybe display current Git branch?) that would also be appreciated.

@gokcehan
Copy link
Owner

@joelim-work We can merge the patch if it is ready. I wasn't sure because there is still an ongoing discussion but I guess it is mostly about future changes.

Regarding the builtin diskfree, I'm still not sure if it is better to remove it. I'm not personally using it myself so I don't mind it either way, but still I think I'm in favor of keeping it for the following reasons:

  • It can be a useful feature that some users are expected to find in a file manager. I feel like most of the alternatives we discuss here are intended for power users and I expect there will be some users who are hesitant to use these solutions for such simple functionality.
  • It also works in Windows (which would otherwise be difficult?)
  • ranger also shows this information by default.
  • It would be a breaking change to remove it (minor but valid constraint since it is a relatively new feature anyways).
  • I don't expect we get other requests in the future (ranger also shows the sum of sizes so maybe that could be requested?).

On the other hand, it also has some disadvantages:

  • It requires df_<platform>.go files for the implementation.
  • It is not customizable (e.g. only shows the current disk).
  • I'm not sure how accurate the current implementation is (e.g. due to possible differences in Bavail/Bfree and Bsize/Frsize).

A quick note about df_<platform>.go files is that I gave up on reporting the missing statvfs calls to Go x/sys. As far as I understand, Linux only has statfs as a system call and the POSIX statvfs call is implemented as a thin wrapper on top of statfs in LibC. I suspect Go only implements the native system calls so I doubt they would be interested in the missing statvfs calls. It is a little ironic that currently I think it is possible to write a portable diskfree function in C but not in Go. I think there is room for a thin POSIX library in the Go ecosystem, though I'm not sure if statvfs is the only thing in this situation or not.

I feel like the discussion can benefit from additional use cases about the use of user options other than diskfree. I have been trying a few examples myself with the on-cd command. The following shows git information in the ruler:

cmd on-cd &{{
    source /etc/bash_completion.d/git-prompt
    GIT_PS1_SHOWDIRTYSTATE=auto
    GIT_PS1_SHOWSTASHSTATE=auto
    GIT_PS1_SHOWUNTRACKEDFILES=auto
    GIT_PS1_SHOWUPSTREAM=auto
    git=$(__git_ps1 " (%s)") || true
    lf -remote "send $id set user_git \"$git\""
}}
on-cd
set ruler lf_user_git

Another example is to show the sum of sizes in the current directory:

cmd on-cd &{{
    sum=$(ls -la | awk '{ sum += $5 } END { print sum }' | numfmt --to=si)
    lf -remote "send $id set user_du \"sum:$sum\""
}}
on-cd
set ruler lf_user_du

Do we have other use cases to stimulate the discussion?

The way I see this patch, the primary purpose is its capability to show the values of options in the ruler. The secondary purpose is acting as a barrier to prevent requests about niche use cases, though I'm not sure diskfree is one of them especially if it requires making use of cron or polling standalone commands for the implementation.

Copy link
Collaborator

@ilyagr ilyagr left a comment

Choose a reason for hiding this comment

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

The code looks great to me.

In retrospect, it would have been good to mark the diskfree functionality as experimental and subject to change when we merged it. Perhaps we could do it now.

At the moment, I don't have an opinion on whether the diskfree stuff should stay as is, be changed, or be removed entirely in favor of the scripts. I'll need to play with the scripts a bit to decide, and perhaps other people will try that out as well.

eval.go Outdated
app.ui.echoerr("ruler: should consist of 'df', 'acc', 'progress', 'selection', 'filter' or 'ind' separated with colon")
return
if !strings.HasPrefix(s, "lf_") {
app.ui.echoerr("ruler: should consist of 'df', 'acc', 'progress', 'selection', 'filter', 'ind' or start with 'lf_' separated with colon")
Copy link
Collaborator

@ilyagr ilyagr May 21, 2023

Choose a reason for hiding this comment

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

Minor and optional: I was thinking about how to better word this. Here's one idea.

"ruler: invalid value '%s'. Must be a colon-separated list consisting of a subset of 'df', 'acc', 'progress', 'selection', 'filter', 'ind', or 'lf_OPTION_NAME'."

I'm also worried that this is usually too long to fit one the screen. Perhaps we should point people to the docs instead of listing the options? Or print the error to stderr? I don't think we have to resolve that for the purposes of this PR, though. For now, if people really want to read it, they can maximize their window, make the font tiny, and try again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For now I just simply changed start with 'lf_' to lf_<option_name>, hopefully that makes the grammar a bit more clear to understand.

I don't really have a strong opinion on how to format these error messages, apart from the desire to keep things fairly consistent among the different user options. Both ruler: should consist of ... and ruler: invalid value, please see the documentation for more information sound fine to me. One other thing worth mentioning is that if all the different fields/possibilties are listed, it's possible someone in the future may forget to update the error message when adding a new one. That being said, I haven't seen any issues raised about unclear error messages, and I (would like to) think that users are generally smart enough to turn to the documentation anyway if they need help on configuration, so I'm not really inclined to change anything right now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Feel free to go with whatever makes sense to you. This is a very optional suggestion, and I agree this doesn't matter very much.

I'll still elaborate: I was mainly thinking of 1) including the offending value and 2) saying "colon-separated list" first, in the part of the message that would probably fit on the screen.

Copy link
Collaborator

Choose a reason for hiding this comment

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

And by the way, I think the change you made definitely helps. In general, I think the PR is in a good shape. Thanks for creating it in the first place and and fixing all the nits!

@joelim-work
Copy link
Collaborator Author

The way I see this patch, the primary purpose is its capability to show the values of options in the ruler. The secondary purpose is acting as a barrier to prevent requests about niche use cases, though I'm not sure diskfree is one of them especially if it requires making use of cron or polling standalone commands for the implementation.

@gokcehan I think this a good interpretation - the idea is to store all the options as a map[string]string so that they can be displayed in the ruler (e.g. selmode, hidden, sortby), and the fact that user options are also provided (for custom use cases) is a bonus. If that is the case, then I would say that this PR is ready for merging.

Regarding the diskfree code, I'm actually starting to think it should be kept, at least for the following reasons:

  • I did a search in GitHub for ruler and it already looks like there are people who are already using this, and it doesn't even count users who don't make their dotfiles public.
  • I took a very quick look at the code in ranger and from the looks of it (correct me if I'm wrong) the disk space is calculated synchronously when drawing the UI. I can't see any way to implement this using external commands without some kind of drawback:
    • Invoking an external command synchronously is too slow.
    • Invoking an external command asynchronously or via a timer results in a small (and sometimes noticeable) delay from when the UI is initially drawn, and then refreshed with the updated information.
    • Invoking external commands from hooks like on-cd as you have shown works, but is limited to updating only when the hook is actually triggered. For example, switching to a different Git branch, or creating a new file externally won't trigger the hook and refresh the information.
  • As lf is a file manager, I think users will generally want some file-related features to be built-in without having to mess around with shell scripts, and perhaps the df_<platform>.go files can serve as a reference for other similar features in the future.

I can't really think of many other use cases myself, TBH the idea of setting user information externally and then displaying it in the statusbar is already quite niche.

@ilyagr Hopefully I have addressed all of your comments, if not then let me know. Thanks once again for your review.

@joelim-work
Copy link
Collaborator Author

I have found another use of user-defined variables, which is to alter the way a builtin setting is displayed (i.e. display something based on the value of builtin settings). For example, the following config shows hidden when the hidden option is enabled, and nothing if disabled:

set ruler acc:lf_user_hidden:ind

cmd draw_hidden %{{
    if [ "$lf_hidden" = true ]; then
        lf -remote "send $id set user_hidden hidden"
    else
        lf -remote "send $id set user_hidden"
    fi
}}

map zh :set hidden!; draw_hidden

# start with hidden enabled (optional)
set hidden; draw_hidden

@gokcehan
Copy link
Owner

@joelim-work Thanks again for the patch.

@gokcehan gokcehan merged commit e08eebb into gokcehan:master May 24, 2023
@joelim-work joelim-work deleted the ruler-enhance branch May 24, 2023 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants