sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit f6d09bbe5237bfe2cf238f41693c2107118df581
parent b07f8a2374b5bb1a2570760ec1f243376c710fb0
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun, 28 Jun 2020 14:49:15 +1000

use Unicode::DisplayWidth to handle basic emoji

This fixes display glitches which can occur when the sender name or
subject line contain emoji.

We intentionally avoid using the unicode-display_width gem's optional
emoji support, because that turns out to be prohibitively slow for these
methods which are in the hot path of rendering the message list. With
this patch we will still count basic emoji characters themselves as
double-width. As far as I can tell, the only consequence of avoiding the
extra emoji support is that we will *overestimate* the length of emoji
ZWJ sequences. That's not the end of the world, since we are just
truncating strings here. And many terminal emulators still can't render
emoji ZWJ sequences correctly anyway.

The Unicode::DisplayWidth.of method is careful to always return
a value greater than zero so we can simplify the code on our side
a bit too.

Diffstat:
M lib/sup/util.rb | 15 +++------------
M sup.gemspec | 1 +
M test/unit/util/test_string.rb | 6 ++++++
3 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/lib/sup/util.rb b/lib/sup/util.rb
@@ -8,6 +8,7 @@ require 'set'
 require 'enumerator'
 require 'benchmark'
 require 'unicode'
+require 'unicode/display_width'
 require 'fileutils'
 
 class Lockfile
@@ -239,22 +240,12 @@ end
 
 class String
   def display_length
-    @display_length ||= Unicode.width(self.fix_encoding!, false)
-
-    # if Unicode.width fails and returns -1, fall back to
-    # regular String#length, see pull-request: #256.
-    if @display_length < 0
-      @display_length = self.length
-    end
-
-    @display_length
+    @display_length ||= Unicode::DisplayWidth.of(self)
   end
 
   def slice_by_display_length len
     each_char.each_with_object "" do |c, buffer|
-      width = Unicode.width(c, false)
-      width = 1 if width < 0
-      len -= width
+      len -= Unicode::DisplayWidth.of(c)
       return buffer if len < 0
       buffer << c
     end
diff --git a/sup.gemspec b/sup.gemspec
@@ -59,6 +59,7 @@ SUP: please note that our old mailing lists have been shut down,
   s.add_runtime_dependency "locale", "~> 2.0"
   s.add_runtime_dependency "chronic"
   s.add_runtime_dependency "unicode", "~> 0.4.4"
+  s.add_runtime_dependency "unicode-display_width"
 
   s.add_development_dependency "bundler", ">= 1.3", "< 3"
   s.add_development_dependency "rake"
diff --git a/test/unit/util/test_string.rb b/test/unit/util/test_string.rb
@@ -11,6 +11,8 @@ describe "Sup's String extension" do
         ['some words', 10,],
         ['δΈ­ζ–‡', 4,],
         ['Γ€', 1,],
+        ['😱', 2],
+        #['πŸ³οΈβ€πŸŒˆ', 2],  # Emoji ZWJ sequence not yet supported (see PR #563)
       ]
     end
 
@@ -27,6 +29,8 @@ describe "Sup's String extension" do
         ['some words', 6, 'some w'],
         ['δΈ­ζ–‡', 2, 'δΈ­'],
         ['Γ€lpha', 3, 'Γ€lp'],
+        ['😱😱', 2, '😱'],
+        #['πŸ³οΈβ€πŸŒˆ', 2, 'πŸ³οΈβ€πŸŒˆ'],  # Emoji ZWJ sequence not yet supported (see PR #563)
       ]
     end
 
@@ -45,6 +49,8 @@ describe "Sup's String extension" do
         ['δΈ­ζ–‡', 2, ['δΈ­', 'ζ–‡']],
         ['δΈ­ζ–‡', 5, ['δΈ­ζ–‡']],
         ['Γ€lpha', 3, ['Γ€lp', 'ha']],
+        ['😱😱', 2, ['😱', '😱']],
+        #['πŸ³οΈβ€πŸŒˆπŸ³οΈβ€πŸŒˆ', 2, ['πŸ³οΈβ€πŸŒˆ', 'πŸ³οΈβ€πŸŒˆ']],  # Emoji ZWJ sequence not yet supported (see PR #563)
       ]
     end