packages feed

glirc 2.32 → 2.33

raw patch · 16 files changed

+203/−79 lines, 16 filesdep ~basedep ~config-schemadep ~config-value

Dependency ranges changed: base, config-schema, config-value, hookup, lens, template-haskell

Files

ChangeLog.md view
@@ -2,6 +2,14 @@  ## 2.32 +* Fixed cursor position with wide characters in input box+* Added ability to force request capabilities.+* Generalized `FreRelay` hook+* Add `show-accounts` setting to make account status visible+* Query channel modes on join++## 2.32+ * Fix SASL EXTERNAL * Better /url matching * Support for config-schema improvements
glirc.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.2 name:                glirc-version:             2.32+version:             2.33 synopsis:            Console IRC client description:         Console IRC client                      .@@ -22,9 +22,9 @@ tested-with:         GHC==8.6.5  custom-setup-  setup-depends: base     >=4.11 && <4.13,+  setup-depends: base     >=4.11 && <4.14,                  filepath >=1.4  && <1.5,-                 Cabal    >=2.2  && <2.5,+                 Cabal    >=2.2  && <4,  source-repository head   type: git@@ -54,7 +54,7 @@   includes:            include/glirc-api.h   install-includes:    glirc-api.h   default-language:    Haskell2010-  build-tools:         hsc2hs+  build-tool-depends:  hsc2hs:hsc2hs    exposed-modules:     Client.Authentication.Ecdsa                        Client.CApi@@ -106,6 +106,7 @@                        Client.State.Focus                        Client.State.Network                        Client.State.Window+                       Client.UserHost                        Client.View                        Client.View.Cert                        Client.View.ChannelInfo@@ -136,24 +137,24 @@   autogen-modules:     Paths_glirc                        Build_glirc -  build-depends:       base                 >=4.11   && <4.13,+  build-depends:       base                 >=4.11   && <4.14,                        HsOpenSSL            >=0.11   && <0.12,                        async                >=2.2    && <2.3,                        attoparsec           >=0.13   && <0.14,                        base64-bytestring    >=1.0.0.1&& <1.1,                        bytestring           >=0.10.8 && <0.11,-                       config-schema        ^>=1.1.0.0,-                       config-value         ^>=0.6,+                       config-schema        ^>=1.2.0.0,+                       config-value         ^>=0.7,                        containers           >=0.5.7  && <0.7,                        directory            >=1.2.6  && <1.4,                        filepath             >=1.4.1  && <1.5,                        free                 >=4.12   && <5.2,                        gitrev               >=1.2    && <1.4,                        hashable             >=1.2.4  && <1.4,-                       hookup               >=0.2.3  && <0.3,+                       hookup               >=0.2.3  && <0.4,                        irc-core             >=2.7.1  && <2.8,                        kan-extensions       >=5.0    && <5.3,-                       lens                 >=4.14   && <4.18,+                       lens                 >=4.14   && <4.19,                        network              >=2.6.2  && <3.2,                        process              >=1.4.2  && <1.7,                        psqueues             >=0.2.7  && <0.3,@@ -162,7 +163,7 @@                        semigroupoids        >=5.1    && <5.4,                        split                >=0.2    && <0.3,                        stm                  >=2.4    && <2.6,-                       template-haskell     >=2.11   && <2.15,+                       template-haskell     >=2.11   && <2.16,                        text                 >=1.2.2  && <1.3,                        time                 >=1.6    && <1.10,                        transformers         >=0.5.2  && <0.6,
src/Client/CApi/Exports.hs view
@@ -93,6 +93,7 @@ import           Client.State.Focus import           Client.State.Network import           Client.State.Window+import           Client.UserHost import           Control.Concurrent.MVar import           Control.Exception import           Control.Lens
src/Client/Commands.hs view
@@ -42,6 +42,7 @@ import           Client.State.Focus import           Client.State.Network import           Client.State.Window+import           Client.UserHost import           Control.Applicative import           Control.Concurrent.Async (cancel) import           Control.Exception (displayException, try)
src/Client/Configuration/ServerSettings.hs view
@@ -16,6 +16,7 @@   (   -- * Server settings type     ServerSettings(..)+  , HookConfig(..)   , serverSpec   , identifierSpec @@ -51,6 +52,8 @@   , ssSts   , ssTlsPubkeyFingerprint   , ssTlsCertFingerprint+  , ssShowAccounts+  , ssCapabilities    -- * Load function   , loadDefaultServerSettings@@ -68,7 +71,7 @@ import           Control.Lens import qualified Data.ByteString as B import           Data.Functor.Alt                    ((<!>))-import           Data.List.NonEmpty (NonEmpty)+import           Data.List.NonEmpty (NonEmpty((:|))) import           Data.ByteString (ByteString) import           Data.Maybe (fromMaybe) import           Data.Monoid@@ -103,7 +106,7 @@   , _ssChanservChannels :: ![Identifier] -- ^ Channels with chanserv permissions   , _ssFloodPenalty     :: !Rational -- ^ Flood limiter penalty (seconds)   , _ssFloodThreshold   :: !Rational -- ^ Flood limited threshold (seconds)-  , _ssMessageHooks     :: ![Text] -- ^ Initial message hooks+  , _ssMessageHooks     :: ![HookConfig] -- ^ Initial message hooks   , _ssName             :: !(Maybe Text) -- ^ The name referencing the server in commands   , _ssReconnectAttempts:: !Int -- ^ The number of reconnect attempts to make on error   , _ssAutoconnect      :: !Bool -- ^ Connect to this network on server startup@@ -113,9 +116,15 @@   , _ssSts              :: !Bool -- ^ Honor STS policies when true   , _ssTlsPubkeyFingerprint :: !(Maybe Fingerprint) -- ^ optional acceptable public key fingerprint   , _ssTlsCertFingerprint   :: !(Maybe Fingerprint) -- ^ optional acceptable certificate fingerprint+  , _ssShowAccounts     :: !Bool -- ^ Render account names+  , _ssCapabilities     :: ![Text] -- ^ Extra capabilities to unconditionally request   }   deriving Show +-- | Hook name and configuration arguments+data HookConfig = HookConfig Text [Text]+  deriving Show+ -- | Security setting for network connection data UseTls   = UseTls         -- ^ TLS connection@@ -172,6 +181,8 @@        , _ssSts              = True        , _ssTlsPubkeyFingerprint = Nothing        , _ssTlsCertFingerprint   = Nothing+       , _ssShowAccounts     = False+       , _ssCapabilities     = []        }  serverSpec :: ValueSpec (ServerSettings -> ServerSettings)@@ -259,7 +270,7 @@       , req "flood-threshold" ssFloodThreshold anySpec         "RFC 1459 rate limiting, seconds of allowed penalty accumulation (default 10)" -      , req "message-hooks" ssMessageHooks anySpec+      , req "message-hooks" ssMessageHooks (listSpec hookSpec)         "Special message hooks to enable: \"buffextras\" available"        , req "reconnect-attempts" ssReconnectAttempts anySpec@@ -285,7 +296,18 @@        , opt "tls-pubkey-fingerprint" ssTlsPubkeyFingerprint fingerprintSpec         "Check SHA1, SHA256, or SHA512 public key fingerprint"++      , req "show-accounts" ssShowAccounts yesOrNoSpec+        "Render account names alongside chat messages"++      , req "capabilities" ssCapabilities anySpec+        "Extra capabilities to unconditionally request from the server"       ]++hookSpec :: ValueSpec HookConfig+hookSpec =+  flip HookConfig [] <$> anySpec <!>+  (\(x:|xs) -> HookConfig x xs) <$> nonemptySpec anySpec  -- | Match fingerprints in plain hex or colon-delimited bytes. -- SHA-1 is 20 bytes. SHA-2-256 is 32 bytes. SHA-2-512 is 64 bytes.
src/Client/Configuration/Sts.hs view
@@ -20,6 +20,7 @@   ) where  import           Config (Value(..), Section(..), parse, pretty)+import           Config.Number (integerToNumber) import           Config.Schema.Spec import           Config.Schema.Load (loadValue) import           Control.Exception (try)@@ -64,7 +65,7 @@         [ Section () "host"             (Text () k),           Section () "port"-            (Number () 10 (fromIntegral (_stsPort v))),+            (Number () (integerToNumber (fromIntegral (_stsPort v)))),           Section () "until"             (Text ()                (Text.pack
src/Client/EventLoop.hs view
@@ -25,7 +25,6 @@ import           Client.EventLoop.Errors (exceptionToLines) import           Client.EventLoop.Network (clientResponse) import           Client.Hook-import           Client.Hooks import           Client.Image import           Client.Image.Layout (scrollAmount) import           Client.Log@@ -279,9 +278,7 @@                  (stateHook, viewHook)                       = over both applyMessageHooks                       $ partition (view messageHookStateful)-                      $ lookups-                          (view csMessageHooks cs)-                          messageHooks+                      $ view csMessageHooks cs               case stateHook (cookIrcMsg raw) of                Nothing  -> return st1 -- Message ignored@@ -323,12 +320,6 @@ parseZncTime :: String -> Maybe UTCTime parseZncTime = parseTimeM True defaultTimeLocale              $ iso8601DateFormat (Just "%T%Q%Z")----- | Returns the list of values that were stored at the given indexes, if--- a value was stored at that index.-lookups :: Ixed m => [Index m] -> m -> [IxValue m]-lookups ks m = mapMaybe (\k -> preview (ix k) m) ks   -- | Update the height and width fields of the client state
src/Client/Hook/FreRelay.hs view
@@ -29,15 +29,18 @@ import           Irc.UserInfo (UserInfo(..)) import           StrQuote (str) -freRelayHook :: MessageHook-freRelayHook = MessageHook "frerelay" False remap+-- | Hook for mapping frerelay messages in #dronebl on freenode+-- to appear like native messages.+freRelayHook :: [Text] -> Maybe MessageHook+freRelayHook args = Just (MessageHook "frerelay" False (remap (map mkId args)))  -- | Remap messages from frerelay on #dronebl that match one of the -- rewrite rules.-remap :: IrcMsg -> MessageResult-remap (Privmsg (UserInfo "frerelay" _ _) chan@"#dronebl" msg)-  | Just sub <- rules chan msg = RemapMessage sub-remap _ = PassMessage+remap :: [Identifier] -> IrcMsg -> MessageResult+remap nicks (Privmsg (UserInfo nick _ _) chan@"#dronebl" msg)+  | nick `elem` nicks+  , Just sub <- rules chan msg = RemapMessage sub+remap _ _ = PassMessage  -- | Generate a replacement message for a chat message from frerelay -- when the message matches one of the replacement rules.@@ -53,6 +56,8 @@     , rule (partMsg chan) partRe msg     , rule quitMsg quitRe msg     , rule nickMsg nickRe msg+    , rule (kickMsg chan) kickRe msg+    , rule (modeMsg chan) modeRe msg     ]  -- | Match the message against the regular expression and use the given@@ -68,13 +73,15 @@     [_:xs] -> matchRule xs mk     _      -> Nothing -chatRe, actionRe, joinRe, quitRe, nickRe, partRe :: Regex+chatRe, actionRe, joinRe, quitRe, nickRe, partRe, kickRe, modeRe :: Regex Right chatRe   = compRe [str|^<([^>]+)> (.*)$|] Right actionRe = compRe [str|^\* ([^ ]+) (.*)$|] Right joinRe   = compRe [str|^\*\*\* \[([^]]+)\] ([^ ]+) \(([^@]+)@([^)]+)\) has joined the channel$|] Right quitRe   = compRe [str|^\*\*\* \[([^]]+)\] ([^ ]+) has signed off \((.*)\)$|] Right nickRe   = compRe [str|^\*\*\* \[([^]]+)\] ([^ ]+) changed nick to ([^ ]+)$|] Right partRe   = compRe [str|^\*\*\* \[([^]]+)\] ([^ ]+) has left the channel( \((.*)\))?$|]+Right kickRe   = compRe [str|^\*\*\* \[([^]]+)\] ([^ ]+) has been kicked by ([^ ]+) \((.*)\)$|]+Right modeRe   = compRe [str|^\*\*\* \[([^]]+)\] ([^ ]+) sets mode (.*)$|]  -- | Compile a regular expression for using in message matching. compRe ::@@ -153,6 +160,32 @@     (userInfo (old <> "@" <> srv))     (mkId (new <> "@" <> srv)) +kickMsg ::+  Identifier {- ^ channel     -} ->+  Text       {- ^ server      -} ->+  Text       {- ^ kickee nick -} ->+  Text       {- ^ kicker nick -} ->+  Text       {- ^ reason      -} ->+  IrcMsg+kickMsg chan srv kickee kicker reason =+  Kick+    (userInfo (kicker <> "@" <> srv))+    chan+    (mkId (kickee <> "@" <> srv))+    reason++modeMsg ::+  Identifier {- ^ channel     -} ->+  Text       {- ^ server      -} ->+  Text       {- ^ nickname    -} ->+  Text       {- ^ modes       -} ->+  IrcMsg+modeMsg chan srv nick modes =+  Mode+    (userInfo (nick <> "@" <> srv))+    chan+    (Text.words modes)+ -- | Construct dummy user info when we don't know the user or host part. userInfo ::   Text {- ^ nickname -} ->@@ -161,15 +194,12 @@  ------------------------------------------------------------------------ +-- | The class allows n-ary functions of the form+-- `Text -> Text -> ... -> IrcMsg` to be used to exhaustively consume the+-- matched elements of a regular expression. class Rule a where   matchRule :: [Text] -> a -> Maybe IrcMsg -class RuleArg a where-  matchArg :: Text -> Maybe a--instance RuleArg Text where-  matchArg = Just- instance (RuleArg a, Rule b) => Rule (a -> b) where   matchRule tts f =     do (t,ts) <- uncons tts@@ -180,3 +210,6 @@   matchRule args ircMsg     | null args = Just ircMsg     | otherwise = Nothing++class    RuleArg a    where matchArg :: Text -> Maybe a+instance RuleArg Text where matchArg = Just
src/Client/Hook/Znc/Buffextras.hs view
@@ -29,8 +29,12 @@ -- | Map ZNC's buffextras messages to native client messages. -- Set debugging to pass through buffextras messages that -- the hook doesn't understand.-buffextrasHook :: Bool {- ^ enable debugging -} -> MessageHook-buffextrasHook = MessageHook "buffextras" False . remap+buffextrasHook :: [Text] {- ^ arguments -} -> Maybe MessageHook+buffextrasHook args =+  case args of+    []        -> Just (MessageHook "buffextras" False (remap False))+    ["debug"] -> Just (MessageHook "buffextras" False (remap True))+    _         -> Nothing  remap ::   Bool {- ^ enable debugging -} ->
src/Client/Hooks.hs view
@@ -24,10 +24,9 @@ import Client.Hook.Znc.Buffextras  -- | All the available message hooks.-messageHooks :: HashMap Text MessageHook+messageHooks :: HashMap Text ([Text] -> Maybe MessageHook) messageHooks = fromList-  [ ("snotice", snoticeHook)-  , ("buffextras", buffextrasHook False)-  , ("buffextras-debug", buffextrasHook True)-  , ("frerelay", freRelayHook)+  [ ("snotice"   , \_ -> Just snoticeHook)+  , ("frerelay"  , freRelayHook)+  , ("buffextras", buffextrasHook)   ]
src/Client/Image/Message.hs view
@@ -36,9 +36,12 @@ import           Client.Message import           Client.State.DCC (isSend) import           Client.State.Window+import           Client.UserHost import           Control.Lens import           Data.Char import           Data.Hashable (hash)+import           Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap import           Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import           Data.List@@ -61,6 +64,7 @@   , rendNicks      :: HashSet Identifier -- ^ nicknames to highlight   , rendMyNicks    :: HashSet Identifier -- ^ nicknames to highlight in red   , rendPalette    :: Palette -- ^ nick color palette+  , rendAccounts   :: HashMap Identifier UserAndHost   }  -- | Default 'MessageRendererParams' with no sigils or nicknames specified@@ -71,6 +75,7 @@   , rendNicks       = HashSet.empty   , rendMyNicks     = HashSet.empty   , rendPalette     = defaultPalette+  , rendAccounts    = HashMap.empty   }  @@ -207,8 +212,14 @@       myNicks = rendMyNicks rp       rm      = NormalRender -      who n   = string (view palSigil pal) sigils <>-                coloredUserInfo pal rm myNicks n+      who n   = string (view palSigil pal) sigils <> ui+        where+          baseUI    = coloredUserInfo pal rm myNicks n+          ui = case rendAccounts rp ^? ix (userNick n) . uhAccount of+                 Just acct+                   | Text.null acct -> "~" <> baseUI+                   | mkId acct /= userNick n -> baseUI <> "(" <> text' defAttr (cleanText acct) <> ")"+                 _ -> baseUI   in   case body of     Join       {} -> mempty@@ -321,8 +332,19 @@       nicks   = rendNicks rp       rm      = DetailedRender -      who n = string (view palSigil pal) sigils <>-              coloredUserInfo pal rm myNicks n+      plainWho = coloredUserInfo pal rm myNicks++      who n =+        -- sigils+        string (view palSigil pal) sigils <>++        -- nick!user@host+        plainWho n <>++        case rendAccounts rp ^? ix (userNick n) . uhAccount of+          Just acct+            | not (Text.null acct) -> text' quietAttr ("(" <> cleanText acct <> ")")+          _ -> ""   in   case body of     Nick old new ->@@ -333,21 +355,21 @@      Join nick _chan acct ->       string quietAttr "join " <>-      coloredUserInfo pal rm myNicks nick <>+      plainWho nick <>       if Text.null acct         then mempty-        else " " <> text' quietAttr (cleanText acct)+        else text' quietAttr ("(" <> cleanText acct <> ")")      Part nick _chan mbreason ->       string quietAttr "part " <>-      coloredUserInfo pal rm myNicks nick <>+      who nick <>       foldMap (\reason -> string quietAttr " (" <>                           parseIrcText reason <>                           string quietAttr ")") mbreason      Quit nick mbreason ->       string quietAttr "quit "   <>-      coloredUserInfo pal rm myNicks nick   <>+      who nick <>       foldMap (\reason -> string quietAttr " (" <>                           parseIrcText reason <>                           string quietAttr ")") mbreason@@ -362,7 +384,7 @@      Topic src _dst txt ->       string quietAttr "tpic " <>-      coloredUserInfo pal rm myNicks src <>+      who src <>       " changed the topic: " <>       parseIrcText txt 
src/Client/Image/Textbox.hs view
@@ -107,7 +107,9 @@   leftCur = take (view Edit.pos cur) (view Edit.text cur)    -- ["one","two"] "three" --> "two one three"-  leftLen = length leftCur + length leftImgs + sum (map imageWidth leftImgs)+  leftLen = length leftImgs -- separators+          + sum (map imageWidth leftImgs)+          + imageWidth (parseIrcText' True (Text.pack leftCur))    rndr = renderLine st pal myNick nicks macros 
src/Client/State.hs view
@@ -325,15 +325,22 @@       , rendNicks       = HashSet.fromList (channelUserList network channel' st)       , rendMyNicks     = highlights       , rendPalette     = clientPalette st+      , rendAccounts    = accounts       }      -- on failure returns mempty/""-    possibleStatusModes     = view (clientConnection network . csStatusMsg) st+    cs = st ^?! clientConnection network+    possibleStatusModes     = view csStatusMsg cs     (statusModes, channel') = splitStatusMsgModes possibleStatusModes channel     importance              = msgImportance msg st     highlights              = clientHighlightsNetwork network st +    accounts =+      if view (csSettings . ssShowAccounts) cs+      then view csUsers cs+      else HashMap.empty + recordLogLine ::   ClientMessage {- ^ message      -} ->   Identifier    {- ^ target       -} ->@@ -678,7 +685,7 @@   compile     defaultCompOpt     defaultExecOpt{captureGroups=False}-    "https?://([[:alnum:]-]+\\.)*([[:alnum:]-]+)(:[[:digit:]]+)?(/[-0-9a-zA-Z$_.+!*'(),%?=:@/;~#]*)?|\+    "https?://([[:alnum:]-]+\\.)*([[:alnum:]-]+)(:[[:digit:]]+)?(/[-0-9a-zA-Z$_.+!*'(),%?&=:@/;~#]*)?|\     \<https?://[^>]*>|\     \\\(https?://[^\\)]*\\)" 
src/Client/State/Network.hs view
@@ -1,4 +1,4 @@-{-# Language TemplateHaskell, OverloadedStrings, BangPatterns #-}+{-# Language BlockArguments, TemplateHaskell, OverloadedStrings, BangPatterns #-}  {-| Module      : Client.State.Network@@ -47,12 +47,6 @@   , csMessageHooks   , csAuthenticationState -  -- * User information-  , UserAndHost(..)-  , uhUser-  , uhHost-  , uhAccount-   -- * Cross-message state   , Transaction(..) @@ -83,6 +77,9 @@ import           Client.Configuration.ServerSettings import           Client.Network.Async import           Client.State.Channel+import           Client.UserHost+import           Client.Hook (MessageHook)+import           Client.Hooks (messageHooks) import           Control.Lens import           Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap@@ -125,7 +122,7 @@   , _csUsers        :: !(HashMap Identifier UserAndHost) -- ^ user and hostname for other nicks   , _csModeCount    :: !Int -- ^ maximum mode changes per MODE command   , _csNetwork      :: !Text -- ^ name of network connection-  , _csMessageHooks :: ![Text] -- ^ names of message hooks to apply to this connection+  , _csMessageHooks :: ![MessageHook] -- ^ names of message hooks to apply to this connection   , _csAuthenticationState :: !AuthenticateState    -- Timing information@@ -135,7 +132,6 @@   , _csLastReceived :: !(Maybe UTCTime) -- ^ time of last message received   , _csCertificate  :: ![Text]   }-  deriving Show  -- | State of the authentication transaction data AuthenticateState@@ -146,14 +142,6 @@   | AS_ExternalStarted    -- ^ EXTERNAL mode initiated   deriving Show --- | Pair of username and hostname. Empty strings represent missing information.-data UserAndHost = UserAndHost-  { _uhUser    :: {-# UNPACK #-}!Text -- ^ username-  , _uhHost    :: {-# UNPACK #-}!Text -- ^ hostname-  , _uhAccount :: {-# UNPACK #-}!Text -- ^ services account-  }-  deriving Show- -- | Status of the ping timer data PingStatus   = PingSent !UTCTime -- ^ ping sent at given time, waiting for pong@@ -178,7 +166,6 @@   deriving Show  makeLenses ''NetworkState-makeLenses ''UserAndHost makePrisms ''Transaction makePrisms ''PingStatus makePrisms ''TimedAction@@ -271,7 +258,7 @@   , _csModeCount    = 3   , _csUsers        = HashMap.empty   , _csNetwork      = network-  , _csMessageHooks = view ssMessageHooks settings+  , _csMessageHooks = buildMessageHooks (view ssMessageHooks settings)   , _csAuthenticationState = AS_None   , _csPingStatus   = ping   , _csLatency      = Nothing@@ -280,6 +267,10 @@   , _csCertificate  = []   } +buildMessageHooks :: [HookConfig] -> [MessageHook]+buildMessageHooks = mapMaybe \(HookConfig name args) ->+  do hookFun <- HashMap.lookup name messageHooks+     hookFun args  -- | Used for updates to a 'NetworkState' that require no reply. --@@ -304,10 +295,17 @@     Ping args -> ([ircPong args], cs)     Pong _    -> noReply $ doPong msgWhen cs     Join user chan acct ->-           noReply-         $ recordUser user acct+         ( reply+         , recordUser user acct          $ overChannel chan (joinChannel (userNick user))-         $ createOnJoin user chan cs+         $ createOnJoin user chan cs )+     where+       showAccounts = view (csSettings . ssShowAccounts) cs+       reply+         | userNick user == view csNick cs =+              ircMode chan [] :+              [ircWho [idText chan, "%tuhna,616"] | showAccounts ]+         | otherwise = []      Account user acct ->            noReply@@ -691,7 +689,9 @@   NetworkState         {- ^ network state  -} ->   [(Text, Maybe Text)] {- ^ server caps    -} ->   [Text]               {- ^ caps to enable -}-selectCaps cs offered = supported `intersect` Map.keys capMap+selectCaps cs offered = (supported `intersect` Map.keys capMap)+                        `union`+                        view (csSettings . ssCapabilities) cs   where     capMap = Map.fromList offered 
+ src/Client/UserHost.hs view
@@ -0,0 +1,31 @@+{-# Language TemplateHaskell #-}+{-|+Module      : Client.UserHost+Description : Type for tracking user, host and account+Copyright   : (c) Eric Mertens, 2019+License     : ISC+Maintainer  : emertens@gmail.com++-}++module Client.UserHost+  (+  -- * User information+    UserAndHost(..)+  , uhUser+  , uhHost+  , uhAccount+  ) where++import           Control.Lens (makeLenses)+import           Data.Text (Text)++-- | Pair of username and hostname. Empty strings represent missing information.+data UserAndHost = UserAndHost+  { _uhUser    :: {-# UNPACK #-}!Text -- ^ username+  , _uhHost    :: {-# UNPACK #-}!Text -- ^ hostname+  , _uhAccount :: {-# UNPACK #-}!Text -- ^ services account+  }+  deriving (Read, Show)++makeLenses ''UserAndHost
src/Client/View/UserList.hs view
@@ -19,6 +19,7 @@ import           Client.State import           Client.State.Channel import           Client.State.Network+import           Client.UserHost import           Control.Lens import qualified Data.HashMap.Strict as HashMap import qualified Data.Map.Strict as Map