diff --git a/complete.go b/complete.go index cae50828..3d911861 100644 --- a/complete.go +++ b/complete.go @@ -218,6 +218,7 @@ var ( "infotimefmtnew", "infotimefmtold", "truncatechar", + "truncatepct", } ) diff --git a/doc.go b/doc.go index 806b6aac..782f167f 100644 --- a/doc.go +++ b/doc.go @@ -172,6 +172,7 @@ The following options can be used to customize the behavior of lf: tempmarks string (default '') timefmt string (default 'Mon Jan _2 15:04:05 2006') truncatechar string (default '~') + truncatepct int (default 100) waitmsg string (default 'Press any key to continue') wrapscan bool (default true) wrapscroll bool (default false) @@ -941,6 +942,23 @@ Format string of the file modification time shown in the bottom line. Truncate character shown at the end when the file name does not fit to the pane. + truncatepct int (default 100) + +When a filename is too long to be shown completely, the available space is +partitioned in two pieces. truncatepct defines a fraction (in percent +between 0 and 100) for the size of the first piece, which will show the +beginning of the filename. The second piece will show the end of the filename +and will use the rest of the available space. Both pieces are separated by the +truncation character (truncatechar). +A value of 100 will only show the beginning of the filename, +while a value of 0 will only show the end of the filename, e.g.: + +- `set truncatepct 100` -> "very-long-filename-tr~" (default) + +- `set truncatepct 50` -> "very-long-f~-truncated" + +- `set truncatepct 0` -> "~ng-filename-truncated" + waitmsg string (default 'Press any key to continue') String shown after commands of shell-wait type. diff --git a/docstring.go b/docstring.go index 61692de5..24f6275d 100644 --- a/docstring.go +++ b/docstring.go @@ -175,6 +175,7 @@ The following options can be used to customize the behavior of lf: tempmarks string (default '') timefmt string (default 'Mon Jan _2 15:04:05 2006') truncatechar string (default '~') + truncatepct int (default 100) waitmsg string (default 'Press any key to continue') wrapscan bool (default true) wrapscroll bool (default false) @@ -1013,6 +1014,22 @@ Format string of the file modification time shown in the bottom line. Truncate character shown at the end when the file name does not fit to the pane. + truncatepct int (default 100) + +When a filename is too long to be shown completely, the available space is +partitioned in two pieces. truncatepct defines a fraction (in percent between +0 and 100) for the size of the first piece, which will show the beginning of +the filename. The second piece will show the end of the filename and will use +the rest of the available space. Both pieces are separated by the truncation +character (truncatechar). A value of 100 will only show the beginning of the +filename, while a value of 0 will only show the end of the filename, e.g.: + +- 'set truncatepct 100' -> "very-long-filename-tr~" (default) + +- 'set truncatepct 50' -> "very-long-f~-truncated" + +- 'set truncatepct 0' -> "~ng-filename-truncated" + waitmsg string (default 'Press any key to continue') String shown after commands of shell-wait type. diff --git a/eval.go b/eval.go index 24975c3c..2eea96dc 100644 --- a/eval.go +++ b/eval.go @@ -831,6 +831,17 @@ func (e *setExpr) eval(app *app, args []string) { } gOpts.truncatechar = e.val + case "truncatepct": + n, err := strconv.Atoi(e.val) + if err != nil { + app.ui.echoerrf("truncatepct: %s", err) + return + } + if n < 0 || n > 100 { + app.ui.echoerrf("truncatepct: must be between 0 and 100 (both inclusive), got %d", n) + return + } + gOpts.truncatepct = n case "waitmsg": gOpts.waitmsg = e.val case "wrapscan": diff --git a/lf.1 b/lf.1 index 561edc59..2804db4b 100644 --- a/lf.1 +++ b/lf.1 @@ -191,6 +191,7 @@ The following options can be used to customize the behavior of lf: tempmarks string (default '') timefmt string (default 'Mon Jan _2 15:04:05 2006') truncatechar string (default '~') + truncatepct int (default 100) waitmsg string (default 'Press any key to continue') wrapscan bool (default true) wrapscroll bool (default false) @@ -1119,6 +1120,18 @@ Format string of the file modification time shown in the bottom line. .PP Truncate character shown at the end when the file name does not fit to the pane. .PP +.EX + truncatepct int (default 100) +.EE +.PP +When a filename is too long to be shown completely, the available space is partitioned in two pieces. truncatepct defines a fraction (in percent between 0 and 100) for the size of the first piece, which will show the beginning of the filename. The second piece will show the end of the filename and will use the rest of the available space. Both pieces are separated by the truncation character (truncatechar). A value of 100 will only show the beginning of the filename, while a value of 0 will only show the end of the filename, e.g.: +.PP +- `set truncatepct 100` -> "very-long-filename-tr~" (default) +.PP +- `set truncatepct 50` -> "very-long-f~-truncated" +.PP +- `set truncatepct 0` -> "~ng-filename-truncated" +.PP .EX waitmsg string (default 'Press any key to continue') .EE diff --git a/misc.go b/misc.go index b0303eaf..8199697f 100644 --- a/misc.go +++ b/misc.go @@ -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 `maxWidth` space. +func runeSliceWidthLastRange(rs []rune, maxWidth int) []rune { + lastWidth := 0 + for i := len(rs) - 1; i >= 0; i-- { + w := runewidth.RuneWidth(rs[i]) + if lastWidth+w > maxWidth { + return rs[i+1:] + } + lastWidth += w + } + return rs +} + // This function is used to escape whitespaces and special characters with // backlashes in a given string. func escape(s string) string { diff --git a/misc_test.go b/misc_test.go index aa6f6df5..758e3429 100644 --- a/misc_test.go +++ b/misc_test.go @@ -83,6 +83,47 @@ func TestRuneSliceWidthRange(t *testing.T) { } } +func TestRuneSliceWidthLastRange(t *testing.T) { + tests := []struct { + rs []rune + maxWidth int + exp []rune + }{ + {[]rune{}, 0, []rune{}}, + {[]rune{}, 1, []rune{}}, + {[]rune{'a', 'ı', 'b', ' '}, 0, []rune{}}, + {[]rune{'a', 'ı', 'b', ' '}, 1, []rune{' '}}, + {[]rune{'a', 'ı', 'b', ' '}, 2, []rune{'b', ' '}}, + {[]rune{'a', 'ı', 'b', ' '}, 3, []rune{'ı', 'b', ' '}}, + {[]rune{'a', 'ı', 'b', ' '}, 4, []rune{'a', 'ı', 'b', ' '}}, + {[]rune{'a', 'ı', 'b', ' '}, 5, []rune{'a', 'ı', 'b', ' '}}, + {[]rune{'世', '界', '世', '界'}, 0, []rune{}}, + {[]rune{'世', '界', '世', '界'}, 1, []rune{}}, + {[]rune{'世', '界', '世', '界'}, 2, []rune{'界'}}, + {[]rune{'世', '界', '世', '界'}, 3, []rune{'界'}}, + {[]rune{'世', '界', '世', '界'}, 4, []rune{'世', '界'}}, + {[]rune{'世', '界', '世', '界'}, 5, []rune{'世', '界'}}, + {[]rune{'世', '界', '世', '界'}, 6, []rune{'界', '世', '界'}}, + {[]rune{'世', '界', '世', '界'}, 7, []rune{'界', '世', '界'}}, + {[]rune{'世', '界', '世', '界'}, 8, []rune{'世', '界', '世', '界'}}, + {[]rune{'世', '界', '世', '界'}, 9, []rune{'世', '界', '世', '界'}}, + {[]rune{'世', 'a', '界', 'ı'}, 0, []rune{}}, + {[]rune{'世', 'a', '界', 'ı'}, 1, []rune{'ı'}}, + {[]rune{'世', 'a', '界', 'ı'}, 2, []rune{'ı'}}, + {[]rune{'世', 'a', '界', 'ı'}, 3, []rune{'界', 'ı'}}, + {[]rune{'世', 'a', '界', 'ı'}, 4, []rune{'a', '界', 'ı'}}, + {[]rune{'世', 'a', '界', 'ı'}, 5, []rune{'a', '界', 'ı'}}, + {[]rune{'世', 'a', '界', 'ı'}, 6, []rune{'世', 'a', '界', 'ı'}}, + {[]rune{'世', 'a', '界', 'ı'}, 7, []rune{'世', 'a', '界', 'ı'}}, + } + + for _, test := range tests { + if got := runeSliceWidthLastRange(test.rs, test.maxWidth); !reflect.DeepEqual(got, test.exp) { + t.Errorf("at input '%v' expected '%v' but got '%v'", test.rs, test.exp, got) + } + } +} + func TestEscape(t *testing.T) { tests := []struct { s string diff --git a/opts.go b/opts.go index 21d624c4..5b8feaaf 100644 --- a/opts.go +++ b/opts.go @@ -72,6 +72,7 @@ var gOpts struct { infotimefmtnew string infotimefmtold string truncatechar string + truncatepct int ratios []int hiddenfiles []string history bool @@ -133,6 +134,7 @@ func init() { gOpts.infotimefmtnew = "Jan _2 15:04" gOpts.infotimefmtold = "Jan _2 2006" gOpts.truncatechar = "~" + gOpts.truncatepct = 100 gOpts.ratios = []int{1, 2, 3} gOpts.hiddenfiles = []string{".*"} gOpts.history = true diff --git a/ui.go b/ui.go index 635a4281..4b8cb550 100644 --- a/ui.go +++ b/ui.go @@ -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 + lastPart := runeSliceWidthLastRange(filename, maxFilenameWidth-truncatePos-1) + filename = runeSliceWidthRange(filename, 0, truncatePos) filename = append(filename, []rune(gOpts.truncatechar)...) + filename = append(filename, lastPart...) } for i := runeSliceWidth(filename); i < maxFilenameWidth; i++ { filename = append(filename, ' ')