From 389d89a9362e2e782f17074331bf85bb579d7466 Mon Sep 17 00:00:00 2001 From: Vitaly Ovchinnikov Date: Mon, 4 Sep 2023 18:39:41 +0300 Subject: msgviewer: show attachment file names first if possible Change the parts switcher so it displays the file names first (if any) and then the mime types. The mime types are aligned to the right to help the file names to be more visible. If there is no file name - the mime type is displayed on the left, as usual. The idea is that the file name (if present) is more important than the mime type. Especially when both file names and mime types are long - this quickly becomes a mess. If there is no space for both file name and mime, the mime type is truncated with ellipsis. If there is no space for mime at all - it is dropped completely. If then there is no space for the file name, it is also truncated with ellipsis. Signed-off-by: Vitaly Ovchinnikov Signed-off-by: Robin Jarry Tested-by: Inwit --- widgets/msgviewer.go | 37 +++++++++++++++++++++----- widgets/msgviewer_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 widgets/msgviewer_test.go diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go index 0908417d..c94d57a3 100644 --- a/widgets/msgviewer.go +++ b/widgets/msgviewer.go @@ -431,12 +431,9 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) { style = ps.mv.uiConfig.GetStyleSelected(config.STYLE_DEFAULT) } ctx.Fill(0, y+i, ctx.Width(), 1, ' ', style) - name := part.part.FullMIMEType() - filename := part.part.FileName() - if filename != "" { - name += fmt.Sprintf(" (%s)", filename) - } - ctx.Printf(len(part.index)*2, y+i, style, "%s", name) + left := len(part.index) * 2 + name := formatMessagePart(part.part.FullMIMEType(), part.part.FileName(), ctx.Width()-left) + ctx.Printf(left, y+i, style, "%s", name) } ps.parts[ps.selected].Draw(ctx.Subcontext( 0, 0, ctx.Width(), ctx.Height()-height)) @@ -503,6 +500,34 @@ func (ps *PartSwitcher) Cleanup() { } } +func formatMessagePart(mime, filename string, width int) string { + lname := runewidth.StringWidth(filename) + lmime := runewidth.StringWidth(mime) + + switch { + case width <= 0: + return "" + + case filename == "": + return runewidth.Truncate(mime, width, "…") + + case lname+lmime+3 <= width: + // simple scenario - everything fits + return fmt.Sprintf("%s (%s)", + runewidth.FillRight(filename, width-lmime-3), mime) + + case lname+3 < width: + // file name fits + we have space for parentheses and at least + // one symbol of mime + return fmt.Sprintf("%s (%s)", filename, + runewidth.Truncate(mime, width-lname-3, "…")) + + default: + // ok, we don't have space even for the file name + return runewidth.Truncate(filename, width, "…") + } +} + func (mv *MessageViewer) Event(event tcell.Event) bool { return mv.switcher.Event(event) } diff --git a/widgets/msgviewer_test.go b/widgets/msgviewer_test.go new file mode 100644 index 00000000..f6d01edb --- /dev/null +++ b/widgets/msgviewer_test.go @@ -0,0 +1,68 @@ +package widgets + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormatMessageNoFilename(t *testing.T) { + assert.Equal(t, "mime/type", formatMessagePart("mime/type", "", 24)) + assert.Equal(t, "m/type", formatMessagePart("m/type", "", 24)) + assert.Equal(t, "2", formatMessagePart("2", "", 24)) + assert.Equal(t, "2", formatMessagePart("2", "", 20)) +} + +func TestFormatMessageNoFilenameNotEnoguhSpace(t *testing.T) { + assert.Equal(t, "mime/type", formatMessagePart("mime/type", "", 24)) + assert.Equal(t, "mime/type", formatMessagePart("mime/type", "", 9)) + assert.Equal(t, "mime/ty…", formatMessagePart("mime/type", "", 8)) + assert.Equal(t, "mime/…", formatMessagePart("mime/type", "", 6)) + assert.Equal(t, "m…", formatMessagePart("mime/type", "", 2)) + assert.Equal(t, "…", formatMessagePart("mime/type", "", 1)) + + assert.Equal(t, "", formatMessagePart("mime/type", "", 0)) + assert.Equal(t, "", formatMessagePart("mime/type", "", -1)) + assert.Equal(t, "", formatMessagePart("mime/type", "", -10)) +} + +func TestFormatMessagePartSimpleCases(t *testing.T) { + assert.Equal(t, "filename.doc (mime/type)", formatMessagePart("mime/type", "filename.doc", 24)) + assert.Equal(t, "имяфайла.док (mime/type)", formatMessagePart("mime/type", "имяфайла.док", 24)) + assert.Equal(t, "file.doc (m/type)", formatMessagePart("m/type", "file.doc", 24)) + assert.Equal(t, "1 (2)", formatMessagePart("2", "1", 24)) + assert.Equal(t, "1 (2)", formatMessagePart("2", "1", 20)) + assert.Equal(t, "1 (2)", formatMessagePart("2", "1", 5)) +} + +func TestFormatMessagePartNotEnoughSpaceForMime(t *testing.T) { + assert.Equal(t, "filename.doc (mime/type)", formatMessagePart("mime/type", "filename.doc", 30)) + assert.Equal(t, "filename.doc (mime/type)", formatMessagePart("mime/type", "filename.doc", 25)) + assert.Equal(t, "filename.doc (mime/type)", formatMessagePart("mime/type", "filename.doc", 24)) + assert.Equal(t, "filename.doc (mime/ty…)", formatMessagePart("mime/type", "filename.doc", 23)) + assert.Equal(t, "имяфайла.док (mime/ty…)", formatMessagePart("mime/type", "имяфайла.док", 23)) + assert.Equal(t, "filename.doc (m…)", formatMessagePart("mime/type", "filename.doc", 17)) + assert.Equal(t, "filename.doc (…)", formatMessagePart("mime/type", "filename.doc", 16)) + assert.Equal(t, "имяфайла.док (…)", formatMessagePart("mime/type", "имяфайла.док", 16)) + assert.Equal(t, "filename.doc", formatMessagePart("mime/type", "filename.doc", 15)) + assert.Equal(t, "filename.doc", formatMessagePart("mime/type", "filename.doc", 14)) + assert.Equal(t, "filename.doc", formatMessagePart("mime/type", "filename.doc", 13)) + assert.Equal(t, "filename.doc", formatMessagePart("mime/type", "filename.doc", 12)) + assert.Equal(t, "имяфайла.док", formatMessagePart("mime/type", "имяфайла.док", 12)) +} + +func TestFormatMessagePartNotEnoughSpaceForFilename(t *testing.T) { + assert.Equal(t, "filename.d…", formatMessagePart("mime/type", "filename.doc", 11)) + assert.Equal(t, "filename…", formatMessagePart("mime/type", "filename.doc", 9)) + assert.Equal(t, "f…", formatMessagePart("mime/type", "filename.doc", 2)) + assert.Equal(t, "…", formatMessagePart("mime/type", "filename.doc", 1)) + + assert.Equal(t, "", formatMessagePart("mime/type", "filename.doc", 0)) + assert.Equal(t, "", formatMessagePart("mime/type", "filename.doc", -1)) + assert.Equal(t, "", formatMessagePart("mime/type", "filename.doc", -10)) + + assert.Equal(t, "имяфайла.д…", formatMessagePart("mime/type", "имяфайла.док", 11)) + assert.Equal(t, "имяфайла…", formatMessagePart("mime/type", "имяфайла.док", 9)) + assert.Equal(t, "и…", formatMessagePart("mime/type", "имяфайла.док", 2)) + assert.Equal(t, "…", formatMessagePart("mime/type", "имяфайла.док", 1)) +} -- cgit