-
Notifications
You must be signed in to change notification settings - Fork 330
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
Flexible filename truncation #1029
Conversation
This is an interesting idea but I think you could improve upon it. For example if you have long filenames that match in some places then the algorithm should work this out and show the parts that differ. Having a fixed percentage can't do this and it wouldn't be able to show the relevant parts. IMO this implementation (with a fixed percentage) does not offer enough benefits over the current version. |
Thanks 😃
Sure, there's always room for improvement. However, I see the following points with this suggestion, which is basically an extension of the requirements:
Well, I tend to disagree. In my experience resp. in my filesystems most long filenames differ in the beginning or much more often in the end, especially the file ending, e.g. .jpeg vs. .pdf, or .h vs. .c etc. I never had difficulties with the Midnight Commander (regarding this issue), but so far with lf, that's why I proposed this feature, which solves my problem. As the proposed extension has not so much benefits (in my opinion), especially with the changes to the code needed, I do not see it as part of this Pull-Request, but maybe for another one (if someone volunteers to implement it and the maintainer is ok with it). |
@raslop Thanks for the patch but there is already a lot of complexity with truncation due to having multiple options for configuration (e.g. Even if you make it work I also don't see much benefit in this feature that will make the additional complexity acceptable. For many of the usecases I can image there are other ways to handle (i.e. switching to single column when filenames are long). |
Well, I think the complexity with truncation is not too high as of now. And the added complexity by my patch is also reasonable (in my opinion, I would even say it simplifies one or two things in ui.go). It worked well in my tests yesterday, even with fileinfo enabled, though I didn't encounter the issue shown. I was able to reproduce the mentioned issues and fixed them, see FIXUP commits (to be squashed before merge).
As said before, I believe the additional complexity acceptable.
Right, this is not useful for every use-case and especially not for very narrow windows or extremely long filename. In such cases I indeed switch to a single-column view. But anyway, in some use-cases this is really helpful and save some keypresses to switch to a one-column view. |
@raslop Here's an example with the latest code: This should be enough to argue that the complexity isn't worth it. |
As I want this feature, I'm willing to invest more time to finish it, until you it's good enough for you. I think it's not too far anymore. If you are willing to give further feedback, I would like to continue. |
@raslop The issue is that
Isn't that the problem? Now that you have been working on this part of the code for the last couple of days, you probably have a much better understanding of the code than anyone else currently. Personally, I don't remember anything about this part of the code. You will be in the same spot as me in 3 months or so. If something pops up, there won't be anyone to fix it without bashing their head. This is what I mean by complexity.
My only aim to have a rock solid stable software. All I did here was to play around for a few minutes to come up with these bugs. Previously, some of the bugs related to this part of the code were not even be discovered for a long time. The way you test these things is to systematically change options and parameters. For example if there are 3 boolean options, you may need to evaluate 8 different use cases by also changing the terminal size dynamically at the same time. This means an exponentially growing number of use cases. Currently, I think we already have many more options (e.g. If you like, you can keep the PR open for a while for interested users to test it. It can also be useful to see if there is enough demand for this feature by others. With enough people, I think I can usually be pressured into accepting a patch. Nowadays, I'm hoping to push a new release if I can motivate myself into composing a changelog. I'm not planning to include any other features before the release. |
Preliminary report, mainly just FYI.
I don't think so. Altogether I only spent a few hours on it, and the vast majority of it was to find out that
Ah, ok, now I get it (I think). There was obviously a misunderstanding. When reading "complexity" I though about the complexity of the problem or algorithm (which is quite low here). But you mean the "complexity" of the source code, which closely relates to maintainability and readability of the source code. I agree that the readability could be better, e.g. the repeated calculations of widths with similar meaning in different places is hard to grasp (e.g.
I agree, that systematic testing is necessary, e.g. to avoid regressions. In absence of automatic test-tools (I couldn't find any) I only did some manual tests with different values for the options that I thought have influence on my change. I obviously missed some options. From your comment it seems that you also did such tests manually in the past. The large number of test-cases calls for automation. So I wrote a little script that does mostly automated regression tests for one version of lf against a reference version, while permuting all necessary parameters. It employs tmux to control the window width and capture the output of lf after setting lf options with I have pushed another fix-up commit and used the regression-test-script to verify the absence of further regressions regarding the following options: I still see 1846 differences out of 11264 test-cases, all with the same pattern. The reference version (i.e. trunk-version of lf) sometimes truncates the filename to fit the available space, but doesn't append the truncation character. See below screenshot, which shows the diff of this-branch-lf vs. trunk-lf, e.g. the filename Regarding While there are not real regressions, the actual feature I'm trying to implement still has a bug unfortunately.
Does that mean, you won't have another look at it, until more people are interested in this? |
@raslop Your attention to detail seems more than I'm looking for. Ping me when you are ready and I will take a look at this again. And sorry for being uptight about these things. I have spent a lot of time fixing broken patches of others in the past and I don't want to do it anymore. |
misc.go
Outdated
@@ -47,7 +50,7 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune { | |||
} | |||
curr += w | |||
} | |||
return nil | |||
return rs[b:curr] |
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 doesn't look right - curr
is a cell index (unlike b
which is a rune index) so it doesn't make sense to use curr
here for indexing rs
. I agree the current implementation doesn't work for a few edge cases, and have suggested an alternate implementation in #1270, but I don't know if it will get accepted or not.
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.
Ok.
I've tried your alternate implementation from #1270 and it works fine for the purpose of this PR too (i.e. properly returns the last part of a rune slice and looks good with different values of fnametruncpct
and my ui regression test script shows no regressions).
So I would like your alternate implementation to be merged.
After a long time I worked again on this PR. I have rebased and force-pushed the the branch. Some of the bugs mentioned above have already been fixed on the master branch, thus this change became easier. The mentioned regression-test-script (not part of this PR) tested ~117k configuration combinations and found no differences. However, I just noticed #1270 and decided to make my PR a draft again, until the |
Hi @raslop, if you really want to have this feature accepted, I think it's best to split the task into two parts, one to refactor the code and another to actually implement the feature. I have already submitted a PR for the refactor part, see #1272. This isolates the filename truncation code into one location so that it is easier to work with, but maintains the same intended behaviour without adding any features. Hopefully once that gets merged, you can then implement your feature on top of it. Apologies for intruding upon your PR, but this cleanup was something I wanted to do regardless. Looking at the comment history in this PR, I think it was quite unfortunate you chose to work on this before I cleaned up the |
Sounds reasonable.
Thanks, that's great. I had a short (not too deep) look at it, and it looks good to me (and vaguely familiar).
Yes, my feature will then only change a few lines 😃
No need for apologies. I'm happy someone with more knowledge about lf and especially go did/does this cleanup. |
Now that #1272 and #1273 are merged (thanks @joelim-work and @gokcehan), I'll try again with my feature. I've rebased to the current master and force-pushed my branch. I figured that [I'm still not sure if the option name |
misc.go
Outdated
@@ -53,6 +53,19 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune { | |||
return rs[b:] | |||
} | |||
|
|||
// Returns the last runes of `rs` that take up at most `max_width` space. | |||
func runeSliceWidthLastRange(rs []rune, max_width int) []rune { |
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.
The implementation for this function looks fine, but please add unit tests in misc_test.go
, including tests with Chinese characters like 世界. You can use the same inputs as the unit tests for runeSliceWidthRange
.
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.
Sure, test cases are now added.
misc.go
Outdated
@@ -53,6 +53,19 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune { | |||
return rs[b:] | |||
} | |||
|
|||
// Returns the last runes of `rs` that take up at most `max_width` space. | |||
func runeSliceWidthLastRange(rs []rune, max_width int) []rune { | |||
last_width := 0 |
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.
Regarding naming conventions, the recommendation for Go is to use camelcase, see https://go.dev/doc/effective_go#mixed-caps
last_width := 0 | |
lastWidth := 0 |
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.
Ok, thanks. Is now changed.
ui.go
Outdated
@@ -468,8 +468,11 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir | |||
|
|||
filename := []rune(f.Name()) | |||
if runeSliceWidth(filename) > maxFilenameWidth { | |||
filename = runeSliceWidthRange(filename, 0, maxFilenameWidth-1) | |||
var truncatePos int = (maxFilenameWidth - 1) * gOpts.fnametruncpct / 100 |
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.
Minor: I'm not sure if you had a reason for not using short declarations (i.e. :=
syntax), but from what I can see most of the code uses this so it would be better to follow it:
var truncatePos int = (maxFilenameWidth - 1) * gOpts.fnametruncpct / 100 | |
truncatePos := (maxFilenameWidth - 1) * gOpts.fnametruncpct / 100 |
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.
Well, I want truncatePos to be an integer instead of a float. I vaguely remember that this "expclicit cast" (or what ever this is called in go) was necessary. But now I can't reproduce this anymore and it seems to make no difference. So I have changed it to the short declaration.
@raslop We already have a Note, I will not be around for a couple of weeks, but I will try to take a look at this again when I get access to a computer again. |
As of now, lf truncates always at the end. So for long filenames one can not see the end (especially not the file ending, like .jpeg or .pdf). [MidnightCommander](https://github.com/MidnightCommander/mc/) truncates too long filenames in the middle, so it's possible to see the beginning and the end of the too long filename. This commit allows to specify a flexible point for the truncation in percent of the available space.
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.
@gokcehan , @joelim-work Thanks for your time, comments and suggestions.
I have updated the branch (rebased and force-pushed) and done the changes asked for (rename option to truncatepct
, add test cases for new function, adhere to naming convention).
If there's still anything missing or not right yet, please let me know.
misc.go
Outdated
@@ -53,6 +53,19 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune { | |||
return rs[b:] | |||
} | |||
|
|||
// Returns the last runes of `rs` that take up at most `max_width` space. | |||
func runeSliceWidthLastRange(rs []rune, max_width int) []rune { | |||
last_width := 0 |
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.
Ok, thanks. Is now changed.
ui.go
Outdated
@@ -468,8 +468,11 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir | |||
|
|||
filename := []rune(f.Name()) | |||
if runeSliceWidth(filename) > maxFilenameWidth { | |||
filename = runeSliceWidthRange(filename, 0, maxFilenameWidth-1) | |||
var truncatePos int = (maxFilenameWidth - 1) * gOpts.fnametruncpct / 100 |
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.
Well, I want truncatePos to be an integer instead of a float. I vaguely remember that this "expclicit cast" (or what ever this is called in go) was necessary. But now I can't reproduce this anymore and it seems to make no difference. So I have changed it to the short declaration.
misc.go
Outdated
@@ -53,6 +53,19 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune { | |||
return rs[b:] | |||
} | |||
|
|||
// Returns the last runes of `rs` that take up at most `max_width` space. | |||
func runeSliceWidthLastRange(rs []rune, max_width int) []rune { |
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.
Sure, test cases are now added.
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.
You've got plenty of reviewers already, so I only glanced at this and had a couple of minor comments. Thanks for the feature, I'll try it out and will likely enjoy it!
doc.go
Outdated
|
||
- `set truncatepct 0` -> "~ng-filename-truncated" | ||
|
||
The default of 100 means no change in behaviour to before the introduction of |
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.
There's no need to say how this compares to previous versions in the documentation. You can put it into the PR/commit description if you haven't 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.
Right. I've removed the sentence and put it in the commit message (of the next additional commit).
eval.go
Outdated
return | ||
} | ||
if n < 0 || n > 100 { | ||
app.ui.echoerr("truncatepct: must be between 0 and 100 (both inclusive)") |
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.
Very minor: It would be nice to include ", got %d" at the end of this error message to print the value of n
(and also change it to echoerrf
).
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.
Right, thanks. Changed.
misc_test.go
Outdated
{[]rune{'世', '界', '世', '界'}, 8, []rune{'世', '界', '世', '界'}}, | ||
{[]rune{'世', '界', '世', '界'}, 9, []rune{'世', '界', '世', '界'}}, | ||
{[]rune{'世', '界', '世', '界'}, 10, []rune{'世', '界', '世', '界'}}, | ||
{[]rune{'世', '界', '世', '界'}, 11, []rune{'世', '界', '世', '界'}}, |
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.
Very minor: This slice only has a width of 8, perhaps checking for a value of 11 is not necessary, and you can probably get rid of the test case for 10 as well.
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.
Yes, you are right the test cases with 10 and 11 are redundant (at least for the current implementation). This is just some going a little bit further, just in case.
Anyway, I've removed them.
misc.go
Outdated
lastWidth := 0 | ||
for i := len(rs) - 1; i >= 0; i-- { | ||
w := runewidth.RuneWidth(rs[i]) | ||
if lastWidth+w > max_width { |
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 should be camelcase too, apologies I missed it last time.
if lastWidth+w > max_width { | |
if lastWidth+w > maxWidth { |
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 missed that one too. Now it's also changed.
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.
Thanks.
I've pushed some additional to address the new comments.
doc.go
Outdated
|
||
- `set truncatepct 0` -> "~ng-filename-truncated" | ||
|
||
The default of 100 means no change in behaviour to before the introduction of |
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.
Right. I've removed the sentence and put it in the commit message (of the next additional commit).
eval.go
Outdated
return | ||
} | ||
if n < 0 || n > 100 { | ||
app.ui.echoerr("truncatepct: must be between 0 and 100 (both inclusive)") |
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.
Right, thanks. Changed.
misc.go
Outdated
lastWidth := 0 | ||
for i := len(rs) - 1; i >= 0; i-- { | ||
w := runewidth.RuneWidth(rs[i]) | ||
if lastWidth+w > max_width { |
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 missed that one too. Now it's also changed.
misc_test.go
Outdated
{[]rune{'世', '界', '世', '界'}, 8, []rune{'世', '界', '世', '界'}}, | ||
{[]rune{'世', '界', '世', '界'}, 9, []rune{'世', '界', '世', '界'}}, | ||
{[]rune{'世', '界', '世', '界'}, 10, []rune{'世', '界', '世', '界'}}, | ||
{[]rune{'世', '界', '世', '界'}, 11, []rune{'世', '界', '世', '界'}}, |
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.
Yes, you are right the test cases with 10 and 11 are redundant (at least for the current implementation). This is just some going a little bit further, just in case.
Anyway, I've removed them.
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 have a couple more minor comments, apologies for missing them earlier. But I think this should be the last of them, and if you fix these then I would be willing to approve your PR.
ui.go
Outdated
@@ -468,8 +468,11 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir | |||
|
|||
filename := []rune(f.Name()) | |||
if runeSliceWidth(filename) > maxFilenameWidth { | |||
filename = runeSliceWidthRange(filename, 0, maxFilenameWidth-1) | |||
truncatePos := (maxFilenameWidth - 1) * gOpts.truncatepct / 100 | |||
var lastPart []rune = runeSliceWidthLastRange(filename, maxFilenameWidth-truncatePos-1) |
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.
runeSliceWidthLastRange
already returns []rune
so you should just be able to write this:
var lastPart []rune = runeSliceWidthLastRange(filename, maxFilenameWidth-truncatePos-1) | |
lastPart := runeSliceWidthLastRange(filename, maxFilenameWidth-truncatePos-1) |
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.
Ok, yes it works, thanks.
Changed accordingly.
ui.go
Outdated
filename = append(filename, []rune(gOpts.truncatechar)...) | ||
filename = append(filename, []rune(lastPart)...) |
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.
Similarly lastPart
is already a []rune
so you don't need a cast here:
filename = append(filename, []rune(lastPart)...) | |
filename = append(filename, lastPart...) |
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.
Works too, thanks.
Also changed.
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.
Thanks for the hints. I've pushed another commit to address them.
ui.go
Outdated
@@ -468,8 +468,11 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir | |||
|
|||
filename := []rune(f.Name()) | |||
if runeSliceWidth(filename) > maxFilenameWidth { | |||
filename = runeSliceWidthRange(filename, 0, maxFilenameWidth-1) | |||
truncatePos := (maxFilenameWidth - 1) * gOpts.truncatepct / 100 | |||
var lastPart []rune = runeSliceWidthLastRange(filename, maxFilenameWidth-truncatePos-1) |
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.
Ok, yes it works, thanks.
Changed accordingly.
ui.go
Outdated
filename = append(filename, []rune(gOpts.truncatechar)...) | ||
filename = append(filename, []rune(lastPart)...) |
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.
Works too, thanks.
Also changed.
doc.go
Outdated
|
||
- `set truncatepct 0` -> "~ng-filename-truncated" | ||
|
||
. |
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.
Thanks for the fix! One last nit: could you remove this period and make sure there's one blank line before the next item?
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.
Ok, well.
After removing the "100 does not change the behaviour" line after the examples list, I saw that go fmt
would re-format the next line with waitmsg
(which I don't want to touch in this change) unnecessarily (i.e. convert the leading tab to space). That's why I introduced the line with the single dot, to work around this unexpected formatting behaviour.
However, I tried other things and found that removing the spaces before the list items works too (i.e. go fmt
doesn't touch the next item with waitmsg
)
See the additional commit.
This was there to avoid that `go fmt` touches the next item with `waitmsg`. However, removing the spaces before the example list items has the same effect.
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've added another commit to address the "line with single dot" in doc.go
issue.
doc.go
Outdated
|
||
- `set truncatepct 0` -> "~ng-filename-truncated" | ||
|
||
. |
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.
Ok, well.
After removing the "100 does not change the behaviour" line after the examples list, I saw that go fmt
would re-format the next line with waitmsg
(which I don't want to touch in this change) unnecessarily (i.e. convert the leading tab to space). That's why I introduced the line with the single dot, to work around this unexpected formatting behaviour.
However, I tried other things and found that removing the spaces before the list items works too (i.e. go fmt
doesn't touch the next item with waitmsg
)
See the additional commit.
Thanks @raslop for the patch and @joelim-work and @ilyagr for the reviews. |
Thanks, all. |
Inspired how MidnightCommander display too long filenames, I would like to propose a new config option fnametruncpct to allow flexible truncation of too long filenames, i.e. not only at the end like before, but also in the middle of a loo long filename, e.g.:
set fnametruncpct 100
-> "very-long-filename-tr~"set fnametruncpct 50
-> "very-long-f~-truncated"set fnametruncpct 0
-> "~ng-filename-truncated"The additional fix commit was necessary to fix a presumed bug in
runeSliceWidthRange()
discovered during developing this change.Let me know what you think.
[I'm especially not sure if the option name fnametruncpct fits well (I would have made it more verbose, i.e. longer, like filename_truncate_percent, all others are much shorter and without underlines).]