packages feed

glirc 2.21.1 → 2.22

raw patch · 29 files changed

+1228/−465 lines, 29 filesdep ~config-schemadep ~irc-corePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: config-schema, irc-core

API changes (from Hackage documentation)

- Client.Configuration: [_configIndentWrapped] :: Configuration -> Maybe Int
- Client.Configuration: configIndentWrapped :: Lens' Configuration (Maybe Int)
- Client.Image.LineWrap: lineWrapChat :: Int -> Maybe Int -> Image' -> [Image']
- Client.Image.Message: [rendNickPadding] :: MessageRendererParams -> Maybe Integer
- Client.Image.Message: rightPad :: RenderMode -> Maybe Integer -> Image' -> Image'
- Client.State.Window: [_wlText] :: WindowLine -> {-# UNPACK #-} !Text
+ Client.Configuration: LeftPadding :: !Int -> PaddingMode
+ Client.Configuration: NoPadding :: PaddingMode
+ Client.Configuration: RightPadding :: !Int -> PaddingMode
+ Client.Configuration: data PaddingMode
+ Client.Configuration: instance GHC.Show.Show Client.Configuration.PaddingMode
+ Client.Image.LineWrap: fullLineWrap :: Int -> Image' -> [Image']
+ Client.Image.LineWrap: lineWrapPrefix :: Int -> Image' -> Image' -> [Image']
+ Client.Image.LineWrap: terminate :: Int -> Image -> Image
+ Client.Image.Message: nickPad :: PaddingMode -> Image' -> Image'
+ Client.Image.Message: timeImage :: Palette -> TimeOfDay -> Image'
+ Client.Image.PackedImage: resizeImage :: Int -> Image' -> Image'
+ Client.State: [_clientRtsStats] :: ClientState -> Maybe Stats
+ Client.State: clientMatcher' :: ClientState -> Maybe (Text -> Bool)
+ Client.State: clientRtsStats :: Lens' ClientState (Maybe Stats)
+ Client.State.Focus: FocusRtsStats :: Subfocus
+ Client.State.Window: [_wlPrefix] :: WindowLine -> !Image'
+ Client.State.Window: data PackedTime
+ Client.State.Window: packZonedTime :: ZonedTime -> PackedTime
+ Client.State.Window: unpackTimeOfDay :: PackedTime -> TimeOfDay
+ Client.State.Window: unpackUTCTime :: PackedTime -> UTCTime
+ Client.State.Window: wlPrefix :: Lens' WindowLine Image'
+ Client.View.RtsStats: rtsStatsLines :: Maybe Stats -> Palette -> [Image']
- Client.Configuration: Configuration :: ServerSettings -> (HashMap Text ServerSettings) -> Palette -> Text -> HashSet Identifier -> Maybe Integer -> Maybe Int -> Maybe FilePath -> Recognizer Macro -> [FilePath] -> Maybe FilePath -> HashSet Identifier -> Bool -> Bool -> Bool -> KeyMap -> LayoutMode -> [Modifier] -> Configuration
+ Client.Configuration: Configuration :: ServerSettings -> (HashMap Text ServerSettings) -> Palette -> Text -> HashSet Identifier -> PaddingMode -> Maybe FilePath -> Recognizer Macro -> [FilePath] -> Maybe FilePath -> HashSet Identifier -> Bool -> Bool -> Bool -> KeyMap -> LayoutMode -> [Modifier] -> Configuration
- Client.Configuration: [_configNickPadding] :: Configuration -> Maybe Integer
+ Client.Configuration: [_configNickPadding] :: Configuration -> PaddingMode
- Client.Configuration: configNickPadding :: Lens' Configuration (Maybe Integer)
+ Client.Configuration: configNickPadding :: Lens' Configuration PaddingMode
- Client.Image.LineWrap: lineWrap :: Int -> Image -> Image
+ Client.Image.LineWrap: lineWrap :: Int -> Image' -> [Image']
- Client.Image.Message: MessageRendererParams :: [Char] -> [Char] -> HashSet Identifier -> HashSet Identifier -> Palette -> Maybe Integer -> MessageRendererParams
+ Client.Image.Message: MessageRendererParams :: [Char] -> [Char] -> HashSet Identifier -> HashSet Identifier -> Palette -> MessageRendererParams
- Client.Image.Message: msgImage :: RenderMode -> ZonedTime -> MessageRendererParams -> MessageBody -> Image'
+ Client.Image.Message: msgImage :: ZonedTime -> MessageRendererParams -> MessageBody -> (Image', Image', Image')
- Client.Image.StatusLine: minorStatusLineImage :: Focus -> Int -> Bool -> ClientState -> Image
+ Client.Image.StatusLine: minorStatusLineImage :: Focus -> Int -> Bool -> ClientState -> Image'
- Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Focus -> !Subfocus -> ![Focus] -> !(IntMap NetworkState) -> !NetworkId -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !EditBox -> !Int -> !Int -> !Int -> !Int -> !Bool -> !Bool -> Maybe Regex -> !LayoutMode -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ![LogLine] -> Maybe Text -> ClientState
+ Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Focus -> !Subfocus -> ![Focus] -> !(IntMap NetworkState) -> !NetworkId -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !EditBox -> !Int -> !Int -> !Int -> !Int -> !Bool -> !Bool -> Maybe Regex -> !LayoutMode -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ![LogLine] -> Maybe Text -> Maybe Stats -> ClientState
- Client.State.EditBox: class HasLine c_au2K where pos = (.) line pos text = (.) line text
+ Client.State.EditBox: class HasLine c_aIxW where pos = (.) line pos text = (.) line text
- Client.State.EditBox: line :: HasLine c_au2K => Lens' c_au2K Line
+ Client.State.EditBox: line :: HasLine c_aIxW => Lens' c_aIxW Line
- Client.State.EditBox: pos :: HasLine c_au2K => Lens' c_au2K Int
+ Client.State.EditBox: pos :: HasLine c_aIxW => Lens' c_aIxW Int
- Client.State.EditBox: text :: HasLine c_au2K => Lens' c_au2K String
+ Client.State.EditBox: text :: HasLine c_aIxW => Lens' c_aIxW String
- Client.State.EditBox.Content: class HasLine c_au2K where pos = (.) line pos text = (.) line text
+ Client.State.EditBox.Content: class HasLine c_aIxW where pos = (.) line pos text = (.) line text
- Client.State.EditBox.Content: line :: HasLine c_au2K => Lens' c_au2K Line
+ Client.State.EditBox.Content: line :: HasLine c_aIxW => Lens' c_aIxW Line
- Client.State.EditBox.Content: pos :: HasLine c_au2K => Lens' c_au2K Int
+ Client.State.EditBox.Content: pos :: HasLine c_aIxW => Lens' c_aIxW Int
- Client.State.EditBox.Content: text :: HasLine c_au2K => Lens' c_au2K String
+ Client.State.EditBox.Content: text :: HasLine c_aIxW => Lens' c_aIxW String
- Client.State.Window: WindowLine :: !IrcSummary -> {-# UNPACK #-} !Text -> !Image' -> !Image' -> !WindowLineImportance -> {-# UNPACK #-} !UTCTime -> WindowLine
+ Client.State.Window: WindowLine :: !IrcSummary -> !Image' -> !Image' -> !Image' -> !WindowLineImportance -> {-# UNPACK #-} !PackedTime -> WindowLine
- Client.State.Window: [_wlTimestamp] :: WindowLine -> {-# UNPACK #-} !UTCTime
+ Client.State.Window: [_wlTimestamp] :: WindowLine -> {-# UNPACK #-} !PackedTime
- Client.State.Window: wlText :: Lens' WindowLine Text
+ Client.State.Window: wlText :: Getter WindowLine Text
- Client.State.Window: wlTimestamp :: Lens' WindowLine UTCTime
+ Client.State.Window: wlTimestamp :: Lens' WindowLine PackedTime
- Client.View: viewLines :: Focus -> Subfocus -> Int -> ClientState -> [Image]
+ Client.View: viewLines :: Focus -> Subfocus -> Int -> ClientState -> [Image']
- Client.View.ChannelInfo: channelInfoImages :: Text -> Identifier -> ClientState -> [Image]
+ Client.View.ChannelInfo: channelInfoImages :: Text -> Identifier -> ClientState -> [Image']
- Client.View.Digraphs: digraphLines :: Int -> ClientState -> [Image]
+ Client.View.Digraphs: digraphLines :: Int -> ClientState -> [Image']
- Client.View.Help: helpImageLines :: Maybe Text -> Palette -> [Image]
+ Client.View.Help: helpImageLines :: Maybe Text -> Palette -> [Image']
- Client.View.KeyMap: keyMapLines :: ClientState -> [Image]
+ Client.View.KeyMap: keyMapLines :: ClientState -> [Image']
- Client.View.MaskList: maskListImages :: Char -> Text -> Identifier -> Int -> ClientState -> [Image]
+ Client.View.MaskList: maskListImages :: Char -> Text -> Identifier -> Int -> ClientState -> [Image']
- Client.View.Mentions: mentionsViewLines :: Int -> ClientState -> [Image]
+ Client.View.Mentions: mentionsViewLines :: Int -> ClientState -> [Image']
- Client.View.Messages: chatMessageImages :: Focus -> Int -> ClientState -> [Image]
+ Client.View.Messages: chatMessageImages :: Focus -> Int -> ClientState -> [Image']
- Client.View.Palette: paletteViewLines :: Palette -> [Image]
+ Client.View.Palette: paletteViewLines :: Palette -> [Image']
- Client.View.UrlSelection: urlSelectionView :: Focus -> String -> ClientState -> [Image]
+ Client.View.UrlSelection: urlSelectionView :: Int -> Focus -> String -> ClientState -> [Image']
- Client.View.UserList: userInfoImages :: Text -> Identifier -> ClientState -> [Image]
+ Client.View.UserList: userInfoImages :: Text -> Identifier -> ClientState -> [Image']
- Client.View.UserList: userListImages :: Text -> Identifier -> ClientState -> [Image]
+ Client.View.UserList: userListImages :: Text -> Identifier -> ClientState -> [Image']
- Client.View.Windows: windowsImages :: WindowsFilter -> ClientState -> [Image]
+ Client.View.Windows: windowsImages :: WindowsFilter -> ClientState -> [Image']

Files

ChangeLog.md view
@@ -1,5 +1,23 @@ # Revision history for glirc2 +## 2.22++* Added dynanic indentation of wrapped lines. Lines now+  wrap to where the message portion of the line started.+  This removes `indent-wrapped-lines` configuration setting+* Added pervasive word-boundary oriented line wrapping+  (beyond what was previously restricted to chat messages)+* Made nick prefix padding configurable to be on left or right.+  See `nick-padding` setting in `glirc2 --config-format`.+  In addition this setting is now reconfigurable at runtime+  via `/reload`+* Added `/oper` command for network operator authentication.+* Memory savings by packing message timestamp information, and+  using the "detailed" image for regular expression matching.+* Added `/rtsstats` command for inspecting the GHC RTS statistics+* Added many minor IRC query commands: admin, info, map, rules+  motd, version, lusers, kill, knock, list.+ ## 2.21.1  * Support for latest config-schema
glirc.cabal view
@@ -1,5 +1,5 @@ name:                glirc-version:             2.21.1+version:             2.22 synopsis:            Console IRC client description:         Console IRC client                      .@@ -54,7 +54,7 @@   build-depends:       base, glirc, lens, text, vty  library-  ghc-options: -Wall+  ghc-options:         -Wall -O2   hs-source-dirs:      src   include-dirs:        include   includes:            include/glirc-api.h@@ -113,6 +113,7 @@                        Client.View.Mentions                        Client.View.Messages                        Client.View.Palette+                       Client.View.RtsStats                        Client.View.UrlSelection                        Client.View.UserList                        Client.View.Windows@@ -120,6 +121,7 @@   other-modules:       LensUtils                        StrictUnit                        Digraphs+                       RtsStats                        Paths_glirc                        Build_glirc @@ -129,14 +131,14 @@                        bytestring           >=0.10.8 && <0.11,                        base64-bytestring    >=1.0.0.1 && <1.1,                        config-value         >=0.6    && <0.7,-                       config-schema        >=0.4    && <0.5,+                       config-schema        >=0.4    && <0.6,                        containers           >=0.5.7  && <0.6,                        directory            >=1.2.6  && <1.4,                        filepath             >=1.4.1  && <1.5,                        gitrev               >=1.2    && <1.4,                        hashable             >=1.2.4  && <1.3,                        HsOpenSSL            >=0.11   && <0.12,-                       irc-core             >=2.2    && <2.3,+                       irc-core             >=2.3    && <2.4,                        lens                 >=4.14   && <4.16,                        kan-extensions       >=5.0    && <5.1,                        network              >=2.6.2  && <2.7,
src/Client/Commands.hs view
@@ -61,6 +61,7 @@ import           Irc.UserInfo import           Irc.Modes import           LensUtils+import           RtsStats (getStats) import           System.Process import           Text.Read import           Text.Regex.TDFA@@ -338,6 +339,12 @@     $ ClientCommand cmdKeyMap noClientTab    , Command+      (pure "rtsstats")+      NoArg+      "Show the GHC RTS statistics.\n"+    $ ClientCommand cmdRtsStats noClientTab++  , Command       (pure "exec")       (RemainingArg "arguments")       "Execute a command synchnonously sending the to a configuration destination.\n\@@ -346,7 +353,7 @@       \\n\       \When \^Binput\^B is specified it is sent to the stdin.\n\       \\n\-      \When neither \^Bnetwork\^B nor \^Bchannel\^B are specified output goes to client window (*)\n\+      \When neither \^Bnetwork\^B nor \^Bchannel\^B are specified output goes to client window (*).\n\       \When \^Bnetwork\^B is specified output is sent as raw IRC traffic to the network.\n\       \When \^Bchannel\^B is specified output is sent as chat to the given channel on the current network.\n\       \When \^Bnetwork\^B and \^Bchannel\^B are specified output is sent as chat to the given channel on the given network.\n\@@ -494,18 +501,32 @@   , Command       (pure "splits")       (RemainingArg "focuses")-      "Set the extra message view splits.\n\+      "\^BParameters:\^B\n\       \\n\-      \\^Bfocuses\^B: space delimited list of focus names.\n\+      \    focuses: List of focus names\n\       \\n\-      \Client:  *\n\-      \Network: \^BNETWORK\^B:\n\-      \Channel: \^BNETWORK\^B:\^B#CHANNEL\^B\n\-      \User:    \^BNETWORK\^B:\^BNICK\^B\n\+      \\^BDescription:\^B\n\       \\n\-      \If the network part is omitted, the current network will be used\n\+      \    This command sents the set of focuses that will always\n\+      \    be visible, even when unfocused. When the client is focused\n\+      \    to an active network, the network can be omitted when\n\+      \    specifying a focus. If no focuses are listed, they will\n\+      \    all be cleared.\n\       \\n\-      \Not providing an argument unsplits the current windows.\n"+      \    Client:  *\n\+      \    Network: \^_network\^_:\n\+      \    Channel: \^_#channel\^_\n\+      \    Channel: \^_network\^_:\^_#channel\^_\n\+      \    User:    \^_nick\^_\n\+      \    User:    \^_network\^_:\^_nick\^_\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /splits * fn:#haskell fn:chanserv\n\+      \    /splits #haskell #haskell-lens nickserv\n\+      \    /splits\n\+      \\n\+      \\^BSee also:\^B splits+, splits-\n"     $ ClientCommand cmdSplits tabSplits    , Command@@ -521,7 +542,7 @@       \Channel: \^BNETWORK\^B:\^B#CHANNEL\^B\n\       \User:    \^BNETWORK\^B:\^BNICK\^B\n\       \\n\-      \If the network part is omitted, the current network will be used\n"+      \If the network part is omitted, the current network will be used.\n"     $ ClientCommand cmdSplitsAdd tabSplits    , Command@@ -537,7 +558,7 @@       \Channel: \^BNETWORK\^B:\^B#CHANNEL\^B\n\       \User:    \^BNETWORK\^B:\^BNICK\^B\n\       \\n\-      \If the network part is omitted, the current network will be used\n"+      \If the network part is omitted, the current network will be used.\n"     $ ClientCommand cmdSplitsDel tabActiveSplits    , Command@@ -581,89 +602,219 @@   [ Command       ("join" :| ["j"])       (ReqTokenArg "channels" (OptTokenArg "keys" NoArg))-      "Join a chat channel.\n\+      "\^BParameters:\^B\n\       \\n\-      \\^Bchannels\^B: comma-separated list of channels\n\-      \\^Bkeys\^B: comma-separated list of keys\n"-    $ NetworkCommand cmdJoin   simpleNetworkTab+      \    channels: Comma-separated list of channels\n\+      \    keys:     Comma-separated list of keys\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Join the given channels. When keys are provided, they should\n\+      \    occur in the same order as the channels.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /join #friends\n\+      \    /join #secret thekey\n\+      \    /join #secret1,#secret2 key1,key2\n\+      \\n\+      \\^BSee also:\^B channel, clear, part\n"+    $ NetworkCommand cmdJoin simpleNetworkTab    , Command       (pure "part")       (RemainingArg "reason")-      "Part from the current channel.\n"+      "\^BParameters:\^B\n\+      \\n\+      \    reason: Optional message sent to channel as part reason\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Part from the current channel.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /part\n\+      \    /part It's not me, it's you\n\+      \\n\+      \\^BSee also:\^B clear, join, quit\n"     $ ChannelCommand cmdPart simpleChannelTab    , Command       (pure "msg")       (ReqTokenArg "target" (RemainingArg "message"))-      "Send a chat message to a user or a channel.\n\+      "\^BParameters:\^B\n\       \\n\-      \\^Btarget\^B can be a channel or nickname.\n"-    $ NetworkCommand cmdMsg    simpleNetworkTab+      \    target:  Comma-separated list of nicknames and channels\n\+      \    message: Formatted message body\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Send a chat message to a user or a channel. On servers\n\+      \    with STATUSMSG support, the channel name can be prefixed\n\+      \    with a sigil to restrict the recipients to those with the\n\+      \    given mode.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /msg buddy I'm sending you a message.\n\+      \    /msg #friends This message is for the whole channel.\n\+      \    /msg him,her I'm chatting with two people.\n\+      \    /msg @#users This message is only for ops!\n\+      \\n\+      \\^BSee also:\^B notice, me, say\n"+    $ NetworkCommand cmdMsg simpleNetworkTab    , Command       (pure "me")       (RemainingArg "message")-      "Send an 'action' to the current chat window.\n"+      "\^BParameters:\^B\n\+      \\n\+      \    message: Body of action message\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Sends an action message to the currently focused channel.\n\+      \    Most clients will render these messages prefixed with\n\+      \    only your nickname as though describing an action.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /me shrugs\n\+      \\n\+      \\^BSee also:\^B notice, msg, say\n"     $ ChatCommand cmdMe simpleChannelTab    , Command       (pure "say")       (RemainingArg "message")-      "Send a message to the current chat window.\n\+      "\^BParameters:\^B\n\       \\n\-      \This can be useful for sending a chat message with a leading '/'.\n"+      \    message: Body of message\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Send a message to the current chat window.  This can be useful\n\+      \    for sending a chat message with a leading '/' to the current\n\+      \    chat window.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /say /help is the right place to start!\n\+      \\n\+      \\^BSee also:\^B notice, me, msg\n"     $ ChatCommand cmdSay simpleChannelTab    , Command       (pure "notice")       (ReqTokenArg "target" (RemainingArg "message"))-      "Send a notice message to a user or a channel. Notices are typically used by bots.\n\+      "\^BParameters:\^B\n\       \\n\-      \\^Btarget\^B can be a channel or nickname.\n"+      \    target:  Comma-separated list of nicknames and channels\n\+      \    message: Formatted message body\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Send a chat notice to a user or a channel. On servers\n\+      \    with STATUSMSG support, the channel name can be prefixed\n\+      \    with a sigil to restrict the recipients to those with the\n\+      \    given mode. Notice messages were originally intended to be\n\+      \    used by bots. Different clients will render these in different\n\+      \    ways.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /notice buddy I'm sending you a message.\n\+      \    /notice #friends This message is for the whole channel.\n\+      \    /notice him,her I'm chatting with two people.\n\+      \    /notice @#users This message is only for ops!\n\+      \\n\+      \\^BSee also:\^B me, msg, say\n"     $ NetworkCommand cmdNotice simpleNetworkTab    , Command       (pure "ctcp")       (ReqTokenArg "target" (ReqTokenArg "command" (RemainingArg "arguments")))-      "Send a CTCP command to a user or a channel.\n\+      "\^BParameters:\^B\n\       \\n\-      \Examples:\n\-      \Version query:    /ctcp user1 version\n\-      \Local-time query: /ctcp user1 time\n\+      \    target:    Comma-separated list of nicknames and channels\n\+      \    command:   CTCP command name\n\+      \    arguments: CTCP command arguments\n\       \\n\-      \\^Btarget\^B can be a channel or nickname.\n\-      \\^Bcommand\^B can be any CTCP command.\n\-      \\^Barguments\^B are specific to a particular command.\n"+      \\^BDescription:\^B\n\+      \\n\+      \    Client-to-client protocol (CTCP) commands can be used\n\+      \    to query information from another user's client application\n\+      \    directly. Common CTCP commands include: ACTION, PING, VERSION,\n\+      \    USERINFO, CLIENTINFO, and TIME. glirc does not automatically\n\+      \    respond to CTCP commands.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /ctcp myfriend VERSION\n\+      \    /ctcp myfriend CLIENTINFO\n"     $ NetworkCommand cmdCtcp simpleNetworkTab    , Command       (pure "nick")       (ReqTokenArg "nick" NoArg)-      "Change your nickname.\n"-    $ NetworkCommand cmdNick   simpleNetworkTab+      "\^BParameters:\^B\n\+      \\n\+      \    nick: New nickname\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Change your nickname on the currently focused server.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /nick guest123\n\+      \    /nick better_nick\n"+    $ NetworkCommand cmdNick simpleNetworkTab    , Command       (pure "away")       (RemainingArg "message")-      "Set away status.\n\+      "\^BParameters:\^B\n\       \\n\-      \When \^Bmessage\^B is omitted away status is cleared.\n"+      \    message: Optional away message\n\+      \\n\+      \\^BDescription:\^B\n\+      \\n\+      \    Change your nickname on the currently focused server.\n\+      \    Omit the message parameter to clear your away status.\n\+      \    The away message is only used by the server to update\n\+      \    status in /whois and to provide automated responses.\n\+      \    It is not used by this client directly.\n\+      \\n\+      \\^BExamples:\^B\n\+      \\n\+      \    /away\n\+      \    /away Out getting some sun\n"     $ NetworkCommand cmdAway simpleNetworkTab    , Command       ("users" :| ["names"])       NoArg-      "Show the user list for the current channel.\n\+      "\^BDescription:\^B\n\       \\n\-      \Detailed view (F2) shows full hostmask.\n\-      \Hostmasks can be populated with /who #channel.\n"+      \    Show the user list for the current channel.\n\+      \    Detailed view (default key F2) shows full hostmask.\n\+      \    Hostmasks can be populated with /who #channel.\n\+      \    Press ESC to exit the userlist.\n\+      \\n\+      \\^BSee also:\^B channelinfo, masks\n"     $ ChannelCommand cmdUsers  noChannelTab    , Command       (pure "channelinfo")       NoArg-      "Show information about the current channel.\n"+      "\^BDescription:\^B\n\+      \\n\+      \    Show information about the current channel.\n\+      \    Press ESC to exit the channel info window.\n\+      \\n\+      \\^BSee also:\^B masks, users\n"     $ ChannelCommand cmdChannelInfo noChannelTab    , Command@@ -707,14 +858,8 @@     $ NetworkCommand cmdUserhost simpleNetworkTab    , Command-      (pure "links")-      (RemainingArg "arguments")-      "Send LINKS query to server with given arguments.\n"-    $ NetworkCommand cmdLinks simpleNetworkTab--  , Command       (pure "time")-      (RemainingArg "arguments")+      (OptTokenArg "servername" NoArg)       "Send TIME query to server with given arguments.\n"     $ NetworkCommand cmdTime simpleNetworkTab @@ -724,6 +869,42 @@       "Send STATS query to server with given arguments.\n"     $ NetworkCommand cmdStats simpleNetworkTab +  , Command+      (pure "lusers")+      (OptTokenArg "mask" (OptTokenArg "servername" NoArg))+      "Send LUSERS query to server with given arguments.\n"+    $ NetworkCommand cmdLusers simpleNetworkTab++  , Command+      (pure "motd") (OptTokenArg "servername" NoArg)+      "Send MOTD query to server.\n"+    $ NetworkCommand cmdMotd simpleNetworkTab++  , Command+      (pure "admin") (OptTokenArg "servername" NoArg)+      "Send ADMIN query to server.\n"+    $ NetworkCommand cmdAdmin simpleNetworkTab++  , Command+      (pure "rules") (OptTokenArg "servername" NoArg)+      "Send RULES query to server.\n"+    $ NetworkCommand cmdRules simpleNetworkTab++  , Command+      (pure "info") NoArg+      "Send INFO query to server.\n"+    $ NetworkCommand cmdInfo noNetworkTab++  , Command+      (pure "list") (RemainingArg "arguments")+      "Send LIST query to server.\n"+    $ NetworkCommand cmdList simpleNetworkTab++  , Command+      (pure "version") (OptTokenArg "servername" NoArg)+      "Send VERSION query to server.\n"+    $ NetworkCommand cmdVersion simpleNetworkTab+   ------------------------------------------------------------------------   ] , CommandSection "IRC channel management"   ------------------------------------------------------------------------@@ -799,6 +980,12 @@       \See also: /kick /kickban\n"     $ ChannelCommand cmdRemove simpleChannelTab +  , Command+      (pure "knock")+      (ReqTokenArg "channel" (RemainingArg "message"))+      "Request entry to an invite-only channel.\n"+    $ NetworkCommand cmdKnock simpleNetworkTab+   ------------------------------------------------------------------------   ] , CommandSection "ZNC Support"   ------------------------------------------------------------------------@@ -828,6 +1015,32 @@       \Note that the playback module is not installed in ZNC by default!\n"     $ NetworkCommand cmdZncPlayback noNetworkTab +  ] , CommandSection "Network operator commands"++  [ Command+      (pure "oper")+      (ReqTokenArg "user" (ReqTokenArg "password" NoArg))+      "Authenticate as a server operator.\n"+    $ NetworkCommand cmdOper noNetworkTab++  , Command+      (pure "kill")+      (ReqTokenArg "client" (RemainingArg "reason"))+      "Kill a client connection to the server.\n"+    $ NetworkCommand cmdKill simpleNetworkTab++  , Command+      (pure "map")+      NoArg+      "Display network map.\n"+    $ NetworkCommand cmdMap simpleNetworkTab++  , Command+      (pure "links")+      (RemainingArg "arguments")+      "Send LINKS query to server with given arguments.\n"+    $ NetworkCommand cmdLinks simpleNetworkTab+   ]]  -- | Provides no tab completion for client commands@@ -1083,6 +1296,16 @@ cmdKeyMap :: ClientCommand () cmdKeyMap st _ = commandSuccess (changeSubfocus FocusKeyMap st) +-- | Implementation of @/rtsstats@ command. Set subfocus to RtsStats.+-- Update cached rts stats in client state.+cmdRtsStats :: ClientCommand ()+cmdRtsStats st _ =+  do mb <- getStats+     case mb of+       Nothing -> commandFailureMsg "RTS statistics not available. (Use +RTS -T)" st+       Just{}  -> commandSuccess $ set clientRtsStats mb+                                 $ changeSubfocus FocusRtsStats st+ -- | Implementation of @/help@ command. Set subfocus to Help. cmdHelp :: ClientCommand (Maybe (String, ())) cmdHelp st mb = commandSuccess (changeSubfocus focus st)@@ -1269,6 +1492,64 @@   do sendMsg cs (ircStats (Text.pack <$> words rest))      commandSuccess st +cmdLusers :: NetworkCommand (Maybe (String, Maybe (String, ())))+cmdLusers cs st arg =+  do sendMsg cs $ ircLusers $ fmap Text.pack $+       case arg of+         Nothing                -> []+         Just (x, Nothing)      -> [x]+         Just (x, (Just (y,_))) -> [x,y]+     commandSuccess st++cmdMotd :: NetworkCommand (Maybe (String, ()))+cmdMotd cs st mbservername =+  do sendMsg cs $ ircMotd $ case mbservername of+                              Just (s,_) -> Text.pack s+                              Nothing    -> ""+     commandSuccess st++cmdAdmin :: NetworkCommand (Maybe (String, ()))+cmdAdmin cs st mbservername =+  do sendMsg cs $ ircAdmin $ case mbservername of+                              Just (s,_) -> Text.pack s+                              Nothing    -> ""+     commandSuccess st++cmdRules :: NetworkCommand (Maybe (String, ()))+cmdRules cs st mbservername =+  do sendMsg cs $ ircRules $+       case mbservername of+         Just (s,_) -> Text.pack s+         Nothing    -> ""+     commandSuccess st++cmdMap :: NetworkCommand ()+cmdMap cs st _ =+  do sendMsg cs ircMap+     commandSuccess st++cmdInfo :: NetworkCommand ()+cmdInfo cs st _ =+  do sendMsg cs ircInfo+     commandSuccess st++cmdVersion :: NetworkCommand (Maybe (String, ()))+cmdVersion cs st mbservername =+  do sendMsg cs $ ircVersion $ case mbservername of+                                Just (s,_) -> Text.pack s+                                Nothing    -> ""+     commandSuccess st++cmdList :: NetworkCommand String+cmdList cs st rest =+  do sendMsg cs (ircList (Text.pack <$> words rest))+     commandSuccess st++cmdKill :: NetworkCommand (String, String)+cmdKill cs st (client,rest) =+  do sendMsg cs (ircKill (Text.pack client) (Text.pack rest))+     commandSuccess st+ cmdAway :: NetworkCommand String cmdAway cs st rest =   do sendMsg cs (ircAway (Text.pack rest))@@ -1279,9 +1560,12 @@   do sendMsg cs (ircLinks (Text.pack <$> words rest))      commandSuccess st -cmdTime :: NetworkCommand String-cmdTime cs st rest =-  do sendMsg cs (ircTime (Text.pack <$> words rest))+cmdTime :: NetworkCommand (Maybe (String, ()))+cmdTime cs st arg =+  do sendMsg cs $ ircTime $+       case arg of+         Nothing    -> ""+         Just (x,_) -> Text.pack x      commandSuccess st  cmdZnc :: NetworkCommand String@@ -1439,6 +1723,11 @@      cs' <- sendModeration channelId [cmd] cs      commandSuccessUpdateCS cs' st +cmdKnock :: NetworkCommand (String, String)+cmdKnock cs st (chan,message) =+  do sendMsg cs (ircKnock (Text.pack chan) (Text.pack message))+     commandSuccess st+ cmdJoin :: NetworkCommand (String, Maybe (String, ())) cmdJoin cs st (channels, mbKeys) =   do let network = view csNetwork cs@@ -1812,3 +2101,8 @@       case compile defaultCompOpt{caseSensitive=sensitive} defaultExecOpt str of         Left e -> commandFailureMsg (Text.pack e) st         Right r -> commandSuccess (set clientRegex (Just r) st)++cmdOper :: NetworkCommand (String, (String, ()))+cmdOper cs st (user, (pass, ())) =+  do sendMsg cs (ircOper (Text.pack user) (Text.pack pass))+     commandSuccess st
src/Client/Configuration.hs view
@@ -20,6 +20,7 @@     Configuration(..)   , ConfigurationFailure(..)   , LayoutMode(..)+  , PaddingMode(..)    -- * Lenses   , configDefaults@@ -27,7 +28,6 @@   , configPalette   , configWindowNames   , configNickPadding-  , configIndentWrapped   , configConfigPath   , configMacros   , configExtensions@@ -91,8 +91,7 @@   , _configPalette         :: Palette   , _configWindowNames     :: Text -- ^ Names of windows, used when alt-jumping)   , _configExtraHighlights :: HashSet Identifier -- ^ Extra highlight nicks/terms-  , _configNickPadding     :: Maybe Integer -- ^ Padding of nicks-  , _configIndentWrapped   :: Maybe Int -- ^ How far to indent wrapped lines+  , _configNickPadding     :: PaddingMode -- ^ Padding of nicks in messages   , _configConfigPath      :: Maybe FilePath         -- ^ manually specified configuration path, used for reloading   , _configMacros          :: Recognizer Macro -- ^ command macros@@ -108,6 +107,13 @@   }   deriving Show +-- | Setting for how to pad the message prefix.+data PaddingMode+  = LeftPadding  !Int -- ^ Whitespace add to the left side of chat prefix+  | RightPadding !Int -- ^ Whitespace add to the right side of chat prefix+  | NoPadding         -- ^ No whitespace added+  deriving (Show)+ data LayoutMode   -- | Vertically stack all windows in a single column   = OneColumn@@ -255,10 +261,8 @@                                "External command used by /url command"      _configExtraHighlights <- sec' mempty "extra-highlights" identifierSetSpec                                "Extra words to highlight in chat messages"-     _configNickPadding     <- optSection' "nick-padding" nonnegativeSpec+     _configNickPadding     <- sec' NoPadding "nick-padding" nickPaddingSpec                                "Amount of space to reserve for nicknames in chat messages"-     _configIndentWrapped   <- optSection' "indent-wrapped-lines" nonnegativeSpec-                               "Amount of indentation for wrapped message lines"      _configIgnores         <- sec' mempty "ignores" identifierSetSpec                                "Set of nicknames to ignore on startup"      _configActivityBar     <- sec' False  "activity-bar" yesOrNoSpec@@ -277,6 +281,37 @@                  _configServers  = buildServerMap _configDefaults ssUpdates                  _configKeyMap   = foldl (\acc f -> f acc) initialKeyMap bindings              in Configuration{..})++-- | The default nick padding side if padding is going to be used+defaultPaddingSide :: Int -> PaddingMode+defaultPaddingSide = RightPadding++-- | Either full or abbreviated nick-padding configuration+--+-- > nick-padding: 10+--+-- > nick-padding:+-- >   side: right+-- >   width: 16+nickPaddingSpec :: ValueSpecs PaddingMode+nickPaddingSpec = defaultPaddingSide <$> nonnegativeSpec <!> fullNickPaddingSpec++-- | Full nick padding specification:+--+-- > nick-padding:+-- >   side: left+-- >   width: 15+fullNickPaddingSpec :: ValueSpecs PaddingMode+fullNickPaddingSpec = sectionsSpec "nick-padding" (sideSec <*> amtSec)+  where+    sideSpec = LeftPadding  <$ atomSpec "left" <!>+               RightPadding <$ atomSpec "right"++    sideSec = fromMaybe defaultPaddingSide+          <$> optSection' "side" sideSpec "Side to pad (default `right`)"++    amtSec  = reqSection' "width" nonnegativeSpec "Field width"+  modifierSpec :: ValueSpecs [Modifier] modifierSpec = toList <$> oneOrNonemptySpec modifier1Spec
src/Client/Image/Layout.hs view
@@ -12,9 +12,10 @@ import Client.State import Client.State.Focus import Client.Configuration (LayoutMode(..))+import Client.Image.PackedImage (Image', unpackImage) import Client.Image.StatusLine (statusLineImage, minorStatusLineImage) import Client.Image.Textbox-import Client.Image.LineWrap (lineWrap)+import Client.Image.LineWrap (lineWrap, terminate) import Client.Image.Palette import Client.View import Graphics.Vty.Image@@ -114,10 +115,10 @@   Int         {- ^ draw width      -} ->   Int         {- ^ draw height     -} ->   Focus       {- ^ focus           -} ->-  [Image]     {- ^ image lines     -} ->+  [Image']    {- ^ image lines     -} ->   Image       {- ^ rendered window -} drawExtra st w h focus lineImages =-    msgImg <-> minorStatusLineImage focus w True st+    msgImg <-> unpackImage (minorStatusLineImage focus w True st)   where     (_, msgImg) = messagePane w h 0 lineImages @@ -129,7 +130,7 @@   Int          {- ^ client width                  -} ->   Int          {- ^ available rows                -} ->   Int          {- ^ current scroll                -} ->-  [Image]      {- ^ focused window                -} ->+  [Image']     {- ^ focused window                -} ->   (Int, Image) {- ^ overscroll, rendered messages -} messagePane w h scroll images = (overscroll, img)   where@@ -143,7 +144,11 @@      assemble acc _ | imageHeight acc >= vh = cropTop vh acc     assemble acc [] = acc-    assemble acc (x:xs) = assemble (lineWrap w x <-> acc) xs+    assemble acc (x:xs) = assemble (this <-> acc) xs+      where+        this = vertCat+             $ map (terminate w . unpackImage)+             $ lineWrap w x   splitHeights ::
src/Client/Image/LineWrap.hs view
@@ -8,7 +8,12 @@ Provides utilities for line wrapping images. -} -module Client.Image.LineWrap (lineWrap, lineWrapChat) where+module Client.Image.LineWrap+  ( lineWrap+  , lineWrapPrefix+  , fullLineWrap+  , terminate+  ) where  import           Client.Image.PackedImage import           Data.Semigroup@@ -16,18 +21,6 @@ import           Graphics.Vty.Attributes import qualified Data.Text.Lazy as L --- | Given an image, break the image up into chunks of at most the--- given width and stack the resulting chunks vertically top-to-bottom.-lineWrap ::-  Int       {- ^ terminal width       -} ->-  Vty.Image {- ^ unwrapped image      -} ->-  Vty.Image {- ^ wrapped image        -}-lineWrap w img-  | Vty.imageWidth img == 0 = Vty.emptyImage-  | Vty.imageWidth img <= w = terminate w img-  | otherwise =-      terminate w (Vty.cropRight w img) Vty.<->-      lineWrap w (Vty.cropLeft (Vty.imageWidth img - w) img)  -- | Trailing space with default attributes deals with bug in VTY -- where the formatting will continue past the end of chat messages.@@ -41,31 +34,52 @@   | otherwise               = img Vty.<|> Vty.char defAttr ' '  -lineWrapChat ::-  Int       {- ^ terminal width       -} ->-  Maybe Int {- ^ optional indentation -} ->-  Image'    {- ^ unwrapped image      -} ->-  [Image']  {- ^ wrapped image        -}-lineWrapChat w (Just i)-  | 2*i <= w = reverse . addPadding i . wordLineWrap w (w-i)-lineWrapChat w _  = reverse . wordLineWrap w w+-- | This version of line wrap wraps without regard for word boundaries.+fullLineWrap ::+  Int       {- ^ terminal width  -} ->+  Image'    {- ^ unwrapped image -} ->+  [Image']  {- ^ wrapped image   -}+fullLineWrap w img+  | iw <= w = [img]+  | otherwise = l : fullLineWrap w r+  where+    iw = imageWidth img+    (l,r) = splitImage w img +lineWrapPrefix ::+  Int       {- ^ terminal width  -} ->+  Image'    {- ^ prefix image    -} ->+  Image'    {- ^ unwrapped image -} ->+  [Image']  {- ^ wrapped image   -}+lineWrapPrefix w pfx img+  | 3*pfxW <= w = pfx <> char defAttr ' ' <> x :+                  map (pad<>) xs+  where+    pfxW = imageWidth pfx+    x:xs = lineWrap (w - pfxW - 1) img+    pad  = string defAttr (replicate (pfxW + 1) ' ') -addPadding :: Int -> [Image'] -> [Image']-addPadding _ [] = []-addPadding i (x:xs) = x : map indent xs-  where indent = (string defAttr (replicate i ' ') <>)+-- Don't index when the window is tiny+lineWrapPrefix w pfx img = lineWrap w (pfx <> char defAttr ' ' <> img)  -wordLineWrap ::+lineWrap ::   Int      {- ^ first line length     -} ->-  Int      {- ^ secondary line length -} ->   Image'   {- ^ image                 -} ->   [Image'] {- ^ splits                -}-wordLineWrap w wNext img+lineWrap w img+  | imageWidth img <= w = [img] -- could be empty+  | otherwise           = lineWrap' w img+++lineWrap' ::+  Int      {- ^ first line length     -} ->+  Image'   {- ^ image                 -} ->+  [Image'] {- ^ splits                -}+lineWrap' w img   | imgW == 0 = []   | imgW <= w = [img]-  | otherwise = l : wordLineWrap wNext wNext (dropSpaces r)+  | otherwise = l : lineWrap' w (dropSpaces r)   where     imgW = imageWidth img     x:xs = splitOptions img@@ -78,7 +92,7 @@      go y [] = min y w     go y (z:zs)-      | z-y > wNext = w+      | z-y > w = w       | z > w = y       | otherwise = go z zs 
src/Client/Image/Message.hs view
@@ -22,9 +22,11 @@   , coloredIdentifier   , cleanText   , cleanChar-  , rightPad+  , nickPad+  , timeImage   ) where +import           Client.Configuration (PaddingMode(..)) import           Client.Image.MircFormatting import           Client.Image.PackedImage import           Client.Image.Palette@@ -55,7 +57,6 @@   , rendNicks      :: HashSet Identifier -- ^ nicknames to highlight   , rendMyNicks    :: HashSet Identifier -- ^ nicknames to highlight in red   , rendPalette    :: Palette -- ^ nick color palette-  , rendNickPadding :: Maybe Integer -- ^ nick padding   }  -- | Default 'MessageRendererParams' with no sigils or nicknames specified@@ -66,132 +67,255 @@   , rendNicks       = HashSet.empty   , rendMyNicks     = HashSet.empty   , rendPalette     = defaultPalette-  , rendNickPadding = Nothing   } + -- | Construct a message given the time the message was received and its -- render parameters. msgImage ::-  RenderMode ->-  ZonedTime {- ^ time of message -} ->-  MessageRendererParams -> MessageBody -> Image'-msgImage rm when params body = mconcat-  [ renderTime rm (rendPalette params) when-  , statusMsgImage (rendStatusMsg params)-  , bodyImage rm params body-  ]+  ZonedTime                {- ^ time of message     -} ->+  MessageRendererParams    {- render parameters     -} ->+  MessageBody              {- ^ message body        -} ->+  (Image', Image', Image') {- ^ prefix, image, full -}+msgImage when params body = (prefix, image, full)+  where+    si = statusMsgImage (rendStatusMsg params) +    prefix = si <> prefixImage params body++    image = bodyImage NormalRender params body++    full =+      mconcat+       [ datetimeImage (rendPalette params) when+       , statusMsgImage (rendStatusMsg params)+       , bodyImage DetailedRender params body+       ]+ cleanChar :: Char -> Char cleanChar x-  | x < '\x20'  = chr (0x2400 + ord x)-  | x == '\DEL' = '␡'-  | isControl x = '�'+  | x < '\x20'  = chr (0x2400 + ord x) -- ␀ .. ␙+  | x == '\DEL' = '\x2421' -- ␡+  | isControl x = '\xfffd' -- �   | otherwise   = x  cleanText :: Text -> Text cleanText = Text.map cleanChar -errorImage ::+errorPrefix ::   MessageRendererParams ->-  Text {- ^ error message -} ->   Image'-errorImage params txt = mconcat-  [ text' (view palError (rendPalette params)) "error "-  , text' defAttr (cleanText txt)-  ]+errorPrefix params =+  text' (view palError (rendPalette params)) "error" <>+  char defAttr ':' -normalImage ::-  MessageRendererParams ->-  Text {- ^ message -} ->-  Image'-normalImage params txt = mconcat-  [ text' (view palLabel (rendPalette params)) "client "-  , text' defAttr (cleanText txt)-  ] --- | Render the given time according to the current mode and palette.-renderTime :: RenderMode -> Palette -> ZonedTime -> Image'-renderTime DetailedRender = datetimeImage-renderTime NormalRender   = timeImage+normalPrefix :: MessageRendererParams -> Image'+normalPrefix params =+  text' (view palLabel (rendPalette params)) "client" <>+  char defAttr ':' + -- | Render the sigils for a restricted message. statusMsgImage :: [Char] {- ^ sigils -} -> Image' statusMsgImage modes   | null modes = mempty-  | otherwise  = string defAttr "(" <>-                 string statusMsgColor modes <>-                 string defAttr ") "+  | otherwise  = "(" <> string statusMsgColor modes <> ") "   where     statusMsgColor = withForeColor defAttr red + -- | Render a 'MessageBody' given the sender's sigils and the nicknames to -- highlight.+prefixImage ::+  MessageRendererParams ->+  MessageBody -> Image'+prefixImage params body =+  case body of+    IrcBody irc  -> ircLinePrefix params irc+    ErrorBody{}  -> errorPrefix  params+    NormalBody{} -> normalPrefix params++-- | Render a 'MessageBody' given the sender's sigils and the nicknames to+-- highlight. bodyImage ::   RenderMode ->   MessageRendererParams ->   MessageBody -> Image' bodyImage rm params body =   case body of-    IrcBody    irc -> ircLineImage rm params irc-    ErrorBody  txt -> errorImage params txt-    NormalBody txt -> normalImage params txt+    IrcBody irc | NormalRender   <- rm -> ircLineImage     params irc+                | DetailedRender <- rm -> fullIrcLineImage params irc+    ErrorBody  txt                     -> text' defAttr (cleanText txt)+    NormalBody txt                     -> text' defAttr (cleanText txt)  -- | Render a 'ZonedTime' as time using quiet attributes -- -- @ -- 23:15 -- @-timeImage :: Palette -> ZonedTime -> Image'+timeImage :: Palette -> TimeOfDay -> Image' timeImage palette   = string (view palTime palette)   . formatTime defaultTimeLocale "%R " --- | Render a 'ZonedTime' as full date and time user quiet attributes+-- | Render a 'ZonedTime' as full date and time user quiet attributes.+-- Excludes the year. -- -- @--- 2016-07-24 23:15:10+-- 07-24 23:15:10 -- @ datetimeImage :: Palette -> ZonedTime -> Image' datetimeImage palette   = string (view palTime palette)-  . formatTime defaultTimeLocale "%F %T "+  . formatTime defaultTimeLocale "%m-%d %T "  -- | Level of detail to use when rendering data RenderMode   = NormalRender -- ^ only render nicknames   | DetailedRender -- ^ render full user info --- | Optionally insert padding on the right of an 'Image' until it has--- the minimum width.-rightPad :: RenderMode -> Maybe Integer -> Image' -> Image'-rightPad NormalRender (Just minWidth) i =-  let w = max 0 (fromIntegral minWidth - imageWidth i)-  in i <> string defAttr (replicate w ' ')-rightPad _ _ i = i+-- | Optionally add padding to an input image according to the+-- specified mode. If the input image is already wider than+-- the specified padding mode, the image is returned unmodified.+nickPad ::+  PaddingMode {- ^ padding mode -} ->+  Image'      {- ^ input image  -} ->+  Image'      {- ^ padded image -}+nickPad mode img =+  case mode of+    LeftPadding  w | w > iw -> mkpad (w-iw) <> img+    RightPadding w | w > iw -> img <> mkpad (w-iw)+    _                       -> img+  where+    iw = imageWidth img+    mkpad n = string defAttr (replicate n ' ') ++-- | Render the sender of a message in normal mode.+-- This is typically something like @\@nickname:@+ircLinePrefix ::+  MessageRendererParams ->+  IrcMsg -> Image'+ircLinePrefix !rp body =+  let pal     = rendPalette rp+      sigils  = rendUserSigils rp+      myNicks = rendMyNicks rp+      rm      = NormalRender++      who n   = string (view palSigil pal) sigils <>+                coloredUserInfo pal rm myNicks n+  in+  case body of+    Join       {} -> mempty+    Part       {} -> mempty+    Quit       {} -> mempty+    Ping       {} -> mempty+    Pong       {} -> mempty+    Nick       {} -> mempty++    Topic src _ _ ->+      who src <> " changed the topic:"++    Kick kicker _channel kickee _reason ->+      who kicker <>+      " kicked " <>+      coloredIdentifier pal NormalIdentifier myNicks kickee <>+      ":"++    Notice src _ _ ->+      who src <>+      string (withForeColor defAttr red) ":"++    Privmsg src _ _ -> who src <> ":"++    Ctcp src _dst "ACTION" _txt ->+      string (withForeColor defAttr blue) "* " <> who src+    Ctcp {} -> mempty++    CtcpNotice src _dst "ACTION" _txt ->+      string (withForeColor defAttr red) "* " <> who src+    CtcpNotice {} -> mempty++    Error {} -> string (view palError pal) "ERROR" <> ":"++    Reply code _ -> replyCodePrefix code++    UnknownMsg irc ->+      case view msgPrefix irc of+        Just ui -> who ui+        Nothing -> string (view palError pal) "?"++    Cap cmd _ ->+      text' (withForeColor defAttr magenta) (renderCapCmd cmd) <> ":"++    Mode nick _ _ -> who nick <> " set mode:"++    Authenticate{} -> "AUTHENTICATE"+    BatchStart{}   -> mempty+    BatchEnd{}     -> mempty++ -- | Render a chat message given a rendering mode, the sigils of the user -- who sent the message, and a list of nicknames to highlight. ircLineImage ::-  RenderMode ->   MessageRendererParams ->   IrcMsg -> Image'-ircLineImage rm !rp body =+ircLineImage !rp body =+  let pal     = rendPalette rp+      myNicks = rendMyNicks rp+      nicks   = rendNicks rp+  in+  case body of+    Join        {} -> mempty+    Part        {} -> mempty+    Quit        {} -> mempty+    Ping        {} -> mempty+    Pong        {} -> mempty+    BatchStart  {} -> mempty+    BatchEnd    {} -> mempty+    Nick        {} -> mempty+    Authenticate{} -> "***"++    Error                   txt -> parseIrcText txt+    Topic      _ _          txt -> parseIrcTextWithNicks pal myNicks nicks txt+    Kick       _ _ _        txt -> parseIrcTextWithNicks pal myNicks nicks txt+    Notice     _ _          txt -> parseIrcTextWithNicks pal myNicks nicks txt+    Privmsg    _ _          txt -> parseIrcTextWithNicks pal myNicks nicks txt+    Ctcp       _ _ "ACTION" txt -> parseIrcTextWithNicks pal myNicks nicks txt+    Ctcp {}                     -> mempty+    CtcpNotice _ _ "ACTION" txt -> parseIrcTextWithNicks pal myNicks nicks txt+    CtcpNotice {}               -> mempty++    Reply code params -> renderReplyCode NormalRender code params+    UnknownMsg irc    ->+      text' defAttr (view msgCommand irc) <>+      char defAttr ' ' <>+      separatedParams (view msgParams irc)+    Cap _ args        -> separatedParams args+    Mode _ _ params   -> ircWords params++-- | Render a chat message given a rendering mode, the sigils of the user+-- who sent the message, and a list of nicknames to highlight.+fullIrcLineImage ::+  MessageRendererParams ->+  IrcMsg -> Image'+fullIrcLineImage !rp body =   let quietAttr = view palMeta pal       pal     = rendPalette rp       sigils  = rendUserSigils rp       myNicks = rendMyNicks rp       nicks   = rendNicks rp-      detail img =-        case rm of-          NormalRender   -> mempty-          DetailedRender -> img+      rm      = DetailedRender++      who n = string (view palSigil pal) sigils <>+              coloredUserInfo pal rm myNicks n   in   case body of     Nick old new ->-      detail (string quietAttr "nick ") <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks old <>-      string defAttr " is now known as " <>+      string quietAttr "nick " <>+      who old <>+      " is now known as " <>       coloredIdentifier pal NormalIdentifier myNicks new      Join nick _chan ->@@ -213,84 +337,70 @@                           string quietAttr ")") mbreason      Kick kicker _channel kickee reason ->-      detail (string quietAttr "kick ") <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks kicker <>-      string defAttr " kicked " <>+      string quietAttr "kick " <>+      who kicker <>+      " kicked " <>       coloredIdentifier pal NormalIdentifier myNicks kickee <>-      string defAttr ": " <>+      ": " <>       parseIrcText reason      Topic src _dst txt ->-      detail (string quietAttr "tpic ") <>+      string quietAttr "tpic " <>       coloredUserInfo pal rm myNicks src <>-      string defAttr " changed the topic to: " <>+      " changed the topic: " <>       parseIrcText txt      Notice src _dst txt ->-      detail (string quietAttr "note ") <>-      rightPad rm (rendNickPadding rp)-        (string (view palSigil pal) sigils <>-         coloredUserInfo pal rm myNicks src) <>+      string quietAttr "note " <>+      who src <>       string (withForeColor defAttr red) ": " <>       parseIrcTextWithNicks pal myNicks nicks txt      Privmsg src _dst txt ->-      detail (string quietAttr "chat ") <>-      rightPad rm (rendNickPadding rp)-        (string (view palSigil pal) sigils <>-         coloredUserInfo pal rm myNicks src) <>-      string defAttr ": " <>+      string quietAttr "chat " <>+      who src <> ": " <>       parseIrcTextWithNicks pal myNicks nicks txt      Ctcp src _dst "ACTION" txt ->-      detail (string quietAttr "actp ") <>+      string quietAttr "actp " <>       string (withForeColor defAttr blue) "* " <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks src <>-      string defAttr " " <>+      who src <> " " <>       parseIrcTextWithNicks pal myNicks nicks txt      CtcpNotice src _dst "ACTION" txt ->-      detail (string quietAttr "actn ") <>+      string quietAttr "actn " <>       string (withForeColor defAttr red) "* " <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks src <>-      string defAttr " " <>+      who src <> " " <>       parseIrcTextWithNicks pal myNicks nicks txt      Ctcp src _dst cmd txt ->-      detail (string quietAttr "ctcp ") <>+      string quietAttr "ctcp " <>       string (withForeColor defAttr blue) "! " <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks src <>-      string defAttr " " <>+      who src <> " " <>       parseIrcText cmd <>       separatorImage <>       parseIrcText txt      CtcpNotice src _dst cmd txt ->-      detail (string quietAttr "ctcp ") <>+      string quietAttr "ctcp " <>       string (withForeColor defAttr red) "! " <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks src <>-      string defAttr " " <>+      who src <> " " <>       parseIrcText cmd <>       separatorImage <>       parseIrcText txt      Ping params ->-      string defAttr "PING " <> separatedParams params+      "PING " <> separatedParams params      Pong params ->-      string defAttr "PONG " <> separatedParams params+      "PONG " <> separatedParams params      Error reason ->       string (view palError pal) "ERROR " <>       parseIrcText reason      Reply code params ->-      renderReplyCode rm rp code params+      renderReplyCode DetailedRender code params      UnknownMsg irc ->       foldMap (\ui -> coloredUserInfo pal rm myNicks ui <> char defAttr ' ')@@ -305,15 +415,13 @@       separatedParams args      Mode nick _chan params ->-      detail (string quietAttr "mode ") <>-      string (view palSigil pal) sigils <>-      coloredUserInfo pal rm myNicks nick <>-      string defAttr " set mode: " <>+      string quietAttr "mode " <>+      who nick <> " set mode: " <>       ircWords params -    Authenticate{} -> string defAttr "AUTHENTICATE ***"-    BatchStart{}   -> string defAttr "BATCH +"-    BatchEnd{}     -> string defAttr "BATCH -"+    Authenticate{} -> "AUTHENTICATE ***"+    BatchStart{}   -> "BATCH +"+    BatchEnd{}     -> "BATCH -"   renderCapCmd :: CapCmd -> Text@@ -339,22 +447,31 @@ ircWords :: [Text] -> Image' ircWords = mconcat . intersperse (char defAttr ' ') . map parseIrcText -renderReplyCode :: RenderMode -> MessageRendererParams -> ReplyCode -> [Text] -> Image'-renderReplyCode rm rp code@(ReplyCode w) params =+replyCodePrefix :: ReplyCode -> Image'+replyCodePrefix code =+  text' attr (replyCodeText info) <>+  char defAttr ':'+  where+    info = replyCodeInfo code++    color = case replyCodeType info of+              ClientServerReply -> magenta+              CommandReply      -> green+              ErrorReply        -> red+              UnknownReply      -> yellow++    attr = withForeColor defAttr color++renderReplyCode :: RenderMode -> ReplyCode -> [Text] -> Image'+renderReplyCode rm code@(ReplyCode w) params =   case rm of-    DetailedRender -> string attr (show w) <> rawParamsImage+    DetailedRender -> string attr (shows w " ") <> rawParamsImage     NormalRender   ->-      rightPad rm (rendNickPadding rp)-        (text' attr (replyCodeText info)) <>-      char defAttr ':' <>-       case code of         RPL_WHOISIDLE -> whoisIdleParamsImage         _             -> rawParamsImage   where-    rawParamsImage =-      char defAttr ' ' <>-      separatedParams params'+    rawParamsImage = separatedParams params'      params' = case rm of                 DetailedRender -> params@@ -373,7 +490,6 @@     whoisIdleParamsImage =       case params' of         [name, idle, signon, _txt] ->-          char defAttr ' ' <>           text' defAttr name <>           text' defAttr " idle: " <>           string defAttr (prettySeconds (Text.unpack idle)) <>
src/Client/Image/PackedImage.hs view
@@ -20,6 +20,7 @@   , imageWidth   , splitImage   , imageText+  , resizeImage   ) where  import           Data.List (findIndex)@@ -42,7 +43,7 @@ -- | Packed, strict version of 'Image' used for long-term storage of images. data Image'   = HorizText'-      Attr -- don't unpack, these get reused from the palette+      !Attr -- don't unpack, these get reused from the palette       {-# UNPACK #-} !S.Text       {-# UNPACK #-} !Int       {-# UNPACK #-} !Int@@ -101,3 +102,11 @@   where     go EmptyImage' = []     go (HorizText' _ t _ _ xs) = t : go xs++resizeImage :: Int -> Image' -> Image'+resizeImage w img =+  let iw = imageWidth img in+  case compare w iw of+    LT -> fst (splitImage w img)+    EQ -> img+    GT -> img <> string defAttr (replicate (w-iw) ' ')
src/Client/Image/StatusLine.hs view
@@ -17,6 +17,7 @@   ) where  import           Client.Image.Message (cleanText)+import           Client.Image.PackedImage import           Client.Image.Palette import           Client.State import           Client.State.Channel@@ -26,10 +27,11 @@ import           Control.Lens import qualified Data.Map.Strict as Map import           Data.Maybe+import           Data.Semigroup import           Data.Text (Text) import qualified Data.Text as Text import           Graphics.Vty.Attributes-import           Graphics.Vty.Image+import qualified Graphics.Vty.Image as Vty import           Irc.Identifier (Identifier, idText) import           Numeric @@ -40,13 +42,14 @@ statusLineImage ::   Int         {- ^ draw width   -} ->   ClientState {- ^ client state -} ->-  Image       {- ^ status bar   -}+  Vty.Image   {- ^ status bar   -} statusLineImage w st =   makeLines w (common : activity ++ errorImgs)   where-    common = horizCat-      [ myNickImage st-      , focusImage (view clientFocus st) st+    common = Vty.horizCat $+      myNickImage st :+      map unpackImage+      [ focusImage (view clientFocus st) st       , subfocusImage st       , detailImage st       , nometaImage (view clientFocus st) st@@ -66,12 +69,12 @@ -- Generates an error message notification image. transientErrorImage ::   Text  {- ^ @error-message@           -} ->-  Image {- ^ @─[error: error-message]@ -}+  Vty.Image {- ^ @─[error: error-message]@ -} transientErrorImage txt =-  text' defAttr "─[" <|>-  text' (withForeColor defAttr red) "error: " <|>-  text' defAttr (cleanText txt) <|>-  text' defAttr "]"+  Vty.text' defAttr "─[" Vty.<|>+  Vty.text' (withForeColor defAttr red) "error: " Vty.<|>+  Vty.text' defAttr (cleanText txt) Vty.<|>+  Vty.text' defAttr "]"   -- | The minor status line is used when rendering the @/splits@ and@@ -80,20 +83,21 @@   Focus {- ^ window name          -} ->   Int   {- ^ draw width           -} ->   Bool  {- ^ show hidemeta status -} ->-  ClientState -> Image+  ClientState {- ^ client state -} ->+  Image' minorStatusLineImage focus w showHideMeta st =-  content <|> charFill defAttr bar fillSize 1+  content <> string defAttr (replicate fillSize bar)   where-    content = focusImage focus st <|>+    content = focusImage focus st <>               if showHideMeta then nometaImage focus st else mempty      fillSize = max 0 (w - imageWidth content)   -- | Indicate when the client is scrolling and old messages are being shown.-scrollImage :: ClientState -> Image+scrollImage :: ClientState -> Image' scrollImage st-  | 0 == view clientScroll st = emptyImage+  | 0 == view clientScroll st = mempty   | otherwise = infoBubble (string attr "scroll")   where     pal  = clientPalette st@@ -102,10 +106,10 @@  -- | Indicate when the client is potentially showing a subset of the -- available chat messages.-filterImage :: ClientState -> Image+filterImage :: ClientState -> Image' filterImage st =   case clientActiveRegex st of-    Nothing -> emptyImage+    Nothing -> mempty     Just {} -> infoBubble (string attr "filtered")   where     pal  = clientPalette st@@ -115,42 +119,42 @@ -- | Indicate the current connection health. This will either indicate -- that the connection is being established or that a ping has been -- sent or long the previous ping round-trip was.-latencyImage :: ClientState -> Image+latencyImage :: ClientState -> Image' latencyImage st =   case views clientFocus focusNetwork st of-    Nothing      -> emptyImage+    Nothing      -> mempty     Just network ->       case preview (clientConnection network) st of         Nothing -> infoBubble (string (view palError pal) "offline")         Just cs ->           case view csPingStatus cs of-            PingNever          -> emptyImage+            PingNever          -> mempty             PingSent {}        -> latency "ping sent"             PingLatency delta  -> latency (showFFloat (Just 2) delta "s")             PingConnecting n _ ->-              infoBubble (string (view palLatency pal) "connecting" <|>+              infoBubble (string (view palLatency pal) "connecting" <>                           retryImage n)   where     pal     = clientPalette st     latency = infoBubble . string (view palLatency pal)      retryImage n-      | n > 0     = string defAttr ": " <|>-                    string (view palLabel pal) ("retry " ++ show n)-      | otherwise = emptyImage+      | n > 0     = ": " <> string (view palLabel pal) ("retry " ++ show n)+      | otherwise = mempty   -- | Wrap some text in parentheses to make it suitable for inclusion in the -- status line.-infoBubble :: Image -> Image-infoBubble img = string defAttr (bar:"(") <|> img <|> string defAttr ")"+infoBubble :: Image' -> Image'+infoBubble img =+  string defAttr (bar:"(") <> img <> ")"   -- | Indicate that the client is in the /detailed/ view.-detailImage :: ClientState -> Image+detailImage :: ClientState -> Image' detailImage st   | view clientDetailView st = infoBubble (string attr "detail")-  | otherwise = emptyImage+  | otherwise = mempty   where     pal  = clientPalette st     attr = view palLabel pal@@ -158,10 +162,10 @@  -- | Indicate that the client isn't showing the metadata lines in /normal/ -- view.-nometaImage :: Focus -> ClientState -> Image+nometaImage :: Focus -> ClientState -> Image' nometaImage focus st   | metaHidden = infoBubble (string attr "nometa")-  | otherwise  = emptyImage+  | otherwise  = mempty   where     pal        = clientPalette st     attr       = view palLabel pal@@ -170,12 +174,12 @@ -- | Image for little box with active window names: -- -- @-[15p]@-activitySummary :: ClientState -> Image+activitySummary :: ClientState -> Vty.Image activitySummary st-  | null indicators = emptyImage-  | otherwise       = string defAttr (bar:"[") <|>-                      horizCat indicators <|>-                      string defAttr "]"+  | null indicators = Vty.emptyImage+  | otherwise       = Vty.string defAttr (bar:"[") Vty.<|>+                      Vty.horizCat indicators Vty.<|>+                      Vty.string defAttr "]"   where     winNames = clientWindowNames st ++ repeat '?' @@ -184,14 +188,14 @@      aux (i,w) rest =       case view winMention w of-        WLImportant -> char (view palMention  pal) i : rest-        WLNormal    -> char (view palActivity pal) i : rest+        WLImportant -> Vty.char (view palMention  pal) i : rest+        WLNormal    -> Vty.char (view palActivity pal) i : rest         WLBoring    -> rest       where         pal = clientPalette st  -- | Multi-line activity information enabled by F3-activityBarImages :: ClientState -> [Image]+activityBarImages :: ClientState -> [Vty.Image] activityBarImages st   = catMaybes   $ zipWith baraux winNames@@ -205,13 +209,13 @@     baraux i (focus,w)       | n == 0 = Nothing -- todo: make configurable       | otherwise = Just-                  $ string defAttr (bar:"[") <|>-                    char (view palWindowName pal) i <|>-                    char defAttr              ':' <|>-                    text' (view palLabel pal) focusText <|>-                    char defAttr              ':' <|>-                    string attr               (show n) <|>-                    string defAttr "]"+                  $ Vty.string defAttr (bar:"[") Vty.<|>+                    Vty.char (view palWindowName pal) i Vty.<|>+                    Vty.char defAttr ':' Vty.<|>+                    Vty.text' (view palLabel pal) focusText Vty.<|>+                    Vty.char defAttr ':' Vty.<|>+                    Vty.string attr (show n) Vty.<|>+                    Vty.string defAttr "]"       where         n   = view winUnread w         pal = clientPalette st@@ -232,35 +236,35 @@ -- cases. makeLines ::   Int     {- ^ window width       -} ->-  [Image] {- ^ components to pack -} ->-  Image-makeLines _ [] = emptyImage+  [Vty.Image] {- ^ components to pack -} ->+  Vty.Image+makeLines _ [] = Vty.emptyImage makeLines w (x:xs) = go x xs   where      go acc (y:ys)-      | let acc' = acc <|> y-      , imageWidth acc' <= w+      | let acc' = acc Vty.<|> y+      , Vty.imageWidth acc' <= w       = go acc' ys      go acc ys = makeLines w ys-            <-> acc <|> charFill defAttr bar (max 0 (w - imageWidth acc)) 1+        Vty.<-> acc Vty.<|> Vty.charFill defAttr bar (max 0 (w - Vty.imageWidth acc)) 1  -myNickImage :: ClientState -> Image+myNickImage :: ClientState -> Vty.Image myNickImage st =   case view clientFocus st of     NetworkFocus network      -> nickPart network Nothing     ChannelFocus network chan -> nickPart network (Just chan)-    Unfocused                 -> emptyImage+    Unfocused                 -> Vty.emptyImage   where     pal = clientPalette st     nickPart network mbChan =       case preview (clientConnection network) st of-        Nothing -> emptyImage-        Just cs -> string (view palSigil pal) myChanModes-               <|> text' defAttr (idText nick)-               <|> parens defAttr (string defAttr ('+' : view csModes cs))+        Nothing -> Vty.emptyImage+        Just cs -> Vty.string (view palSigil pal) myChanModes+           Vty.<|> Vty.text' defAttr (idText nick)+           Vty.<|> parens defAttr (Vty.string defAttr ('+' : view csModes cs))           where             nick      = view csNick cs             myChanModes =@@ -269,14 +273,14 @@                 Just chan -> view (csChannels . ix chan . chanUsers . ix nick) cs  -subfocusImage :: ClientState -> Image+subfocusImage :: ClientState -> Image' subfocusImage st = foldMap infoBubble (viewSubfocusLabel pal subfocus)   where     pal         = clientPalette st     subfocus    = view clientSubfocus st -focusImage :: Focus -> ClientState -> Image-focusImage focus st = infoBubble $ horizCat+focusImage :: Focus -> ClientState -> Image'+focusImage focus st = infoBubble $ mconcat     [ char (view palWindowName pal) windowName     , char defAttr ':'     , viewFocusLabel st focus@@ -290,10 +294,10 @@                     preview (ix i) windowNames  -parens :: Attr -> Image -> Image-parens attr i = char attr '(' <|> i <|> char attr ')'+parens :: Attr -> Vty.Image -> Vty.Image+parens attr i = Vty.char attr '(' Vty.<|> i Vty.<|> Vty.char attr ')' -viewFocusLabel :: ClientState -> Focus -> Image+viewFocusLabel :: ClientState -> Focus -> Image' viewFocusLabel st focus =   let !pal = clientPalette st in   case focus of@@ -302,25 +306,25 @@     NetworkFocus network ->       text' (view palLabel pal) network     ChannelFocus network channel ->-      text' (view palLabel pal) network <|>-      char defAttr ':' <|>-      text' (view palLabel pal) (idText channel) <|>+      text' (view palLabel pal) network <>+      char defAttr ':' <>+      text' (view palLabel pal) (idText channel) <>       channelModesImage network channel st -channelModesImage :: Text -> Identifier -> ClientState -> Image+channelModesImage :: Text -> Identifier -> ClientState -> Image' channelModesImage network channel st =   case preview (clientConnection network . csChannels . ix channel . chanModes) st of     Just modeMap | not (null modeMap) ->-        string defAttr (" +" ++ modes) <|>-        horizCat [ char defAttr ' ' <|> text' defAttr arg | arg <- args, not (Text.null arg) ]+        string defAttr (" +" ++ modes) <>+        mconcat [ char defAttr ' ' <> text' defAttr arg | arg <- args, not (Text.null arg) ]       where (modes,args) = unzip (Map.toList modeMap)-    _ -> emptyImage+    _ -> mempty -viewSubfocusLabel :: Palette -> Subfocus -> Maybe Image+viewSubfocusLabel :: Palette -> Subfocus -> Maybe Image' viewSubfocusLabel pal subfocus =   case subfocus of     FocusMessages -> Nothing-    FocusWindows filt -> Just $ string (view palLabel pal) "windows" <|>+    FocusWindows filt -> Just $ string (view palLabel pal) "windows" <>                                 opt (windowFilterName filt)     FocusInfo     -> Just $ string (view palLabel pal) "info"     FocusUsers    -> Just $ string (view palLabel pal) "users"@@ -328,15 +332,16 @@     FocusPalette  -> Just $ string (view palLabel pal) "palette"     FocusDigraphs -> Just $ string (view palLabel pal) "digraphs"     FocusKeyMap   -> Just $ string (view palLabel pal) "keymap"-    FocusHelp mb  -> Just $ string (view palLabel pal) "help" <|>+    FocusHelp mb  -> Just $ string (view palLabel pal) "help" <>                             opt mb-    FocusMasks m  -> Just $ horizCat+    FocusRtsStats -> Just $ string (view palLabel pal) "rtsstats"+    FocusMasks m  -> Just $ mconcat       [ string (view palLabel pal) "masks"       , char defAttr ':'       , char (view palLabel pal) m       ]   where-    opt = foldMap (\cmd -> char defAttr ':' <|>+    opt = foldMap (\cmd -> char defAttr ':' <>                            text' (view palLabel pal) cmd)  windowFilterName :: WindowsFilter -> Maybe Text
src/Client/Log.hs view
@@ -16,15 +16,12 @@ import           Client.Message import           Control.Exception import           Control.Lens hiding ((<.>))-import qualified Data.ByteString as B import           Data.Monoid import           Data.Time import           Data.Text (Text) import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text import qualified Data.Text.Lazy as L import qualified Data.Text.Lazy.IO as L-import qualified Data.Vector.Primitive as PV import           Irc.Identifier import           Irc.Message import           Irc.UserInfo@@ -90,10 +87,6 @@     success txt = Just LogLine       { logBaseDir = dir       , logDay     = day-      , logTarget  = Text.toLower-                   $ Text.decodeUtf8-                   $ B.pack-                   $ PV.toList-                   $ idDenote target+      , logTarget  = Text.toLower (idTextNorm target)       , logLine    = L.fromChunks ["[", Text.pack todStr, "] "] <> txt <> "\n"       }
src/Client/State.hs view
@@ -42,6 +42,7 @@   , clientActivityReturn   , clientErrorMsg   , clientLayout+  , clientRtsStats    -- * Client operations   , withClientState@@ -49,6 +50,7 @@   , clientShutdown   , clientPark   , clientMatcher+  , clientMatcher'   , clientActiveRegex   , clientToggleHideMeta @@ -133,6 +135,7 @@ import qualified Data.Map as Map import           Data.Text (Text) import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText import           Data.Time import           Foreign.Ptr import           Foreign.StablePtr@@ -142,6 +145,7 @@ import           Irc.RawIrcMsg import           Irc.UserInfo import           LensUtils+import           RtsStats (Stats) import           Text.Regex.TDFA import           Text.Regex.TDFA.String (compile) @@ -179,6 +183,7 @@   , _clientExtensions        :: !ExtensionState           -- ^ state of loaded extensions   , _clientLogQueue          :: ![LogLine]                -- ^ log lines ready to write   , _clientErrorMsg          :: Maybe Text                -- ^ transient error box text+  , _clientRtsStats          :: Maybe Stats               -- ^ most recent GHC RTS stats   }  @@ -259,6 +264,7 @@         , _clientExtensions        = exts         , _clientLogQueue          = []         , _clientErrorMsg          = Nothing+        , _clientRtsStats          = Nothing         }  withExtensionState :: (ExtensionState -> IO a) -> IO a@@ -302,7 +308,6 @@       , rendNicks       = HashSet.fromList (channelUserList network channel' st)       , rendMyNicks     = highlights       , rendPalette     = clientPalette st-      , rendNickPadding = view (clientConfig . configNickPadding) st       }      -- on failure returns mempty/""@@ -514,22 +519,20 @@ toWindowLine :: MessageRendererParams -> WindowLineImportance -> ClientMessage -> WindowLine toWindowLine params importance msg = WindowLine   { _wlSummary    = msgSummary (view msgBody msg)-  , _wlText       = msgText (view msgBody msg)-  , _wlImage      = mkImage NormalRender-  , _wlFullImage  = mkImage DetailedRender+  , _wlPrefix     = prefix+  , _wlImage      = image+  , _wlFullImage  = full   , _wlImportance = importance-  , _wlTimestamp  = zonedTimeToUTC (view msgTime msg)+  , _wlTimestamp  = views msgTime packZonedTime msg   }   where-    mkImage mode =-      msgImage mode (view msgTime msg) params (view msgBody msg)+    (prefix, image, full) = msgImage (view msgTime msg) params (view msgBody msg)  -- | 'toWindowLine' but with mostly defaulted parameters. toWindowLine' :: Configuration -> WindowLineImportance -> ClientMessage -> WindowLine toWindowLine' config =   toWindowLine defaultRenderParams     { rendPalette     = view configPalette     config-    , rendNickPadding = view configNickPadding config     , rendMyNicks     = view configExtraHighlights config     } @@ -594,12 +597,20 @@ -- | Returns the current filtering predicate if one is active. clientMatcher ::   ClientState          {- ^ client state       -} ->-  Maybe (Text -> Bool) {- ^ optional predicate -}+  Maybe (LText.Text -> Bool) {- ^ optional predicate -} clientMatcher st =   do r <- clientActiveRegex st-     return (matchTest r . Text.unpack)+     return (matchTest r . LText.unpack) +-- | Strict version of 'clientMatcher'+clientMatcher' ::+  ClientState          {- ^ client state       -} ->+  Maybe (Text -> Bool) {- ^ optional predicate -}+clientMatcher' st =+  do p <- clientMatcher st+     return (p . LText.fromStrict) + -- | Construct a text matching predicate used to filter the message window. clientActiveRegex :: ClientState -> Maybe Regex clientActiveRegex st =@@ -640,11 +651,13 @@  -- | Find all the URL matches using 'urlPattern' in a given 'Text' suitable -- for being opened. Surrounding @<@ and @>@ are removed.-urlMatches :: Text -> [Text]+urlMatches :: LText.Text -> [Text] urlMatches txt = removeBrackets . extractText . (^?! ix 0)-             <$> matchAll urlPattern (Text.unpack txt)+             <$> matchAll urlPattern (LText.unpack txt)   where-    extractText (off,len) = Text.take len (Text.drop off txt)+    extractText (off,len) = LText.toStrict+                          $ LText.take (fromIntegral len)+                          $ LText.drop (fromIntegral off) txt      removeBrackets t =       case Text.uncons t of
src/Client/State/Channel.hs view
@@ -111,7 +111,7 @@  -- | Remove a user from the user list partChannel :: Identifier -> ChannelState -> ChannelState-partChannel nick = set (chanUsers . at nick) Nothing+partChannel = over chanUsers . sans  -- | Rename a user in the user list nickChange :: Identifier -> Identifier -> ChannelState -> ChannelState
src/Client/State/Focus.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE CPP, TemplateHaskell #-}  {-| Module      : Client.State.Focus@@ -53,6 +53,7 @@   | FocusDigraphs    -- ^ Show all digraphs   | FocusKeyMap      -- ^ Show key bindings   | FocusHelp (Maybe Text) -- ^ Show help window with optional command+  | FocusRtsStats    -- ^ Show GHC RTS statistics   deriving (Eq,Show)  -- | Unfocused first, followed by focuses sorted by network.
src/Client/State/Network.hs view
@@ -273,23 +273,16 @@          $ forgetUser (userNick user)          $ overChannels (partChannel (userNick user)) cs -    Part user chan _mbreason ->-           noReply-         $ forgetUser' (userNick user) -- possibly forget-         $ if userNick user == view csNick cs-             then over csChannels (sans chan) cs-             else overChannel chan (partChannel (userNick user)) cs+    Part user chan _mbreason -> exitChannel chan (userNick user) +    Kick _kicker chan nick _reason -> exitChannel chan nick+     Nick oldNick newNick ->            noReply          $ renameUser (userNick oldNick) newNick          $ updateMyNick (userNick oldNick) newNick          $ overChannels (nickChange (userNick oldNick) newNick) cs -    Kick _kicker chan nick _reason ->-           noReply-         $ forgetUser' nick-         $ overChannel chan (partChannel nick) cs     Reply RPL_WELCOME (me:_) -> doWelcome msgWhen (mkId me) cs     Reply RPL_SASLSUCCESS _ -> ([ircCapEnd], cs)     Reply RPL_SASLFAIL _ -> ([ircCapEnd], cs)@@ -301,6 +294,20 @@     Mode who target (modes:params)  -> doMode msgWhen who target modes params cs     Topic user chan topic  -> noReply (doTopic msgWhen user chan topic cs)     _                      -> noReply cs+  where+    exitChannel chan nick+      | nick == view csNick cs = noReply $ pruneUsers+                               $ over csChannels (sans chan) cs++      | otherwise              = noReply $ forgetUser' nick+                               $ overChannel chan (partChannel nick) cs++-- | Restrict 'csUsers' to only users are in a channel that the client+-- is connected to.+pruneUsers :: NetworkState -> NetworkState+pruneUsers cs = over csUsers (`HashMap.intersection` u) cs+  where+    u = foldOf (csChannels . folded . chanUsers) cs  -- | 001 'RPL_WELCOME' is the first message received when transitioning -- from the initial handshake to a connected state. At this point we know
src/Client/State/Window.hs view
@@ -26,6 +26,7 @@   , WindowLine(..)   , wlSummary   , wlText+  , wlPrefix   , wlImage   , wlFullImage   , wlImportance@@ -40,24 +41,39 @@   , windowSeen   , windowActivate   , windowDeactivate++    -- * Packed time+  , PackedTime+  , packZonedTime+  , unpackUTCTime+  , unpackTimeOfDay   ) where  import           Client.Image.PackedImage import           Client.Message import           Control.Lens-import           Data.Text (Text)-import           Data.Time (UTCTime)+import           Control.Monad ((<$!>))+import           Data.Text.Lazy (Text)+import           Data.Time+import           Data.Word+import           Data.Bits --- | A single message to be displayed in a window+-- | A single message to be displayed in a window.+-- The normal message line consists of the image prefix+-- and the image. This allows line wrapping to be applied+-- separately to the image and prefix so that wrapped+-- messages can fall to the right side of the prefix. data WindowLine = WindowLine   { _wlSummary    :: !IrcSummary  -- ^ Summary value-  , _wlText       :: {-# UNPACK #-} !Text -- ^ Searchable text form+  , _wlPrefix     :: !Image'      -- ^ Normal rendered image prefix   , _wlImage      :: !Image'      -- ^ Normal rendered image   , _wlFullImage  :: !Image'      -- ^ Detailed rendered image   , _wlImportance :: !WindowLineImportance -- ^ Importance of message-  , _wlTimestamp  :: {-# UNPACK #-} !UTCTime+  , _wlTimestamp  :: {-# UNPACK #-} !PackedTime   } +newtype PackedTime = PackedTime Word64+ data WindowLines   = {-# UNPACK #-} !WindowLine :- WindowLines   | Nil@@ -87,6 +103,9 @@ makeLenses ''WindowLine  +wlText :: Getter WindowLine Text+wlText = wlFullImage . to imageText+ -- | A window with no messages emptyWindow :: Window emptyWindow = Window@@ -104,7 +123,7 @@ addToWindow !msg !win = Window     { _winMessages = msg :- view winMessages win     , _winTotal    = view winTotal win + 1-    , _winMarker   = do i <- view winMarker win; return $! i+1+    , _winMarker   = (+1) <$!> view winMarker win     , _winUnread   = if view wlImportance msg == WLBoring                      then view winUnread win                      else view winUnread win + 1@@ -135,3 +154,52 @@ instance Each WindowLines WindowLines WindowLine WindowLine where   each _ Nil = pure Nil   each f (x :- xs) = (:-) <$> f x <*> each f xs++------------------------------------------------------------------------++-- Field   Range   Bits Start+-- year:     0..   33     31+-- month:    1..12 4      27+-- day:      1..31 5      22+-- hour:     0..23 5      17+-- minute:   0..60 6      11+-- second:   0..61 6       5+-- offset: -12..14 5       0++field :: Num a => PackedTime -> Int -> Int -> a+field (PackedTime x) off sz = fromIntegral ((x `shiftR` off) .&. (2^sz-1))+{-# INLINE field #-}++packField :: Int -> Int -> Word64+packField off val = fromIntegral val `shiftL` off++packZonedTime :: ZonedTime -> PackedTime+packZonedTime (ZonedTime (LocalTime (ModifiedJulianDay d) (TimeOfDay h m s)) z)+  = PackedTime+  $ packField 17 h .|.+    packField 11 m .|.+    packField  5 (floor s) .|.+    packField 22 (fromInteger d) .|.+    packField  0 (timeZoneMinutes z `div` 60 + 12)++unpackTimeOfDay :: PackedTime -> TimeOfDay+unpackTimeOfDay !x = TimeOfDay h m s+  where+    h = field x 17 5+    m = field x 11 6+    s = field x  5 6++unpackLocalTime :: PackedTime -> LocalTime+unpackLocalTime !x = LocalTime d t+  where+    d = ModifiedJulianDay (field x 22 42)+    t = unpackTimeOfDay x++unpackUTCTime :: PackedTime -> UTCTime+unpackUTCTime = zonedTimeToUTC . unpackZonedTime++unpackZonedTime :: PackedTime -> ZonedTime+unpackZonedTime !x = ZonedTime t z+  where+    z = minutesToTimeZone ((field x 0 5 - 12) * 60)+    t = unpackLocalTime x
src/Client/View.hs view
@@ -13,6 +13,7 @@   ( viewLines   ) where +import           Client.Image.PackedImage import           Client.State import           Client.State.Focus import           Client.View.ChannelInfo@@ -23,17 +24,17 @@ import           Client.View.Mentions import           Client.View.Messages import           Client.View.Palette+import           Client.View.RtsStats import           Client.View.UrlSelection import           Client.View.UserList import           Client.View.Windows import           Control.Lens-import           Graphics.Vty.Image -viewLines :: Focus -> Subfocus -> Int -> ClientState -> [Image]+viewLines :: Focus -> Subfocus -> Int -> ClientState -> [Image'] viewLines focus subfocus w !st =   case (focus, subfocus) of     _ | Just ("url",arg) <- clientActiveCommand st ->-      urlSelectionView focus arg st+      urlSelectionView w focus arg st     (ChannelFocus network channel, FocusInfo) ->       channelInfoImages network channel st     (ChannelFocus network channel, FocusUsers)@@ -47,6 +48,7 @@     (_, FocusDigraphs) -> digraphLines w st     (_, FocusKeyMap) -> keyMapLines st     (_, FocusHelp mb) -> helpImageLines mb pal+    (_, FocusRtsStats) -> rtsStatsLines (view clientRtsStats st) pal     _ -> chatMessageImages focus w st   where     pal = clientPalette st
src/Client/View/ChannelInfo.hs view
@@ -18,24 +18,24 @@  import           Client.Image.Message import           Client.Image.MircFormatting-import           Client.Image.PackedImage (unpackImage)+import           Client.Image.PackedImage import           Client.Image.Palette import           Client.State import           Client.State.Channel import           Client.State.Network import           Control.Lens import           Data.HashSet (HashSet)+import           Data.Semigroup import           Data.Text (Text) import           Data.Time import           Graphics.Vty.Attributes-import           Graphics.Vty.Image import           Irc.Identifier  -- | Render the lines used in a channel mask list channelInfoImages ::   Text        {- ^ network -} ->   Identifier  {- ^ channel -} ->-  ClientState -> [Image]+  ClientState -> [Image'] channelInfoImages network channelId st    | Just cs      <- preview (clientConnection network) st@@ -46,7 +46,7 @@   where     pal = clientPalette st -channelInfoImages' :: Palette -> HashSet Identifier -> ChannelState -> [Image]+channelInfoImages' :: Palette -> HashSet Identifier -> ChannelState -> [Image'] channelInfoImages' pal myNicks !channel     = topicLine     : provenanceLines@@ -56,8 +56,8 @@   where     label = text' (view palLabel pal) -    topicLine = label "Topic: " <|>-                unpackImage (parseIrcText (view chanTopic channel))+    topicLine = label "Topic: " <>+                parseIrcText (view chanTopic channel)       utcTimeImage = string defAttr . formatTime defaultTimeLocale "%F %T"@@ -66,20 +66,18 @@         case view chanTopicProvenance channel of           Nothing -> []           Just !prov ->-            [ label "Topic set by: " <|>-                unpackImage-                  (coloredUserInfo-                    pal DetailedRender myNicks (view topicAuthor prov))-            , label "Topic set on: " <|> utcTimeImage (view topicTime prov)+            [ label "Topic set by: " <>+                coloredUserInfo pal DetailedRender myNicks (view topicAuthor prov)+            , label "Topic set on: " <> utcTimeImage (view topicTime prov)             ]      creationLines =         case view chanCreation channel of           Nothing   -> []-          Just time -> [label "Created on: " <|> utcTimeImage time]+          Just time -> [label "Created on: " <> utcTimeImage time]      urlLines =         case view chanUrl channel of           Nothing -> []-          Just url -> [ label "Channel URL: " <|> unpackImage (parseIrcText url) ]+          Just url -> [ label "Channel URL: " <> parseIrcText url ] 
src/Client/View/Digraphs.hs view
@@ -12,28 +12,29 @@ module Client.View.Digraphs (digraphLines) where  import           Client.Image.Message (cleanChar)+import           Client.Image.PackedImage import           Client.State import           Data.List import           Data.List.Split import qualified Data.Text as Text import           Digraphs import           Graphics.Vty.Attributes-import           Graphics.Vty.Image+import           Graphics.Vty.Image (wcwidth, wcswidth)  -- | Render the lines of a table showing all of the available digraph entries digraphLines ::   Int         {- ^ draw width   -} ->   ClientState {- ^ client state -} ->-  [Image]     {- ^ output lines -}+  [Image']    {- ^ output lines -} digraphLines w st-  = map (horizCat . intersperse sep)+  = map (mconcat . intersperse sep)   $ chunksOf entriesPerLine   $ map (text' defAttr)   $ matcher   $ map (Text.pack . drawEntry)   $ digraphListToList digraphs   where-    matcher        = maybe id filter (clientMatcher st)+    matcher        = maybe id filter (clientMatcher' st)     entriesPerLine = max 1 -- just in case?                    $ (w + sepWidth) `quot` (entryWidth + sepWidth) @@ -43,7 +44,7 @@ sepWidth :: Int sepWidth = imageWidth sep -sep :: Image+sep :: Image' sep = text' defAttr "   "  drawEntry :: (Char,Char,Char) -> String
src/Client/View/Help.hs view
@@ -29,7 +29,6 @@ import           Data.Text (Text) import qualified Data.Text as Text import           Graphics.Vty.Attributes-import           Graphics.Vty.Image (Image)  -- | Generate either the list of all commands and their arguments, -- or when given a command name generate the detailed help text@@ -37,8 +36,8 @@ helpImageLines ::   Maybe Text {- ^ optional command name -} ->   Palette    {- ^ palette               -} ->-  [Image]    {- ^ help lines            -}-helpImageLines mbCmd pal = map unpackImage $+  [Image']   {- ^ help lines            -}+helpImageLines mbCmd pal =   case mbCmd of     Nothing  -> listAllCommands pal     Just cmd -> commandHelpLines cmd pal@@ -57,7 +56,7 @@       suggestions = Text.unpack $ Text.intercalate " " ((cmdName <>) <$> sfxs)     Exact Command{cmdNames = names, cmdImplementation = impl,                   cmdArgumentSpec = spec, cmdDocumentation = doc} ->-      reverse $ commandSummary pal (pure cmdName) spec+      reverse $ heading "Syntax: " <> commandSummary pal (pure cmdName) spec               : emptyLine               : aliasLines              ++ explainContext impl@@ -67,24 +66,27 @@         aliasLines =           case delete cmdName (toList names) of             [] -> []-            ns -> [ text' defAttr (Text.unwords ("Aliases:":ns))+            ns -> [ heading "Aliases: " <>+                    text' defAttr (Text.intercalate ", " ns)                   , emptyLine ] +heading :: Text -> Image'+heading = text' (withStyle defAttr bold)+ -- | Generate an explanation of the context where the given command -- implementation will be valid. explainContext ::   CommandImpl a {- ^ command implementation -} ->   Image'        {- ^ help line              -} explainContext impl =+  heading "Context: " <>   case impl of-    ClientCommand {} -> go "client command" "works everywhere"-    NetworkCommand{} -> go "network command" "works when focused on active network"-    ChannelCommand{} -> go "channel command" "works when focused on active channel"-    ChatCommand   {} -> go "chat command" "works when focused on an active channel or private message"-  where-    go x y = string (withStyle defAttr bold) x <>-             string defAttr (": " ++ y)+    ClientCommand {} -> "client (works everywhere)"+    NetworkCommand{} -> "network (works when focused on active network)"+    ChannelCommand{} -> "channel (works when focused on active channel)"+    ChatCommand   {} -> "chat (works when focused on an active channel or private message)" + -- | Generate the lines for the help window showing all commands. listAllCommands ::   Palette  {- ^ palette    -} ->@@ -116,7 +118,7 @@   Image'         {- ^ summary help line        -} commandSummary pal (cmd :| _) args  =   char defAttr '/' <>-  text' (view palCommand pal) cmd <>+  text' (view palCommandReady pal) cmd <>   argumentsImage pal' args ""    where
src/Client/View/KeyMap.hs view
@@ -13,26 +13,27 @@  import           Client.Configuration import           Client.EventLoop.Actions+import           Client.Image.PackedImage import           Client.State import           Control.Lens import           Data.List+import           Data.Semigroup import           Data.Ord import           Graphics.Vty.Attributes-import           Graphics.Vty.Image import           Graphics.Vty.Input  -- | Render the lines of a table showing all of the available digraph entries keyMapLines ::   ClientState {- ^ client state -} ->-  [Image]     {- ^ output lines -}+  [Image']    {- ^ output lines -} keyMapLines   = renderEntries   . keyMapEntries   . view (clientConfig . configKeyMap) -renderEntries :: [([Modifier], Key, Action)] -> [Image]+renderEntries :: [([Modifier], Key, Action)] -> [Image'] renderEntries entries =-  [ resizeWidth keyColWidth key <|> act | (key,act) <- images ]+  [ resizeImage keyColWidth key <> act | (key,act) <- images ]    where     third (_,_,x) = x
src/Client/View/MaskList.hs view
@@ -13,6 +13,7 @@   ( maskListImages   ) where +import           Client.Image.PackedImage import           Client.Image.Palette import           Client.State import           Client.State.Channel@@ -23,10 +24,10 @@ import           Data.List import           Data.Ord import           Data.Maybe+import           Data.Semigroup import           Data.Text (Text) import           Data.Time import           Graphics.Vty.Attributes-import           Graphics.Vty.Image import           Irc.Identifier  -- | Render the lines used in a channel mask list@@ -35,7 +36,7 @@   Text        {- ^ network    -} ->   Identifier  {- ^ channel    -} ->   Int         {- ^ draw width -} ->-  ClientState -> [Image]+  ClientState -> [Image'] maskListImages mode network channel w st =   case mbEntries of     Nothing      -> [text' (view palError pal) "Mask list not loaded"]@@ -49,17 +50,17 @@                 . chanLists . ix mode                 ) st -maskListImages' :: HashMap Text MaskListEntry -> Int -> ClientState -> [Image]+maskListImages' :: HashMap Text MaskListEntry -> Int -> ClientState -> [Image'] maskListImages' entries w st = countImage : images   where     pal = clientPalette st -    countImage = text' (view palLabel pal) "Masks (visible/total): " <|>-                 string defAttr (show (length entryList)) <|>-                 char (view palLabel pal) '/' <|>+    countImage = text' (view palLabel pal) "Masks (visible/total): " <>+                 string defAttr (show (length entryList)) <>+                 char (view palLabel pal) '/' <>                  string defAttr (show (HashMap.size entries)) -    matcher = fromMaybe (const True) (clientMatcher st)+    matcher = fromMaybe (const True) (clientMatcher' st)      matcher' (mask,entry) = matcher mask || matcher (view maskListSetter entry) @@ -72,14 +73,14 @@     (masks, whoWhens) = unzip entryList     maskImages       = text' defAttr <$> masks     maskColumnWidth  = maximum (imageWidth <$> maskImages) + 1-    paddedMaskImages = resizeWidth maskColumnWidth <$> maskImages+    paddedMaskImages = resizeImage maskColumnWidth <$> maskImages     width            = max 1 w -    images = [ cropLine $ mask <|>-                          text' defAttr who <|>+    images = [ cropLine $ mask <>+                          text' defAttr who <>                           string defAttr (renderWhen when)              | (mask, MaskListEntry who when) <- zip paddedMaskImages whoWhens ]      cropLine img-      | imageWidth img > width = cropRight width img+      | imageWidth img > width = resizeImage width img       | otherwise              = img
src/Client/View/Mentions.hs view
@@ -14,20 +14,20 @@   ( mentionsViewLines   ) where -import           Client.Image.PackedImage (Image', unpackImage)+import           Client.Image.PackedImage import           Client.Image.StatusLine import           Client.State import           Client.State.Focus import           Client.State.Window import qualified Data.Map as Map import           Control.Lens+import           Data.Semigroup import           Data.Time (UTCTime)-import           Graphics.Vty.Image  -- | Generate the list of message lines marked important ordered by -- time. Each run of lines from the same channel will be grouped -- together. Messages are headed by their window, network, and channel.-mentionsViewLines :: Int -> ClientState -> [Image]+mentionsViewLines :: Int -> ClientState -> [Image'] mentionsViewLines w st = addMarkers w st entries    where@@ -51,10 +51,10 @@   Int           {- ^ draw width                        -} ->   ClientState   {- ^ client state                      -} ->   [MentionLine] {- ^ list of mentions in time order    -} ->-  [Image]       {- ^ mention images and channel labels -}+  [Image']      {- ^ mention images and channel labels -} addMarkers _ _ [] = [] addMarkers w !st (!ml : xs)-  = map (unpackImage . mlImage) (ml:same)+  = map mlImage (ml:same)  ++ minorStatusLineImage (mlFocus ml) w False st   : addMarkers w st rest   where@@ -70,10 +70,14 @@   [MentionLine] windowEntries !detailed name focus w =   [ MentionLine-      { mlTimestamp  = view wlTimestamp l+      { mlTimestamp  = views wlTimestamp unpackUTCTime l       , mlWindowName = name       , mlFocus      = focus-      , mlImage      = if detailed then view wlFullImage l else view wlImage l+      , mlImage      = if detailed+                        then view wlFullImage l+                        else view wlPrefix l <>+                             char mempty ' ' <>+                             view wlImage l       }   | let p x = WLImportant == view wlImportance x   , l <- toListOf (winMessages . each . filtered p) w
src/Client/View/Messages.hs view
@@ -1,3 +1,4 @@+{-# Language OverloadedStrings #-} {-| Module      : Client.View.Messages Description : Chat message view@@ -26,14 +27,13 @@ import           Client.State.Window import           Control.Lens import           Control.Monad+import           Data.List import           Data.Semigroup-import           Graphics.Vty.Attributes-import qualified Graphics.Vty.Image as Vty import           Irc.Identifier import           Irc.Message  -chatMessageImages :: Focus -> Int -> ClientState -> [Vty.Image]+chatMessageImages :: Focus -> Int -> ClientState -> [Image'] chatMessageImages focus w st =   case preview (clientWindows . ix focus) st of     Nothing  -> []@@ -54,12 +54,13 @@    where     palette = clientPalette st-    marker = Vty.string (view palLineMarker palette) (replicate w '-')+    marker = string (view palLineMarker palette) (replicate w '-')     windowLineProcessor hideMeta       | view clientDetailView st =+          concatMap (reverse . fullLineWrap w) .           if hideMeta             then detailedImagesWithoutMetadata st-            else map (views wlFullImage unpackImage)+            else map (view wlFullImage)        | otherwise = windowLinesToImages st w hideMeta . filter (not . isNoisy) @@ -68,11 +69,11 @@         ReplySummary code -> squelchIrcMsg (Reply code [])         _                 -> False -detailedImagesWithoutMetadata :: ClientState -> [WindowLine] -> [Vty.Image]+detailedImagesWithoutMetadata :: ClientState -> [WindowLine] -> [Image'] detailedImagesWithoutMetadata st wwls =   case gatherMetadataLines st wwls of     ([], [])   -> []-    ([], w:ws) -> views wlFullImage unpackImage w+    ([], w:ws) -> view wlFullImage w                 : detailedImagesWithoutMetadata st ws     (_:_, wls) -> detailedImagesWithoutMetadata st wls @@ -82,70 +83,71 @@   Int          {- ^ draw width    -} ->   Bool         {- ^ hide metadata -} ->   [WindowLine] {- ^ window lines  -} ->-  [Vty.Image]  {- ^ image lines   -}+  [Image']     {- ^ image lines   -} windowLinesToImages st w hideMeta wwls =   case gatherMetadataLines st wwls of     ([], [])   -> []-    ([], wl:wls) ->-                   map unpackImage-                         (lineWrapChat w-                           (view (clientConfig . configIndentWrapped) st)-                           (view wlImage wl))+    ([], wl:wls) -> drawLine wl                 ++ windowLinesToImages st w hideMeta wls     ((img,who,mbnext):mds, wls)        | hideMeta -> windowLinesToImages st w hideMeta wls        | otherwise ->-         mkLines w (startMetadata img mbnext who mds palette)+         wrap+           metaPad+           (mconcat+              (intersperse " "+                 (startMetadata img mbnext who mds palette)))       ++ windowLinesToImages st w hideMeta wls    where     palette = clientPalette st+    config  = view clientConfig st+    padAmt  = view configNickPadding config -mkLines :: Int -> [Vty.Image] -> [Vty.Image]-mkLines _ []     = []-mkLines w (x:xs) = reverse (mkLines1 w x xs)+    drawTime = timeImage palette . unpackTimeOfDay+    padNick  = nickPad padAmt+    metaPad  = "      " <> padNick "" -mkLines1 :: Int -> Vty.Image -> [Vty.Image] -> [Vty.Image]-mkLines1 _ x []            = [x]-mkLines1 w x (y:ys)-  | Vty.imageWidth x' <= w = mkLines1 w x' ys-  | otherwise              = x : mkLines1 w y ys-  where-    x' = x Vty.<|> Vty.char defAttr ' ' Vty.<|> y+    drawPrefix = views wlTimestamp drawTime <>+                 views wlPrefix    padNick +    wrap pfx body = reverse (lineWrapPrefix w pfx body)++    drawLine wl = wrap (drawPrefix wl) (view wlImage wl)+ ------------------------------------------------------------------------  type MetadataState =   Identifier                             {- ^ current nick -} ->   [(Image',Identifier,Maybe Identifier)] {- ^ metadata     -} ->   Palette                                {- ^ palette      -} ->-  [Vty.Image]+  [Image']  startMetadata ::   Image'           {- ^ metadata image           -} ->   Maybe Identifier {- ^ possible nick transition -} ->   MetadataState startMetadata img mbnext who mds palette =-  let acc = unpackImage (quietIdentifier palette who <> img)+  let acc = quietIdentifier palette who <> img   in transitionMetadata acc mbnext who mds palette  transitionMetadata ::-  Vty.Image ->+  Image'           {- ^ image accumulator        -} ->   Maybe Identifier {- ^ possible nick transition -} ->   MetadataState transitionMetadata acc mbwho who mds palette =   case mbwho of     Nothing   -> continueMetadata acc who mds palette     Just who' ->-      let acc' = acc Vty.<|> unpackImage (quietIdentifier palette who')+      let acc' = acc <> quietIdentifier palette who'       in continueMetadata acc' who' mds palette -continueMetadata :: Vty.Image -> MetadataState+continueMetadata :: Image' -> MetadataState continueMetadata acc _ [] _ = [acc] continueMetadata acc who1 ((img, who2, mbwho3):mds) palette-  | who1 == who2 = let acc' = acc Vty.<|> unpackImage img+  | who1 == who2 = let acc' = acc <> img                    in transitionMetadata acc' mbwho3 who2 mds palette   | otherwise    = acc : startMetadata img mbwho3 who2 mds palette 
src/Client/View/Palette.hs view
@@ -15,24 +15,24 @@   ) where  import           Client.Image.Palette+import           Client.Image.PackedImage import           Control.Lens import           Data.List import           Graphics.Vty.Attributes-import           Graphics.Vty.Image  digits :: String digits = "0123456789ABCDEF" -digitImage :: Char -> Image+digitImage :: Char -> Image' digitImage d = string defAttr [' ',d,' '] -columns :: [Image] -> Image-columns = horizCat . intersperse (char defAttr ' ')+columns :: [Image'] -> Image'+columns = mconcat . intersperse (char defAttr ' ')  -- | Generate lines used for @/palette@. These lines show -- all the colors used in the current palette as well as -- the colors available for use in palettes.-paletteViewLines :: Palette -> [Image]+paletteViewLines :: Palette -> [Image'] paletteViewLines pal =   [ columns   $ digitImage digit@@ -49,12 +49,12 @@     ]    , columns (map digitImage (' ':digits))-  , emptyImage+  , mempty   , columns     [ text' (view l pal) name     | (name, Lens l) <- paletteMap     ]-  , emptyImage+  , mempty   , columns     [ string attr "nicks"     | attr <- toListOf (palNicks . folded) pal
+ src/Client/View/RtsStats.hs view
@@ -0,0 +1,36 @@+{-# Language CPP, OverloadedStrings #-}+{-|+Module      : Client.View.RtsStats+Description : View current GHC RTS statistics+Copyright   : (c) Eric Mertens, 2017+License     : ISC+Maintainer  : emertens@gmail.com++Lines for the @/rtsstats@ command. This module depends+on GHC 8.2.1 API.++-}++module Client.View.RtsStats+  ( rtsStatsLines+  ) where++import           Client.Image.PackedImage+import           Client.Image.Palette+import           Control.Lens+import           Data.Semigroup+import           Graphics.Vty.Attributes+import           RtsStats++-- | Generate lines used for @/rtsstats@.+rtsStatsLines :: Maybe Stats -> Palette -> [Image']+rtsStatsLines Nothing pal = [text' (view palError pal) "Statistics not available"]+rtsStatsLines (Just stats) pal+  | null entries = [text' (view palError pal) "Statistics empty"]+  | otherwise    = zipWith (\v l -> padV wv v <> " " <> l) valueImages labelImages +  where+    entries     = statsToEntries stats+    labelImages = map (text' (view palLabel pal) . fst) entries+    valueImages = map (text' defAttr . snd) entries+    wv          = maximum (0 : map imageWidth valueImages)+    padV n img  = string defAttr (replicate (n - imageWidth img) ' ') <> img
src/Client/View/UrlSelection.hs view
@@ -1,3 +1,4 @@+{-# Language OverloadedStrings #-} {-| Module      : Client.View.UrlSelection Description : URL selection module@@ -15,8 +16,9 @@  import           Client.Configuration import           Client.Image.Message-import           Client.Image.PackedImage (unpackImage)+import           Client.Image.PackedImage import           Client.Image.Palette+import           Client.Image.LineWrap import           Client.Message import           Client.State import           Client.State.Focus@@ -25,27 +27,32 @@ import           Control.Lens import           Data.HashSet (HashSet) import qualified Data.HashSet as HashSet+import           Data.Semigroup import           Data.Text (Text) import           Graphics.Vty.Attributes-import           Graphics.Vty.Image import           Irc.Identifier import           Text.Read (readMaybe)   -- | Generate the lines used for the view when typing @/url@ urlSelectionView ::+  Int         {- ^ render width        -} ->   Focus       {- ^ window to search    -} ->   String      {- ^ argument to command -} ->   ClientState {- ^ client state        -} ->-  [Image]     {- ^ image lines         -}-urlSelectionView focus arg st =-  zipWith (draw me pal padding selected) [1..] (toListOf urled st)+  [Image']    {- ^ image lines         -}+urlSelectionView w focus arg st+  = concat+  $ zipWith (draw w me pal padding selected) [1..] (toListOf urled st)   where     urled = clientWindows . ix focus           . winMessages   . each           . folding matches +    focused = focus == view clientFocus st+     selected+      | not focused             = 0       | all (==' ') arg         = 1       | Just i <- readMaybe arg = i       | otherwise               = 0 -- won't match@@ -77,20 +84,21 @@  -- | Render one line of the url list draw ::+  Int                       {- ^ rendered width            -} ->   HashSet Identifier        {- ^ my nick                   -} ->   Palette                   {- ^ palette                   -} ->-  Maybe Integer             {- ^ nick render padding       -} ->+  PaddingMode               {- ^ nick render padding       -} ->   Int                       {- ^ selected index            -} ->   Int                       {- ^ url index                 -} ->   (Maybe Identifier, Text)  {- ^ sender and url text       -} ->-  Image                     {- ^ rendered line             -}-draw me pal padding selected i (who,url) =-  unpackImage-   (rightPad NormalRender padding-    (foldMap (coloredIdentifier pal NormalIdentifier me) who)) <|>-  string defAttr ": " <|>-  string attr (shows i ". ") <|>-  text' attr (cleanText url)+  [Image']                  {- ^ rendered lines            -}+draw w me pal padding selected i (who,url)+  = reverse+  $ lineWrapPrefix w+      (string defAttr (shows i ". ") <>+       nickPad padding+         (foldMap (coloredIdentifier pal NormalIdentifier me) who) <> ": ")+      (text' attr (cleanText url))   where     attr | selected == i = withStyle defAttr reverseVideo          | otherwise     = defAttr
src/Client/View/UserList.hs view
@@ -14,7 +14,7 @@   ) where  import           Client.Image.Message-import           Client.Image.PackedImage (unpackImage)+import           Client.Image.PackedImage import           Client.Image.Palette import           Client.State import           Client.State.Channel@@ -25,10 +25,11 @@ import           Data.List import           Data.Maybe import           Data.Ord+import           Data.Semigroup import           Data.Text (Text) import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText import           Graphics.Vty.Attributes-import           Graphics.Vty.Image import           Irc.Identifier import           Irc.UserInfo @@ -39,7 +40,7 @@   Text        {- ^ network -} ->   Identifier  {- ^ channel -} ->   ClientState                 ->-  [Image]+  [Image'] userListImages network channel st =   case preview (clientConnection network) st of     Just cs -> userListImages' cs channel st@@ -47,43 +48,47 @@   where     pal = clientPalette st -userListImages' :: NetworkState -> Identifier -> ClientState -> [Image]+userListImages' :: NetworkState -> Identifier -> ClientState -> [Image'] userListImages' cs channel st =-    [countImage, horizCat (intersperse gap (map renderUser usersList))]+    [countImage, mconcat (intersperse gap (map renderUser usersList))]   where-    countImage = text' (view palLabel pal) "Users:" <|>-                 sigilCountImage+    countImage = drawSigilCount pal (map snd usersList)      matcher = fromMaybe (const True) (clientMatcher st)      myNicks = clientHighlights cs st      renderUser (ident, sigils) =-      string (view palSigil pal) sigils <|>-      unpackImage (coloredIdentifier pal NormalIdentifier myNicks ident)+      string (view palSigil pal) sigils <>+      coloredIdentifier pal NormalIdentifier myNicks ident      gap = char defAttr ' ' -    matcher' (ident,sigils) = matcher (Text.pack sigils `Text.append` idText ident)+    matcher' (ident,sigils) = matcher (LText.fromChunks [Text.pack sigils, idText ident])      usersList = sortBy (comparing fst)               $ filter matcher'               $ HashMap.toList usersHashMap -    sigilCounts = Map.fromListWith (+)-                    [ (take 1 sigil, 1::Int) | (_,sigil) <- usersList ]--    sigilCountImage = horizCat-      [ string (view palSigil pal) (' ':sigil) <|>-        string defAttr (show n)-      | (sigil,n) <- Map.toList sigilCounts-      ]-     pal = clientPalette st      usersHashMap =       view (csChannels . ix channel . chanUsers) cs +drawSigilCount :: Palette -> [String] -> Image'+drawSigilCount pal sigils =+  text' (view palLabel pal) "Users:" <> mconcat entries+  where+    sigilCounts = Map.fromListWith (+) [ (take 1 sigil, 1::Int) | sigil <- sigils ]++    entries+      | Map.null sigilCounts = [" 0"]+      | otherwise = [ string (view palSigil pal) (' ':sigil) <>+                      string defAttr (show n)+                    | (sigil,n) <- Map.toList sigilCounts+                    ]++ -- | Render lines for the @/users@ command in detailed view. -- Each user will be rendered on a separate line with username -- and host visible when known.@@ -91,7 +96,7 @@   Text        {- ^ network -} ->   Identifier  {- ^ channel -} ->   ClientState                 ->-  [Image]+  [Image'] userInfoImages network channel st =   case preview (clientConnection network) st of     Just cs -> userInfoImages' cs channel st@@ -99,21 +104,23 @@   where     pal = clientPalette st -userInfoImages' :: NetworkState -> Identifier -> ClientState -> [Image]-userInfoImages' cs channel st = renderEntry <$> usersList+userInfoImages' :: NetworkState -> Identifier -> ClientState -> [Image']+userInfoImages' cs channel st = countImage : map renderEntry usersList   where     matcher = fromMaybe (const True) (clientMatcher st) +    countImage = drawSigilCount pal (map snd usersList)+     myNicks = clientHighlights cs st      pal = clientPalette st      renderEntry (info, sigils) =-      string (view palSigil pal) sigils <|>-      unpackImage (coloredUserInfo pal DetailedRender myNicks info)+      string (view palSigil pal) sigils <>+      coloredUserInfo pal DetailedRender myNicks info      matcher' (info,sigils) =-      matcher (Text.pack sigils `Text.append` renderUserInfo info)+      matcher (LText.fromChunks [Text.pack sigils, renderUserInfo info])      userInfos = view csUsers cs 
src/Client/View/Windows.hs view
@@ -12,6 +12,7 @@   ( windowsImages   ) where +import           Client.Image.PackedImage import           Client.Image.Palette import           Client.State import           Client.State.Focus@@ -20,12 +21,12 @@ import           Control.Lens import           Data.List import qualified Data.Map as Map+import           Data.Semigroup import           Graphics.Vty.Attributes-import           Graphics.Vty.Image import           Irc.Identifier  -- | Draw the image lines associated with the @/windows@ command.-windowsImages :: WindowsFilter -> ClientState -> [Image]+windowsImages :: WindowsFilter -> ClientState -> [Image'] windowsImages filt st = reverse (createColumns windows)   where     windows = [ renderWindowColumns pal n k v@@ -61,7 +62,7 @@ ------------------------------------------------------------------------  -renderWindowColumns :: Palette -> Char -> Focus -> Window -> [Image]+renderWindowColumns :: Palette -> Char -> Focus -> Window -> [Image'] renderWindowColumns pal name focus win =   [ char (view palWindowName pal) name   , renderedFocus pal focus@@ -69,15 +70,15 @@   ]  -createColumns :: [[Image]] -> [Image]+createColumns :: [[Image']] -> [Image'] createColumns xs = map makeRow xs   where     columnWidths = maximum . map imageWidth <$> transpose xs-    makeRow = horizCat+    makeRow = mconcat             . intersperse (char defAttr ' ')-            . zipWith resizeWidth columnWidths+            . zipWith resizeImage columnWidths -renderedFocus :: Palette -> Focus -> Image+renderedFocus :: Palette -> Focus -> Image' renderedFocus pal focus =   case focus of     Unfocused ->@@ -85,14 +86,14 @@     NetworkFocus network ->       text' (view palLabel pal) network     ChannelFocus network channel ->-      text' (view palLabel pal) network <|>-      char defAttr ':' <|>+      text' (view palLabel pal) network <>+      char defAttr ':' <>       text' (view palLabel pal) (idText channel) -renderedWindowInfo :: Palette -> Window -> Image+renderedWindowInfo :: Palette -> Window -> Image' renderedWindowInfo pal win =-  string (view newMsgAttrLens pal) (views winUnread show win) <|>-  char defAttr '/' <|>+  string (view newMsgAttrLens pal) (views winUnread show win) <>+  char defAttr '/' <>   string (view palActivity pal) (views winTotal show win)   where     newMsgAttrLens =
+ src/RtsStats.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE OverloadedStrings, FlexibleContexts, FlexibleInstances, DeriveGeneric, TypeOperators, StandaloneDeriving, CPP, EmptyCase #-}+{-# OPTIONS_GHC -Wno-orphans -funfolding-use-threshold=2000 -funfolding-creation-threshold=1000 #-}++{-|+Module      : RtsStats+Description : Compatibility layer for GHC RTS statistics across versions+Copyright   : (c) Eric Mertens, 2017+License     : ISC+Maintainer  : emertens@gmail.com++-}++module RtsStats+  ( Stats+  , getStats+  , statsToEntries+  ) where++import           Data.Int+import           Data.List (intercalate)+import           Data.List.Split (chunksOf)+import           Data.Text (Text)+import qualified Data.Text as Text+import           Data.Word+import           GHC.Generics+import           GHC.Stats++type KVs = [(Text,Text)] -> [(Text,Text)]++cons :: (Text, Text) -> KVs+cons = (:)++class    Fields a      where fields :: a -> Text -> KVs+instance Fields Int64  where fields = commaFields+instance Fields Word32 where fields = commaFields+instance Fields Word64 where fields = commaFields+instance Fields Double where fields = showFields++showFields :: Show a => a -> Text -> KVs+showFields x n = cons (n, Text.pack (show x))+{-# NOINLINE showFields #-}++commaFields :: Show a => a -> Text -> KVs+commaFields x n = cons (n, Text.pack (addCommas (show x)))+{-# NOINLINE commaFields #-}++addCommas :: String -> String+addCommas = reverse . intercalate "," . chunksOf 3 . reverse++genericFields :: (Generic a, GFields (Rep a)) => a -> Text -> KVs+genericFields = gfields . from++class GFields f where+  gfields :: f p -> Text -> KVs++instance GFields f => GFields (D1 c f) where+  gfields (M1 x) = gfields x+  {-# INLINE gfields #-}++instance GFields f => GFields (C1 c f) where+  gfields (M1 x) = gfields x+  {-# INLINE gfields #-}++instance (Selector s, GFields f) => GFields (S1 s f) where+  gfields s@(M1 x) _ = gfields x (Text.pack (selName s))+  {-# INLINE gfields #-}++instance (GFields f, GFields g) => GFields (f :*: g) where+  gfields (x :*: y) n = gfields x n . gfields y n+  {-# INLINE gfields #-}++instance (GFields f, GFields g) => GFields (f :+: g) where+  gfields (L1 x) = gfields x+  gfields (R1 x) = gfields x+  {-# INLINE gfields #-}++instance GFields U1 where+  gfields _ _ = id+  {-# INLINE gfields #-}++instance GFields V1 where+  gfields v _ = case v of {}+  {-# INLINE gfields #-}++instance Fields a => GFields (K1 i a) where+  gfields (K1 x) = fields x+  {-# INLINE gfields #-}++statsToEntries :: Stats -> [(Text, Text)]+statsToEntries (Stats rts) = fields rts "stats" []++#if MIN_VERSION_base(4,10,0)++newtype Stats = Stats RTSStats++deriving instance Generic RTSStats+deriving instance Generic GCDetails+instance Fields RTSStats  where fields = genericFields+instance Fields GCDetails where fields = genericFields++getStats :: IO (Maybe Stats)+getStats =+  do enabled <- getRTSStatsEnabled+     if enabled then Just . Stats <$> getRTSStats+                else pure Nothing++#else++newtype Stats = Stats GCStats++deriving instance Generic GCStats+instance Fields GCStats where fields = genericFields++getStats :: IO (Maybe Stats)+getStats =+  do enabled <- getGCStatsEnabled+     if enabled then Just . Stats <$> getGCStats+                else pure Nothing++#endif