mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-07 21:15:19 +03:00
dev: Use realLength from doclayout instead of strWidth and textWidth. (#895)
This gives us more accurate string length calculations. In particular, it handles emoji and other scripts properly.
This commit is contained in:
parent
d1ae0c10d6
commit
ff0132df28
@ -46,6 +46,7 @@ import qualified Data.Set as S
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Tree (Tree(..))
|
||||
import Text.DocLayout (realLength)
|
||||
|
||||
import Hledger.Data.Types
|
||||
import Hledger.Utils
|
||||
@ -186,7 +187,7 @@ elideAccountName width s
|
||||
where
|
||||
elideparts :: Int -> [Text] -> [Text] -> [Text]
|
||||
elideparts width done ss
|
||||
| textWidth (accountNameFromComponents $ done++ss) <= width = done++ss
|
||||
| realLength (accountNameFromComponents $ done++ss) <= width = done++ss
|
||||
| length ss > 1 = elideparts width (done++[textTakeWidth 2 $ head ss]) (tail ss)
|
||||
| otherwise = done++ss
|
||||
|
||||
|
@ -171,7 +171,7 @@ import Test.Tasty.HUnit ((@?=), assertBool, testCase)
|
||||
import Hledger.Data.Types
|
||||
import Hledger.Utils (colorB)
|
||||
import Hledger.Utils.Text (textQuoteIfNeeded)
|
||||
import Text.WideString (WideBuilder(..), textWidth, wbToText, wbUnpack)
|
||||
import Text.WideString (WideBuilder(..), wbFromText, wbToText, wbUnpack)
|
||||
|
||||
|
||||
-- A 'Commodity' is a symbol representing a currency or some other kind of
|
||||
@ -469,14 +469,13 @@ showAmountB :: AmountDisplayOpts -> Amount -> WideBuilder
|
||||
showAmountB _ Amount{acommodity="AUTO"} = mempty
|
||||
showAmountB opts a@Amount{astyle=style} =
|
||||
color $ case ascommodityside style of
|
||||
L -> showC c' space <> quantity' <> price
|
||||
R -> quantity' <> showC space c' <> price
|
||||
L -> showC (wbFromText c) space <> quantity' <> price
|
||||
R -> quantity' <> showC space (wbFromText c) <> price
|
||||
where
|
||||
quantity = showamountquantity a
|
||||
(quantity',c) | amountLooksZero a && not (displayZeroCommodity opts) = (WideBuilder (TB.singleton '0') 1,"")
|
||||
| otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a)
|
||||
space = if not (T.null c) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty
|
||||
c' = WideBuilder (TB.fromText c) (textWidth c)
|
||||
showC l r = if isJust (displayOrder opts) then mempty else l <> r
|
||||
price = if displayPrice opts then showAmountPrice a else mempty
|
||||
color = if displayColour opts && isNegativeAmount a then colorB Dull Red else id
|
||||
|
@ -89,6 +89,7 @@ import qualified Data.Text.Lazy as TL
|
||||
import qualified Data.Text.Lazy.Builder as TB
|
||||
import Data.Time.Calendar (Day)
|
||||
import Safe (headDef, maximumDef)
|
||||
import Text.DocLayout (realLength)
|
||||
|
||||
import Text.Tabular.AsciiWide
|
||||
|
||||
@ -255,7 +256,7 @@ postingAsLines elideamount onelineamounts acctwidth amtwidth p =
|
||||
assertion = maybe mempty ((WideBuilder (TB.singleton ' ') 1 <>).showBalanceAssertion) $ pbalanceassertion p
|
||||
-- pad to the maximum account name width, plus 2 to leave room for status flags, to keep amounts aligned
|
||||
statusandaccount = lineIndent . fitText (Just $ 2 + acctwidth) Nothing False True $ pstatusandacct p
|
||||
thisacctwidth = textWidth $ pacctstr p
|
||||
thisacctwidth = realLength $ pacctstr p
|
||||
|
||||
pacctstr p' = showAccountName Nothing (ptype p') (paccount p')
|
||||
pstatusandacct p' = pstatusprefix p' <> pacctstr p'
|
||||
|
@ -42,7 +42,7 @@ import Text.Printf (printf)
|
||||
|
||||
import Hledger.Utils.Parse
|
||||
import Hledger.Utils.Regex (toRegex', regexReplace)
|
||||
import Text.WideString (charWidth, strWidth)
|
||||
import Text.DocLayout (charWidth, realLength)
|
||||
|
||||
|
||||
-- | Take elements from the end of a list.
|
||||
@ -174,6 +174,10 @@ takeWidth w (c:cs) | cw <= w = c:takeWidth (w-cw) cs
|
||||
strWidthAnsi :: String -> Int
|
||||
strWidthAnsi = strWidth . stripAnsi
|
||||
|
||||
-- | Alias for 'realLength'.
|
||||
strWidth :: String -> Int
|
||||
strWidth = realLength
|
||||
|
||||
-- | Strip ANSI escape sequences from a string.
|
||||
--
|
||||
-- >>> stripAnsi "\ESC[31m-1\ESC[m"
|
||||
|
@ -43,7 +43,6 @@ module Hledger.Utils.Text
|
||||
wbToText,
|
||||
wbFromText,
|
||||
wbUnpack,
|
||||
textWidth,
|
||||
textTakeWidth,
|
||||
-- * Reading
|
||||
readDecimal,
|
||||
@ -58,12 +57,13 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import qualified Data.Text.Lazy.Builder as TB
|
||||
import Text.DocLayout (charWidth, realLength)
|
||||
|
||||
import Test.Tasty (testGroup)
|
||||
import Test.Tasty.HUnit ((@?=), testCase)
|
||||
import Text.Tabular.AsciiWide
|
||||
(Align(..), Header(..), Properties(..), TableOpts(..), renderRow, textCell)
|
||||
import Text.WideString (WideBuilder(..), wbToText, wbFromText, wbUnpack, charWidth, textWidth)
|
||||
import Text.WideString (WideBuilder(..), wbToText, wbFromText, wbUnpack)
|
||||
|
||||
|
||||
-- lowercase, uppercase :: String -> String
|
||||
@ -206,7 +206,7 @@ fitText mminwidth mmaxwidth ellipsify rightside = clip . pad
|
||||
clip s =
|
||||
case mmaxwidth of
|
||||
Just w
|
||||
| textWidth s > w ->
|
||||
| realLength s > w ->
|
||||
if rightside
|
||||
then textTakeWidth (w - T.length ellipsis) s <> ellipsis
|
||||
else ellipsis <> T.reverse (textTakeWidth (w - T.length ellipsis) $ T.reverse s)
|
||||
@ -224,7 +224,7 @@ fitText mminwidth mmaxwidth ellipsify rightside = clip . pad
|
||||
else T.replicate (w - sw) " " <> s
|
||||
| otherwise -> s
|
||||
Nothing -> s
|
||||
where sw = textWidth s
|
||||
where sw = realLength s
|
||||
|
||||
-- | Double-width-character-aware string truncation. Take as many
|
||||
-- characters as possible from a string without exceeding the
|
||||
|
@ -35,7 +35,7 @@ import qualified Data.Text.Lazy as TL
|
||||
import Data.Text.Lazy.Builder (Builder, fromString, fromText, singleton, toLazyText)
|
||||
import Safe (maximumMay)
|
||||
import Text.Tabular
|
||||
import Text.WideString (WideBuilder(..), wbFromText, textWidth)
|
||||
import Text.WideString (WideBuilder(..), wbFromText)
|
||||
|
||||
|
||||
-- | The options to use for rendering a table.
|
||||
@ -63,7 +63,7 @@ emptyCell = Cell TopRight []
|
||||
|
||||
-- | Create a single-line cell from the given contents with its natural width.
|
||||
textCell :: Align -> Text -> Cell
|
||||
textCell a x = Cell a . map (\x -> WideBuilder (fromText x) (textWidth x)) $ if T.null x then [""] else T.lines x
|
||||
textCell a x = Cell a . map wbFromText $ if T.null x then [""] else T.lines x
|
||||
|
||||
-- | Create a multi-line cell from the given contents with its natural width.
|
||||
textsCell :: Align -> [Text] -> Cell
|
||||
|
@ -1,10 +1,6 @@
|
||||
-- | Calculate the width of String and Text, being aware of wide characters.
|
||||
|
||||
module Text.WideString (
|
||||
-- * wide-character-aware layout
|
||||
strWidth,
|
||||
textWidth,
|
||||
charWidth,
|
||||
-- * Text Builders which keep track of length
|
||||
WideBuilder(..),
|
||||
wbUnpack,
|
||||
@ -16,6 +12,7 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import qualified Data.Text.Lazy.Builder as TB
|
||||
import Text.DocLayout (realLength)
|
||||
|
||||
|
||||
-- | Helper for constructing Builders while keeping track of text width.
|
||||
@ -36,68 +33,8 @@ wbToText = TL.toStrict . TB.toLazyText . wbBuilder
|
||||
|
||||
-- | Convert a strict Text to a WideBuilder.
|
||||
wbFromText :: Text -> WideBuilder
|
||||
wbFromText t = WideBuilder (TB.fromText t) (textWidth t)
|
||||
wbFromText t = WideBuilder (TB.fromText t) (realLength t)
|
||||
|
||||
-- | Convert a WideBuilder to a String.
|
||||
wbUnpack :: WideBuilder -> String
|
||||
wbUnpack = TL.unpack . TB.toLazyText . wbBuilder
|
||||
|
||||
|
||||
-- | Calculate the render width of a string, considering
|
||||
-- wide characters (counted as double width)
|
||||
strWidth :: String -> Int
|
||||
strWidth = foldr (\a b -> charWidth a + b) 0
|
||||
|
||||
-- | Calculate the render width of a string, considering
|
||||
-- wide characters (counted as double width)
|
||||
textWidth :: Text -> Int
|
||||
textWidth = T.foldr (\a b -> charWidth a + b) 0
|
||||
|
||||
-- from Pandoc (copyright John MacFarlane, GPL)
|
||||
-- see also http://unicode.org/reports/tr11/#Description
|
||||
|
||||
-- | Get the designated render width of a character: 0 for a combining
|
||||
-- character, 1 for a regular character, 2 for a wide character.
|
||||
-- (Wide characters are rendered as exactly double width in apps and
|
||||
-- fonts that support it.) (From Pandoc.)
|
||||
charWidth :: Char -> Int
|
||||
charWidth c
|
||||
| c < '\x0300' = 1
|
||||
| c >= '\x0300' && c <= '\x036F' = 0 -- combining
|
||||
| c >= '\x0370' && c <= '\x10FC' = 1
|
||||
| c >= '\x1100' && c <= '\x115F' = 2
|
||||
| c >= '\x1160' && c <= '\x11A2' = 1
|
||||
| c >= '\x11A3' && c <= '\x11A7' = 2
|
||||
| c >= '\x11A8' && c <= '\x11F9' = 1
|
||||
| c >= '\x11FA' && c <= '\x11FF' = 2
|
||||
| c >= '\x1200' && c <= '\x2328' = 1
|
||||
| c >= '\x2329' && c <= '\x232A' = 2
|
||||
| c >= '\x232B' && c <= '\x2E31' = 1
|
||||
| c >= '\x2E80' && c <= '\x303E' = 2
|
||||
| c == '\x303F' = 1
|
||||
| c >= '\x3041' && c <= '\x3247' = 2
|
||||
| c >= '\x3248' && c <= '\x324F' = 1 -- ambiguous
|
||||
| c >= '\x3250' && c <= '\x4DBF' = 2
|
||||
| c >= '\x4DC0' && c <= '\x4DFF' = 1
|
||||
| c >= '\x4E00' && c <= '\xA4C6' = 2
|
||||
| c >= '\xA4D0' && c <= '\xA95F' = 1
|
||||
| c >= '\xA960' && c <= '\xA97C' = 2
|
||||
| c >= '\xA980' && c <= '\xABF9' = 1
|
||||
| c >= '\xAC00' && c <= '\xD7FB' = 2
|
||||
| c >= '\xD800' && c <= '\xDFFF' = 1
|
||||
| c >= '\xE000' && c <= '\xF8FF' = 1 -- ambiguous
|
||||
| c >= '\xF900' && c <= '\xFAFF' = 2
|
||||
| c >= '\xFB00' && c <= '\xFDFD' = 1
|
||||
| c >= '\xFE00' && c <= '\xFE0F' = 1 -- ambiguous
|
||||
| c >= '\xFE10' && c <= '\xFE19' = 2
|
||||
| c >= '\xFE20' && c <= '\xFE26' = 1
|
||||
| c >= '\xFE30' && c <= '\xFE6B' = 2
|
||||
| c >= '\xFE70' && c <= '\xFEFF' = 1
|
||||
| c >= '\xFF01' && c <= '\xFF60' = 2
|
||||
| c >= '\xFF61' && c <= '\x16A38' = 1
|
||||
| c >= '\x1B000' && c <= '\x1B001' = 2
|
||||
| c >= '\x1D000' && c <= '\x1F1FF' = 1
|
||||
| c >= '\x1F200' && c <= '\x1F251' = 2
|
||||
| c >= '\x1F300' && c <= '\x1F773' = 1
|
||||
| c >= '\x20000' && c <= '\x3FFFD' = 2
|
||||
| otherwise = 1
|
||||
|
@ -109,6 +109,7 @@ library
|
||||
, containers >=0.5.9
|
||||
, data-default >=0.5
|
||||
, directory
|
||||
, doclayout ==0.3.*
|
||||
, extra >=1.6.3
|
||||
, file-embed >=0.0.10
|
||||
, filepath
|
||||
@ -158,6 +159,7 @@ test-suite doctest
|
||||
, containers >=0.5.9
|
||||
, data-default >=0.5
|
||||
, directory
|
||||
, doclayout ==0.3.*
|
||||
, doctest >=0.18.1
|
||||
, extra >=1.6.3
|
||||
, file-embed >=0.0.10
|
||||
@ -210,6 +212,7 @@ test-suite unittest
|
||||
, containers >=0.5.9
|
||||
, data-default >=0.5
|
||||
, directory
|
||||
, doclayout ==0.3.*
|
||||
, extra >=1.6.3
|
||||
, file-embed >=0.0.10
|
||||
, filepath
|
||||
|
@ -47,6 +47,7 @@ dependencies:
|
||||
- data-default >=0.5
|
||||
- Decimal >=0.5.1
|
||||
- directory
|
||||
- doclayout >=0.3 && <0.4
|
||||
- file-embed >=0.0.10
|
||||
- filepath
|
||||
- hashtables >=1.2.3.1
|
||||
|
@ -26,6 +26,7 @@ import Lens.Micro.Platform
|
||||
import Safe
|
||||
import System.Console.ANSI
|
||||
import System.FilePath (takeFileName)
|
||||
import Text.DocLayout (realLength)
|
||||
|
||||
import Hledger
|
||||
import Hledger.Cli hiding (progname,prognameandversion)
|
||||
@ -122,7 +123,7 @@ asDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}}
|
||||
- 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)
|
||||
displayitems = s ^. asList . listElementsL
|
||||
|
||||
acctwidths = V.map (\AccountsScreenItem{..} -> asItemIndentLevel + Hledger.Cli.textWidth asItemDisplayAccountName) displayitems
|
||||
acctwidths = V.map (\AccountsScreenItem{..} -> asItemIndentLevel + realLength asItemDisplayAccountName) displayitems
|
||||
balwidths = V.map (maybe 0 (wbWidth . showMixedAmountB oneLine) . asItemMixedAmount) displayitems
|
||||
preferredacctwidth = V.maximum acctwidths
|
||||
totalacctwidthseen = V.sum acctwidths
|
||||
|
@ -73,6 +73,7 @@ executable hledger-ui
|
||||
, containers >=0.5.9
|
||||
, data-default
|
||||
, directory
|
||||
, doclayout ==0.3.*
|
||||
, extra >=1.6.3
|
||||
, filepath
|
||||
, fsnotify >=0.2.1.2 && <0.4
|
||||
|
@ -50,6 +50,7 @@ dependencies:
|
||||
- containers >=0.5.9
|
||||
- data-default
|
||||
- directory
|
||||
- doclayout >=0.3 && <0.4
|
||||
- extra >=1.6.3
|
||||
- filepath
|
||||
- fsnotify >=0.2.1.2 && <0.4
|
||||
|
@ -15,6 +15,8 @@ packages:
|
||||
extra-deps:
|
||||
# for Shake.hs (regex doesn't support base-compat-0.11):
|
||||
- regex-1.0.2.0@rev:1
|
||||
- doclayout-0.3.1.1
|
||||
- emojis-0.1.2
|
||||
# for testing base-compat 0.11 compatibility (mutually exclusive with the above):
|
||||
# - aeson-1.4.6.0
|
||||
# - aeson-compat-0.3.9
|
||||
|
@ -17,6 +17,8 @@ extra-deps:
|
||||
- pretty-simple-4.0.0.0
|
||||
- prettyprinter-1.7.0
|
||||
- doctest-0.18.1
|
||||
- doclayout-0.3.1.1
|
||||
- emojis-0.1.2
|
||||
# for hledger:
|
||||
# for hledger-ui:
|
||||
# for hledger-web:
|
||||
|
Loading…
Reference in New Issue
Block a user