packages feed

glirc 2.15 → 2.16

raw patch · 11 files changed

+291/−158 lines, 11 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Client.Image.Palette: [_palWindowDivider] :: Palette -> Attr
+ Client.Image.Palette: palWindowDivider :: Lens' Palette Attr
+ Client.Image.StatusLine: minorStatusLineImage :: Focus -> ClientState -> Image
+ Client.State: [_clientExtraFocus] :: ClientState -> ![Focus]
+ Client.State: clientExtraFocus :: Lens' ClientState [Focus]
+ Client.State: clientExtraFocuses :: ClientState -> [Focus]
+ Client.State: clientWindowHeights :: ClientState -> (Int, Int)
- Client.Image.Palette: Palette :: Vector Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Palette
+ Client.Image.Palette: Palette :: Vector Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Palette
- Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Subfocus -> !(IntMap NetworkState) -> !Int -> !ConnectionContext -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !Vty -> !EditBox -> !Int -> !Int -> !Int -> !Bool -> !Bool -> !Bool -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ClientState
+ Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Subfocus -> ![Focus] -> !(IntMap NetworkState) -> !Int -> !ConnectionContext -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !Vty -> !EditBox -> !Int -> !Int -> !Int -> !Bool -> !Bool -> !Bool -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ClientState
- Client.State.EditBox: class HasLine c_aKQ0 where pos = (.) line pos text = (.) line text
+ Client.State.EditBox: class HasLine c_aKQH where pos = (.) line pos text = (.) line text
- Client.State.EditBox: line :: HasLine c_aKQ0 => Lens' c_aKQ0 Line
+ Client.State.EditBox: line :: HasLine c_aKQH => Lens' c_aKQH Line
- Client.State.EditBox: pos :: HasLine c_aKQ0 => Lens' c_aKQ0 Int
+ Client.State.EditBox: pos :: HasLine c_aKQH => Lens' c_aKQH Int
- Client.State.EditBox: text :: HasLine c_aKQ0 => Lens' c_aKQ0 String
+ Client.State.EditBox: text :: HasLine c_aKQH => Lens' c_aKQH String
- Client.State.EditBox.Content: class HasLine c_aKQ0 where pos = (.) line pos text = (.) line text
+ Client.State.EditBox.Content: class HasLine c_aKQH where pos = (.) line pos text = (.) line text
- Client.State.EditBox.Content: line :: HasLine c_aKQ0 => Lens' c_aKQ0 Line
+ Client.State.EditBox.Content: line :: HasLine c_aKQH => Lens' c_aKQH Line
- Client.State.EditBox.Content: pos :: HasLine c_aKQ0 => Lens' c_aKQ0 Int
+ Client.State.EditBox.Content: pos :: HasLine c_aKQH => Lens' c_aKQH Int
- Client.State.EditBox.Content: text :: HasLine c_aKQ0 => Lens' c_aKQ0 String
+ Client.State.EditBox.Content: text :: HasLine c_aKQH => Lens' c_aKQH String
- Client.View: viewLines :: ClientState -> [Image]
+ Client.View: viewLines :: Focus -> Subfocus -> ClientState -> [Image]
- Client.View.Messages: chatMessageImages :: ClientState -> [Image]
+ Client.View.Messages: chatMessageImages :: Focus -> ClientState -> [Image]

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Revision history for glirc2 +## 2.16++* Add `/splits` to show multiple chat windows simultaneously+ ## 2.15  * Add `/mentions`
README.md view
@@ -1,4 +1,4 @@-My IRC client+GLIRC - Advanced Console IRC Client =============  [![Build Status](https://secure.travis-ci.org/glguy/irc-core.svg)](http://travis-ci.org/glguy/irc-core)@@ -41,20 +41,24 @@ Client Features =============== +* All views and transformation are dynamic and don't change the underlying model. * Subsequent joins and parts fold into one line and do not scroll chat messages off the screen * Ignore support that folds ignored messages into the joins and parts. Toggle it off to see previously hidden messages * Detailed view to see all the messages in a channel in full detail with hostmask and timestamp (F2)-* Nick tab completion-* New message notification-* View ban, quiet, invex, and exception lists+* Context sensitive tab completion+* Searchable ban, quiet, invex, and exception view separate from chat messages+* Searchable user list, detailed view shows full hostmasks * WYSIWYG mIRC formatting input+* Multi-line editing+* Dynamic, in-place message searching * Chanserv integration-* Each user's nick is assigned a consistent color, when a user's nick is rendered in a chat message it uses that same color.+* Nicknames in chat messages are colored to match messages from that nickname * Support for /STATUSMSG/ messages (messages only voice or op users can see) * Run commands upon connection-* Ban lists don't obstruct chat messages-* Ban list and user list are searchable+* Command macros * CERTFP and SASL authentication+* Split-screen view+* Configurable color palette  TLS ===@@ -105,7 +109,8 @@  -- Override the defaults when connecting to specific servers servers:-  * hostname:      "chat.freenode.net"+  * name: "fn"+    hostname:      "chat.freenode.net"     sasl-username: "someuser"     sasl-password: "somepass"     socks-host:    "socks5.example.com"@@ -129,6 +134,10 @@       * "clear"       * "znc *status clearbuffer $channel" +  * name: "mysplits"+    commands:+      * "splits fn:#haskell fn:#haskell-offtopic"+   -- Example use of macro in combination with an extension   * name: "extra"     commands:@@ -155,61 +164,68 @@ Configuration sections: -------- -* `defaults` - These settings are used for all connections-* `servers` - These settings are used to override defaults when the hostname matches-* `palette` - Client color overrides-* `window-names` - text - Names of windows (typically overridden on non QWERTY layouts)-* `nick-padding` - nonnegative integer - Nicks are padded until they have the specified length-* `extra-highlights` - list of text - Extra words/nicks to highlight-* `extensions` - list of text - Filenames of extension to load-* `url-opener` - text - Command to execute with URL parameter for `/url` e.g. gnome-open on GNOME or open on macOS-* `ignores` - list of text - Initial list of nicknames to ignore-* `activity-bar` - yes or no - Initial setting for visibility of activity bar (default no)+| setting            | type                | description                                                                                |+|--------------------|---------------------|--------------------------------------------------------------------------------------------|+| `defaults`         | server              | These settings are used for all connections                                                |+| `servers`          | list of servers     | These settings are used to override defaults when the hostname matches                     |+| `palette`          | palette             | Client color overrides                                                                     |+| `window-names`     | text                | Names of windows (typically overridden on non QWERTY layouts)                              |+| `nick-padding`     | nonnegative integer | Nicks are padded until they have the specified length                                      |+| `extra-highlights` | list of text        | Extra words/nicks to highlight                                                             |+| `extensions`       | list of text        | Filenames of extension to load                                                             |+| `url-opener`       | text                | Command to execute with URL parameter for `/url` e.g. gnome-open on GNOME or open on macOS |+| `ignores`          | list of text        | Initial list of nicknames to ignore                                                        |+| `activity-bar`     | yes or no           | Initial setting for visibility of activity bar (default no)                                | -Settings---------+Server Settings+--------------- -* `name` - text - name of server entry, defaults to `hostname`-* `hostname` - text - hostname used to connect and to specify the server-* `port` - number - port number, defaults to 6667 without TLS and 6697 with TLS-* `nick` - text or list of text - nicknames to try in order-* `username` - text - username-* `realname` - text - realname / GECOS-* `password` - text - server password-* `sasl-username` - text - SASL username-* `sasl-password` - text - SASL password-* `tls` - yes/yes-insecure/no - use TLS to connect (insecure mode disables certificate checks)-* `tls-client-cert` - text - path to TLS client certificate-* `tls-client-key` - text - path to TLS client key-* `connect-cmds` - list of text - client commands to send upon connection-* `socks-host` - text - hostname of SOCKS proxy to connect through-* `socks-port` - number - port number of SOCKS proxy to connect through-* `server-certificates` - list of text - list of CA certificates to use when validating certificates-* `chanserv-channels` - list of text - list of channels with chanserv op permission-* `flood-penalty` - number - cost in seconds per message-* `flood-threshold` - number - threshold of seconds for burst-* `message-hooks` - list of text - names of hooks to enable+| setting               | type                 | description                                                    |+|-----------------------|----------------------|----------------------------------------------------------------|+| `name`                | text                 | name of server entry, defaults to `hostname`                   |+| `hostname`            | text                 | hostname used to connect and to specify the server             |+| `port`                | number               | port number, defaults to 6667 without TLS and 6697 with TLS    |+| `nick`                | text or list of text | nicknames to try in order                                      |+| `username`            | text                 | server username                                                |+| `realname`            | text                 | real name / GECOS                                              |+| `password`            | text                 | server password                                                |+| `sasl-username`       | text                 | SASL username                                                  |+| `sasl-password`       | text                 | SASL password                                                  |+| `tls`                 | yes/yes-insecure/no  | use TLS to connect (insecure mode disables certificate checks) |+| `tls-client-cert`     | text                 | path to TLS client certificate                                 |+| `tls-client-key`      | text                 | path to TLS client key                                         |+| `connect-cmds`        | list of text         | client commands to send upon connection                        |+| `socks-host`          | text                 | hostname of SOCKS proxy to connect through                     |+| `socks-port`          | number               | port number of SOCKS proxy to connect through                  |+| `server-certificates` | list of text         | list of CA certificates to use when validating certificates    |+| `chanserv-channels`   | list of text         | list of channels with chanserv op permission                   |+| `flood-penalty`       | number               | cost in seconds per message                                    |+| `flood-threshold`     | number               | threshold in seconds for burst                                 |+| `message-hooks`       | list of text         | names of hooks to enable                                       |  Palette ------- -* `nick-colors` - List of attr - Use for nick highlights-* `self` - attr - attr of our own nickname(s) outside of mentions-* `self-highlight` - attr - attr of our own nickname(s) in mentions-* `time` - attr - attr for timestamp-* `meta` - attr - attr for metadata-* `sigil` - attr - attr for sigils-* `label` - attr - attr for information labels-* `latency` - attr - attr for latency time-* `error` - attr - attr for error messages-* `textbox` - attr - attr for textbox edges-* `window-name` - attr - attr for current window name-* `activity` - attr - attr for activity notification-* `mention` - attr - attr for mention notification-* `command` - attr - attr for recognized command-* `command-prefix` - attr - attr for prefix of known command-* `command-ready` - attr - attr for recognized command with arguments filled-* `command-placeholder` - attr - attr for command argument placeholder+| entry                 | type         | description                              |+|-----------------------|--------------|------------------------------------------|+| `nick-colors`         | list of attr | Use for nick highlights                  |+| `self`                | attr         | our own nickname(s) outside of mentions  |+| `self-highlight`      | attr         | our own nickname(s) in mentions          |+| `time`                | attr         | timestamp on messages                    |+| `meta`                | attr         | metadata (joins/parts/etc.)              |+| `sigil`               | attr         | sigils (+@)                              |+| `label`               | attr         | information labels                       |+| `latency`             | attr         | latency time                             |+| `error`               | attr         | error messages                           |+| `textbox`             | attr         | textbox edges (^$)                       |+| `window-name`         | attr         | current window name                      |+| `activity`            | attr         | activity notification                    |+| `mention`             | attr         | mention notification                     |+| `command`             | attr         | recognized command                       |+| `command-prefix`      | attr         | prefix of known command                  |+| `command-ready`       | attr         | recognized command with arguments filled |+| `command-placeholder` | attr         | command argument placeholder             |+| `window-divider`      | attr         | the dividing line between split windows  |  Text Attributes ---------------@@ -253,6 +269,7 @@ * `/extension <extension name> <params...>` - Send the given params to the named extension * `/exec [-n network] [-c channel] <command> <arguments...>` - Execute a command, If no network or channel are provided send output to client window, if network and channel are provided send output as messages, if network is provided send output as raw IRC messages. * `/url [n]` - Execute url-opener on the nth URL in the current window (defaults to first)+* `/splits [focuses...]` - Enable split-screen view. Focuses should be space delimited list of NETWORK:CHANNEL  Connection commands 
glirc.cabal view
@@ -1,5 +1,5 @@ name:                glirc-version:             2.15+version:             2.16 synopsis:            Console IRC client description:         Console IRC client license:             ISC
src/Client/Commands.hs view
@@ -45,9 +45,11 @@ import           Control.Monad import           Data.Foldable import           Data.HashSet (HashSet)+import           Data.List (nub) import           Data.List.NonEmpty (NonEmpty((:|))) import           Data.List.Split import qualified Data.HashMap.Strict as HashMap+import qualified Data.Map as Map import           Data.Text (Text) import qualified Data.Text as Text import           Data.Monoid ((<>))@@ -370,6 +372,7 @@       \When \^Bnumber\^B is omitted it defaults to \^B1\^B. The number selects the URL to open counting back from the most recent.\n"     $ ClientCommand cmdUrl noClientTab     )+   , ( pure "help"     , Command (OptTokenArg "command" NoArg)       "Show command documentation.\n\@@ -379,6 +382,19 @@     $ ClientCommand cmdHelp tabHelp     ) +  , ( pure "splits"+    , Command (RemainingArg "focuses")+      "Set the extra message view splits.\n\+      \\n\+      \\^Bfocues\^B: space delimited 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"+    $ ClientCommand cmdSplits tabSplits+    )+   --   -- Network commands   --@@ -824,6 +840,38 @@ cmdHelp st mb = commandSuccess (changeSubfocus focus st)   where     focus = FocusHelp (fmap (Text.pack . fst) mb)++-- | Tab completion for @/splits@+tabSplits :: Bool -> ClientCommand String+tabSplits isReversed st rest+  | all (' '==) rest =+     do let cmd = "/splits " ++ unwords (Text.unpack . render <$> currentExtras)+            newline = Edit.endLine cmd+        commandSuccess (set (clientTextBox . Edit.line) newline st)++  | otherwise = simpleTabCompletion id [] completions isReversed st+  where+    currentExtras = view clientExtraFocus st++    completions = map render+                $ Map.keys+                $ view clientWindows st++    render Unfocused          = "*"+    render (NetworkFocus x)   = x+    render (ChannelFocus x y) = x <> ":" <> idText y++-- | Implementation of @/splits@+cmdSplits :: ClientCommand String+cmdSplits st str = commandSuccess (set clientExtraFocus extras st)+  where+    extras = nub (map toFocus (words str))++    toFocus x =+      case break (==':') x of+        ("*","")     -> Unfocused+        (net,"")     -> NetworkFocus (Text.pack net)+        (net,_:chan) -> ChannelFocus (Text.pack net) (mkId (Text.pack chan))  tabHelp :: Bool -> ClientCommand String tabHelp isReversed st _ = simpleTabCompletion id [] commandNames isReversed st
src/Client/Image.hs view
@@ -11,11 +11,14 @@ -} module Client.Image (clientPicture) where +import           Client.Image.Palette import           Client.Image.StatusLine import           Client.Image.Textbox import           Client.State+import           Client.State.Focus import           Client.View import           Control.Lens+import           Data.List import           Graphics.Vty (Background(..), Picture(..), Cursor(..)) import           Graphics.Vty.Image @@ -34,16 +37,34 @@ clientImage ::   ClientState ->   (Int, Image, ClientState) -- ^ text box cursor position, image, updated state-clientImage st = (pos, img, st')+clientImage st = (pos, img, st'')   where-    (mp, st') = messagePane st-    (pos, tbImg) = textboxImage st'-    img = mp <-> statusLineImage st' <-> tbImg+    (st', mp) = messagePane mainHeight focus (view clientSubfocus st) st+    (st'', extras) = mapAccumL renderExtra st' splits -messagePane :: ClientState -> (Image, ClientState)-messagePane st = (img, st')+    (pos, tbImg) = textboxImage st''+    img = vertCat extras <->+          mp <->+          statusLineImage st'' <->+          tbImg++    focus = view clientFocus st++    (mainHeight, splitHeight) = clientWindowHeights st+    splits                    = clientExtraFocuses st++    renderExtra stIn focus1 = (stOut, outImg)+      where+        (stOut,msgImg) = messagePane splitHeight focus1 FocusMessages stIn+        pal = clientPalette st+        divider = view palWindowDivider pal+        outImg = msgImg <-> minorStatusLineImage focus1 st+                        <-> charFill divider ' ' (view clientWidth st) 1++messagePane :: Int -> Focus -> Subfocus -> ClientState -> (ClientState, Image)+messagePane h focus subfocus st = (st', img)   where-    images = viewLines st+    images = viewLines focus subfocus st     vimg   = assemble emptyImage images     vimg1  = cropBottom h vimg     img    = pad 0 (h - imageHeight vimg1) 0 0 vimg1@@ -59,11 +80,6 @@     scroll = view clientScroll st     vh     = h + scroll -    reservedLines-      | view clientActivityBar st = 3-      | otherwise                 = 2--    h      = view clientHeight st - reservedLines     w      = view clientWidth st  lineWrap :: Int -> Image -> Image
src/Client/Image/Palette.hs view
@@ -32,6 +32,7 @@   , palCommandPlaceholder   , palCommandPrefix   , palCommandError+  , palWindowDivider    , paletteMap @@ -64,6 +65,7 @@   , _palCommandPrefix :: Attr -- ^ prefix of known command   , _palCommandError  :: Attr -- ^ unknown command   , _palCommandPlaceholder :: Attr -- ^ command argument placeholder+  , _palWindowDivider :: Attr -- ^ Divider between split windows   }   deriving Show @@ -90,6 +92,7 @@   , _palCommandPrefix           = withForeColor defAttr blue   , _palCommandError            = withForeColor defAttr red   , _palCommandPlaceholder      = withStyle defAttr reverseVideo+  , _palWindowDivider           = withStyle defAttr reverseVideo   }  -- | Default nick highlighting colors that look nice in my dark solarized@@ -119,4 +122,5 @@   , ("command-placeholder", Lens palCommandPlaceholder)   , ("command-prefix"   , Lens palCommandPrefix)   , ("command-error"    , Lens palCommandError)+  , ("window-divider"   , Lens palWindowDivider)   ]
src/Client/Image/StatusLine.hs view
@@ -1,3 +1,4 @@+{-# Language BangPatterns #-} {-| Module      : Client.Image.StatusLine Description : Renderer for status line@@ -12,6 +13,7 @@ -} module Client.Image.StatusLine   ( statusLineImage+  , minorStatusLineImage   ) where  import           Client.Image.Palette@@ -47,15 +49,19 @@       , latencyImage st       ] +minorStatusLineImage :: Focus -> ClientState -> Image+minorStatusLineImage focus st =+  content <|> charFill defAttr '─' fillSize 1+  where+    content = infoBubble (focusImageMajor focus st)+    fillSize = max 0 (view clientWidth st - imageWidth content) + scrollImage :: ClientState -> Image scrollImage st   | 0 == view clientScroll st = emptyImage-  | otherwise = horizCat-      [ string defAttr "─("-      , string attr "scroll"-      , string defAttr ")"-      ]+  | otherwise = infoBubble+              $ string attr "scroll"   where     pal  = clientPalette st     attr = view palLabel pal@@ -168,7 +174,6 @@         Just cs -> string (view palSigil pal) myChanModes                <|> text' defAttr (idText nick)                <|> parens defAttr (string defAttr ('+' : view csModes cs))-               <|> char defAttr '─'           where             nick      = view csNick cs             myChanModes =@@ -178,57 +183,47 @@   focusImage :: ClientState -> Image-focusImage st = parens defAttr majorImage <|> renderedSubfocus+focusImage st =+    infoBubble (focusImageMajor focus st) <|>+    foldMap infoBubble (viewSubfocusLabel pal subfocus)   where-    majorImage = horizCat-      [ char (view palWindowName pal) windowName-      , char defAttr ':'-      , renderedFocus-      ] -    pal         = clientPalette st+    !pal        = clientPalette st     focus       = view clientFocus st+    subfocus    = view clientSubfocus st++focusImageMajor :: Focus -> ClientState -> Image+focusImageMajor focus st =+  horizCat+    [ char (view palWindowName pal) windowName+    , char defAttr ':'+    , viewFocusLabel st focus+    ]+  where+    !pal        = clientPalette st     windowNames = clientWindowNames st      windowName = fromMaybe '?'                $ do i <- Map.lookupIndex focus (view clientWindows st)                     preview (ix i) windowNames -    subfocusName =-      case view clientSubfocus st of-        FocusMessages -> Nothing-        FocusWindows  -> Just $ string (view palLabel pal) "windows"-        FocusInfo     -> Just $ string (view palLabel pal) "info"-        FocusUsers    -> Just $ string (view palLabel pal) "users"-        FocusMentions -> Just $ string (view palLabel pal) "mentions"-        FocusPalette  -> Just $ string (view palLabel pal) "palette"-        FocusHelp mb  -> Just $ string (view palLabel pal) "help" <|>-                                foldMap (\cmd -> char defAttr ':' <|>-                                            text' (view palLabel pal) cmd) mb-        FocusMasks m  -> Just $ horizCat-          [ string (view palLabel pal) "masks"-          , char defAttr ':'-          , char (view palLabel pal) m-          ] -    renderedSubfocus =-      foldMap (\name -> horizCat-          [ string defAttr "─("-          , name-          , char defAttr ')'-          ]) subfocusName+parens :: Attr -> Image -> Image+parens attr i = char attr '(' <|> i <|> char attr ')' -    renderedFocus =-      case focus of-        Unfocused ->-          char (view palError pal) '*'-        NetworkFocus network ->-          text' (view palLabel pal) network-        ChannelFocus network channel ->-          text' (view palLabel pal) network <|>-          char defAttr ':' <|>-          text' (view palLabel pal) (idText channel) <|>-          channelModesImage network channel st+viewFocusLabel :: ClientState -> Focus -> Image+viewFocusLabel st focus =+  let !pal = clientPalette st in+  case focus of+    Unfocused ->+      char (view palError pal) '*'+    NetworkFocus network ->+      text' (view palLabel pal) network+    ChannelFocus network channel ->+      text' (view palLabel pal) network <|>+      char defAttr ':' <|>+      text' (view palLabel pal) (idText channel) <|>+      channelModesImage network channel st  channelModesImage :: Text -> Identifier -> ClientState -> Image channelModesImage network channel st =@@ -239,5 +234,20 @@       where (modes,args) = unzip (Map.toList modeMap)     _ -> emptyImage -parens :: Attr -> Image -> Image-parens attr i = char attr '(' <|> i <|> char attr ')'+viewSubfocusLabel :: Palette -> Subfocus -> Maybe Image+viewSubfocusLabel pal subfocus =+  case subfocus of+    FocusMessages -> Nothing+    FocusWindows  -> Just $ string (view palLabel pal) "windows"+    FocusInfo     -> Just $ string (view palLabel pal) "info"+    FocusUsers    -> Just $ string (view palLabel pal) "users"+    FocusMentions -> Just $ string (view palLabel pal) "mentions"+    FocusPalette  -> Just $ string (view palLabel pal) "palette"+    FocusHelp mb  -> Just $ string (view palLabel pal) "help" <|>+                            foldMap (\cmd -> char defAttr ':' <|>+                                        text' (view palLabel pal) cmd) mb+    FocusMasks m  -> Just $ horizCat+      [ string (view palLabel pal) "masks"+      , char defAttr ':'+      , char (view palLabel pal) m+      ]
src/Client/State.hs view
@@ -24,6 +24,7 @@   , clientEvents   , clientVty   , clientFocus+  , clientExtraFocus   , clientConnectionContext   , clientConfig   , clientScroll@@ -60,6 +61,9 @@   , clientWindowNames   , clientPalette +  , clientExtraFocuses+  , clientWindowHeights+   -- * Add messages to buffers   , recordChannelMessage   , recordNetworkMessage@@ -110,6 +114,7 @@ import           Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import           Data.IntMap (IntMap)+import           Data.List import           Data.Maybe import           Data.Map (Map) import qualified Data.Map as Map@@ -136,6 +141,7 @@   , _clientPrevFocus         :: !Focus              -- ^ previously focused buffer   , _clientFocus             :: !Focus              -- ^ currently focused buffer   , _clientSubfocus          :: !Subfocus           -- ^ sec+  , _clientExtraFocus        :: ![Focus]            -- ^ extra messages windows to view    , _clientConnections       :: !(IntMap NetworkState) -- ^ state of active connections   , _clientNextConnectionId  :: !Int@@ -222,6 +228,7 @@         , _clientPrevFocus         = Unfocused         , _clientFocus             = Unfocused         , _clientSubfocus          = FocusMessages+        , _clientExtraFocus        = []         , _clientConnectionContext = cxt         , _clientConfig            = cfg         , _clientScroll            = 0@@ -457,14 +464,18 @@ clientTick = set clientBell False . markSeen  --- | Mark the messages on the current window as seen.+-- | Mark the messages on the current window (and any splits) as seen. markSeen :: ClientState -> ClientState markSeen st =   case view clientSubfocus st of-    FocusMessages ->-       overStrict (clientWindows . ix (view clientFocus st)) windowSeen st+    FocusMessages -> foldl' aux st focuses     _             -> st+  where+    aux acc focus = overStrict (clientWindows . ix focus) windowSeen acc +    focuses = view clientFocus st+            : view clientExtraFocus st+ -- | Add the textbox input to the edit history and clear the textbox. consumeInput :: ClientState -> ClientState consumeInput = over clientTextBox Edit.success@@ -474,8 +485,10 @@ currentCompletionList st =   case view clientFocus st of     NetworkFocus network      -> networkChannelList network st-    ChannelFocus network chan -> networkChannelList network st-                              ++ channelUserList network chan st+    ChannelFocus network chan ->+         chan -- might be a disconnected channel or a private chat+       : networkChannelList network st+      ++ channelUserList network chan st     _                         -> []  networkChannelList ::@@ -653,7 +666,40 @@  -- | Compute the number of lines in a page at the current window size scrollAmount :: ClientState -> Int-scrollAmount st = max 1 (view clientHeight st - 2)+scrollAmount st = max 1 (min main extra)+  where+    (main,extra) = clientWindowHeights st+++++-- | List of extra focuses to display as split windows+clientExtraFocuses :: ClientState -> [Focus]+clientExtraFocuses st =+  case view clientSubfocus st of+    FocusMessages -> view clientFocus st `delete` view clientExtraFocus st+    _             -> []++-- | Number of lines to allocate for the focused window and the+-- main window. This doesn't include the textbox, activity bar,+-- or status line.+clientWindowHeights ::+  ClientState {- ^ client state              -} ->+  (Int,Int)   {- ^ main height, extra height -}+clientWindowHeights st = (max 0 (h - overhead - extras*d), max 0 (d-overhead))+  where+    d        = h `quot` (1 + extras)++    h        = max 0 (view clientHeight st - reservedLines) -- lines available++    extras   = length (clientExtraFocuses st)++    overhead = 2 -- status line and textbox/divider++    reservedLines+      | view clientActivityBar st = 1 -- activity bar+      | otherwise                 = 0+   ------------------------------------------------------------------------
src/Client/View.hs view
@@ -26,9 +26,9 @@ import           Control.Lens import           Graphics.Vty.Image -viewLines :: ClientState -> [Image]-viewLines !st =-  case (view clientFocus st, view clientSubfocus st) of+viewLines :: Focus -> Subfocus -> ClientState -> [Image]+viewLines focus subfocus !st =+  case (focus, subfocus) of     (ChannelFocus network channel, FocusInfo) ->       channelInfoImages network channel st     (ChannelFocus network channel, FocusUsers)@@ -41,6 +41,6 @@     (_, FocusPalette) -> paletteViewLines pal     (_, FocusHelp mb) -> helpImageLines mb pal -    _ -> chatMessageImages st+    _ -> chatMessageImages focus st   where     pal = clientPalette st
src/Client/View/Mentions.hs view
@@ -14,76 +14,63 @@   ( mentionsViewLines   ) where -import           Client.Image.Palette+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.Text (Text) import           Data.Time (UTCTime)-import           Irc.Identifier (idText) 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 :: ClientState -> [Image]-mentionsViewLines st = addMarkers pal entries+mentionsViewLines st = addMarkers st entries    where-    pal = clientPalette st-     names = clientWindowNames st ++ repeat '?'      detail = view clientDetailView st      entries = merge-              [windowEntries detail n net (idText chan) v-              | (n,(ChannelFocus net chan, v))+              [windowEntries detail n focus v+              | (n,(focus, v))                 <- names `zip` Map.toList (view clientWindows st) ]  data MentionLine = MentionLine   { mlTimestamp  :: UTCTime   , mlWindowName :: Char-  , mlNetwork    :: Text-  , mlChannel    :: Text+  , mlFocus      :: Focus   , mlImage      :: Image   }  addMarkers ::-  Palette       {- ^ palette                           -} ->+  ClientState   {- ^ client state                      -} ->   [MentionLine] {- ^ list of mentions in time order    -} ->   [Image]       {- ^ mention images and channel labels -} addMarkers _ [] = []-addMarkers !pal (!ml : xs) =-  mlImage ml : map mlImage same ++ windowMarker : addMarkers pal rest+addMarkers !st (!ml : xs)+  = map mlImage (ml:same)+ ++ minorStatusLineImage (mlFocus ml) st+  : addMarkers st rest   where-    isSame ml' = mlWindowName ml == mlWindowName ml'-              && mlNetwork    ml == mlNetwork    ml'-              && mlChannel    ml == mlChannel    ml'+    isSame ml' = mlFocus ml == mlFocus ml'      (same,rest) = span isSame xs -    windowMarker = char (view palWindowName pal) (mlWindowName ml) <|>-                   char defAttr ':' <|>-                   text' (view palLabel pal) (mlNetwork ml) <|>-                   char defAttr ':' <|>-                   text' (view palLabel pal) (mlChannel ml)- windowEntries ::   Bool   {- ^ detailed view -} ->   Char   {- ^ window name   -} ->-  Text   {- ^ network name  -} ->-  Text   {- ^ channel name  -} ->+  Focus  {- ^ window focus  -} ->   Window {- ^ window        -} ->   [MentionLine]-windowEntries !detailed name net chan w =+windowEntries !detailed name focus w =   [ MentionLine       { mlTimestamp  = view wlTimestamp l       , mlWindowName = name-      , mlNetwork    = net-      , mlChannel    = chan+      , mlFocus      = focus       , mlImage      = if detailed then view wlFullImage l else view wlImage l       }   | l <- view winMessages w
src/Client/View/Messages.hs view
@@ -17,6 +17,7 @@ import           Client.Image.Palette import           Client.Image.Message import           Client.State+import           Client.State.Focus import           Client.State.Network import           Client.State.Window import           Client.Message@@ -24,14 +25,14 @@ import           Irc.Identifier import           Graphics.Vty.Image -chatMessageImages :: ClientState -> [Image]-chatMessageImages st = windowLineProcessor focusedMessages+chatMessageImages :: Focus -> ClientState -> [Image]+chatMessageImages focus st = windowLineProcessor focusedMessages   where     matcher = clientMatcher st      focusedMessages         = filter (views wlText matcher)-        $ view (clientWindows . ix (view clientFocus st) . winMessages) st+        $ view (clientWindows . ix focus . winMessages) st      windowLineProcessor