packages feed

glirc 2.20.2.1 → 2.20.3

raw patch · 10 files changed

+426/−255 lines, 10 filesdep ~gitrevPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: gitrev

API changes (from Hackage documentation)

- Client.Image.StatusLine: activityBarImage :: ClientState -> Image
+ Client.Commands: CommandSection :: Text -> [Command] -> CommandSection
+ Client.Commands: [cmdSectionCmds] :: CommandSection -> [Command]
+ Client.Commands: [cmdSectionName] :: CommandSection -> Text
+ Client.Commands: data CommandSection
+ Client.State: [_clientActivityReturn] :: ClientState -> !Focus
+ Client.State: clientActivityReturn :: Lens' ClientState Focus
- Client.Commands: commandsList :: [Command]
+ Client.Commands: commandsList :: [CommandSection]
- Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Subfocus -> ![Focus] -> !(IntMap NetworkState) -> !NetworkId -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !EditBox -> !Int -> !Int -> !Int -> !Int -> !Bool -> !Bool -> !Bool -> Maybe Regex -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ![LogLine] -> 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 -> !Bool -> Maybe Regex -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ![LogLine] -> ClientState
- Client.State.EditBox: class HasLine c_aLuN where pos = (.) line pos text = (.) line text
+ Client.State.EditBox: class HasLine c_aLxf where pos = (.) line pos text = (.) line text
- Client.State.EditBox: line :: HasLine c_aLuN => Lens' c_aLuN Line
+ Client.State.EditBox: line :: HasLine c_aLxf => Lens' c_aLxf Line
- Client.State.EditBox: pos :: HasLine c_aLuN => Lens' c_aLuN Int
+ Client.State.EditBox: pos :: HasLine c_aLxf => Lens' c_aLxf Int
- Client.State.EditBox: text :: HasLine c_aLuN => Lens' c_aLuN String
+ Client.State.EditBox: text :: HasLine c_aLxf => Lens' c_aLxf String
- Client.State.EditBox.Content: class HasLine c_aLuN where pos = (.) line pos text = (.) line text
+ Client.State.EditBox.Content: class HasLine c_aLxf where pos = (.) line pos text = (.) line text
- Client.State.EditBox.Content: line :: HasLine c_aLuN => Lens' c_aLuN Line
+ Client.State.EditBox.Content: line :: HasLine c_aLxf => Lens' c_aLxf Line
- Client.State.EditBox.Content: pos :: HasLine c_aLuN => Lens' c_aLuN Int
+ Client.State.EditBox.Content: pos :: HasLine c_aLxf => Lens' c_aLxf Int
- Client.State.EditBox.Content: text :: HasLine c_aLuN => Lens' c_aLuN String
+ Client.State.EditBox.Content: text :: HasLine c_aLxf => Lens' c_aLxf String

Files

ChangeLog.md view
@@ -1,5 +1,13 @@ # Revision history for glirc2 +## 2.20.3++* Nicer `/help` system, commands are grouped+* Added `/splits+` and `/splits-` for incremental updates to splits+* Jump-to-activity returns to original window after activity is visited+* Extended activity view makes use of empty space above text input+* Parse the timestamp and duration from `/whois` response+ ## 2.20.2.1  * Support `vty-5.15`
README.md view
@@ -266,14 +266,12 @@ * `/disconnect` - Forcefully terminate connection to the current server * `/reconnect` - Reconnect to the current server * `/reload [path]` - Load a new configuration file (optional path)-* `/windows [filter]` - List all open windows (filters: networks, channels, users) * `/palette` - Show the client palette * `/digraphs` - Show the table of digraphs * `/mentions` - Show all the highlighted lines across all windows * `/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 @@ -282,11 +280,15 @@  Window management +* `/windows [filter]` - List all open windows (filters: networks, channels, users) * `/focus <server>` - Change focus to server window * `/focus <server> <channel>` - Change focus to channel window * `/clear [network] [channel]` - Clear contents of current or specified window * `/ignore <nick>` - Toggle ignore of a user * `/channel <channel>` - Change focus to channel on current network (alias: `/c`)+* `/splits [focuses...]` - Enable split-screen view. Focuses should be space delimited list of NETWORK:CHANNEL+* `/splits+ [focuses...]` - Incremental addition to splits+* `/splits- [focuses...]` - Incremental removal from splits  Channel membership 
glirc.cabal view
@@ -1,5 +1,5 @@ name:                glirc-version:             2.20.2.1+version:             2.20.3 synopsis:            Console IRC client description:         Console IRC client                      .@@ -128,7 +128,7 @@                        containers           >=0.5.7  && <0.6,                        directory            >=1.2.6  && <1.4,                        filepath             >=1.4.1  && <1.5,-                       gitrev               >=1.2    && <1.3,+                       gitrev               >=1.2    && <1.4,                        hashable             >=1.2.4  && <1.3,                        HsOpenSSL            >=0.11   && <0.12,                        irc-core             >=2.2    && <2.3,
src/Client/Commands.hs view
@@ -18,6 +18,7 @@   , commandExpansion   , tabCompletion   -- * Commands+  , CommandSection(..)   , Command(..)   , CommandImpl(..)   , commands@@ -44,7 +45,7 @@ import           Control.Monad import           Data.Foldable import           Data.HashSet (HashSet)-import           Data.List (nub)+import           Data.List (nub, (\\)) import           Data.List.NonEmpty (NonEmpty((:|))) import           Data.List.Split import qualified Data.HashMap.Strict as HashMap@@ -113,6 +114,13 @@   , cmdImplementation :: CommandImpl a   } +-- | A command section is a logical grouping of commands. This allows for+-- showing more structure in the help menu system.+data CommandSection = CommandSection+  { cmdSectionName :: Text+  , cmdSectionCmds :: [Command]+  }+ -- | Consider the text entry successful and resume the client commandSuccess :: Monad m => ClientState -> m CommandResult commandSuccess = return . CommandSuccess@@ -274,67 +282,24 @@ -- | Map of built-in client commands to their implementations, tab completion -- logic, and argument structures. commands :: Recognizer Command-commands = fromCommands (expandAliases commandsList)+commands = fromCommands (expandAliases (concatMap cmdSectionCmds commandsList))   -- | Raw list of commands in the order used for @/help@-commandsList :: [Command]+commandsList :: [CommandSection] commandsList =-  ---  -- Client commands-  ---  [ Command-      (pure "connect")-      (ReqTokenArg "network" NoArg)-      "Connect to \^Bnetwork\^B by name.\n\-      \\n\-      \If no name is configured the hostname is the 'name'.\n"-    $ ClientCommand cmdConnect tabConnect -  , Command+  ------------------------------------------------------------------------+  [ CommandSection "Client commands"+  ------------------------------------------------------------------------++  [ Command       (pure "exit")       NoArg       "Exit the client immediately.\n"     $ ClientCommand cmdExit noClientTab    , Command-      (pure "focus")-      (ReqTokenArg "network" (OptTokenArg "channel" NoArg))-      "Change the focused window.\n\-      \\n\-      \When only \^Bnetwork\^B is specified this switches to the network status window.\n\-      \When \^Bnetwork\^B and \^Bchannel\^B are specified this switches to that chat window.\n\-      \\n\-      \Nicknames can be specified in the \^Bchannel\^B parameter to switch to private chat.\n\-      \See also: /channel (aliased /c) to switch to a channel on the current network.\n"-    $ ClientCommand cmdFocus tabFocus--  , Command-      (pure "clear")-      (OptTokenArg "network" (OptTokenArg "channel" NoArg))-      "Clear a window.\n\-      \\n\-      \If no arguments are provided the current window is cleared.\n\-      \If \^Bnetwork\^B is provided the that network window is cleared.\n\-      \If \^Bnetwork\^B and \^Bchannel\^B are provided that chat window is cleared.\n\-      \If \^Bnetwork\^B is provided and \^Bchannel\^B is \^B*\^O all windows for that network are cleared.\n\-      \\n\-      \If a window is cleared and no longer active that window will be removed from the client.\n"-    $ ClientCommand cmdClear tabFocus--  , Command-      (pure "reconnect")-      NoArg-      "Reconnect to the current network.\n"-    $ ClientCommand cmdReconnect noClientTab--  , Command-      (pure "ignore")-      (RemainingArg "nicks")-      "Toggle the soft-ignore on each of the space-delimited given nicknames.\n"-    $ ClientCommand cmdIgnore simpleClientTab--  , Command       (pure "reload")       (OptTokenArg "filename" NoArg)       "Reload the client configuration file.\n\@@ -352,21 +317,6 @@     $ ClientCommand cmdExtension simpleClientTab    , Command-      (pure "windows")-      (OptTokenArg "kind" NoArg)-      "Show a list of all windows with an optional argument to limit the kinds of windows listed.\n\-      \\n\-      \\^Bkind\^O: one of \^Bnetworks\^O, \^Bchannels\^O, \^Busers\^O\n\-      \\n"-    $ ClientCommand cmdWindows tabWindows--  , Command-      (pure "mentions")-      NoArg-      "Show a list of all message that were highlighted as important.\n"-    $ ClientCommand cmdMentions noClientTab--  , Command       (pure "palette")       NoArg       "Show the current palette settings and a color chart to help pick new colors.\n"@@ -421,7 +371,93 @@       \When \^Bcommand\^B is specified detailed help for that command is shown.\n"     $ ClientCommand cmdHelp tabHelp +  ------------------------------------------------------------------------+  ] , CommandSection "Connection commands"+  ------------------------------------------------------------------------++  [ Command+      (pure "connect")+      (ReqTokenArg "network" NoArg)+      "Connect to \^Bnetwork\^B by name.\n\+      \\n\+      \If no name is configured the hostname is the 'name'.\n"+    $ ClientCommand cmdConnect tabConnect+   , Command+      (pure "reconnect")+      NoArg+      "Reconnect to the current network.\n"+    $ ClientCommand cmdReconnect noClientTab++  , Command+      (pure "disconnect")+      NoArg+      "Immediately terminate the current network connection.\n\+      \\n\+      \See also: /quit /exit\n"+    $ NetworkCommand cmdDisconnect noNetworkTab++  , Command+      (pure "quit")+      (RemainingArg "reason")+      "Gracefully disconnect the current network connection.\n\+      \\n\+      \\^Breason\^B: optional quit reason\n\+      \\n\+      \See also: /disconnect /exit\n"+    $ NetworkCommand cmdQuit   simpleNetworkTab++  ------------------------------------------------------------------------+  ] , CommandSection "Window management"+  ------------------------------------------------------------------------++  [ Command+      (pure "focus")+      (ReqTokenArg "network" (OptTokenArg "channel" NoArg))+      "Change the focused window.\n\+      \\n\+      \When only \^Bnetwork\^B is specified this switches to the network status window.\n\+      \When \^Bnetwork\^B and \^Bchannel\^B are specified this switches to that chat window.\n\+      \\n\+      \Nicknames can be specified in the \^Bchannel\^B parameter to switch to private chat.\n\+      \See also: /channel (aliased /c) to switch to a channel on the current network.\n"+    $ ClientCommand cmdFocus tabFocus++  , Command+      ("channel" :| ["c"])+      (ReqTokenArg "channel" NoArg)+      "Change the focused window.\n\+      \\n\+      \Changes the focus to the \^Bchannel\^B chat window on the current network.\n\+      \\n\+      \Nicknames can be specified in the \^Bchannel\^B parameter to switch to private chat.\n\+      \See also: /focus to switch to a channel on a different network.\n\+      \See also: /focus to switch to a channel on a different network.\n"+    $ NetworkCommand cmdChannel tabChannel++  , Command+      (pure "clear")+      (OptTokenArg "network" (OptTokenArg "channel" NoArg))+      "Clear a window.\n\+      \\n\+      \If no arguments are provided the current window is cleared.\n\+      \If \^Bnetwork\^B is provided the that network window is cleared.\n\+      \If \^Bnetwork\^B and \^Bchannel\^B are provided that chat window is cleared.\n\+      \If \^Bnetwork\^B is provided and \^Bchannel\^B is \^B*\^O all windows for that network are cleared.\n\+      \\n\+      \If a window is cleared and no longer active that window will be removed from the client.\n"+    $ ClientCommand cmdClear tabFocus++  , Command+      (pure "windows")+      (OptTokenArg "kind" NoArg)+      "Show a list of all windows with an optional argument to limit the kinds of windows listed.\n\+      \\n\+      \\^Bkind\^O: one of \^Bnetworks\^O, \^Bchannels\^O, \^Busers\^O\n\+      \\n"+    $ ClientCommand cmdWindows tabWindows++  , Command       (pure "splits")       (RemainingArg "focuses")       "Set the extra message view splits.\n\@@ -437,6 +473,38 @@     $ ClientCommand cmdSplits tabSplits    , Command+      (pure "splits+")+      (RemainingArg "focuses")+      "Add windows to the splits list.\n\+      \\n\+      \\^Bfocuses\^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 cmdSplitsAdd tabSplits++  , Command+      (pure "splits-")+      (RemainingArg "focuses")+      "Remove windows from the splits list.\n\+      \\n\+      \\^Bfocuses\^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 cmdSplitsDel tabActiveSplits++  , Command+      (pure "ignore")+      (RemainingArg "nicks")+      "Toggle the soft-ignore on each of the space-delimited given nicknames.\n"+    $ ClientCommand cmdIgnore simpleClientTab++  , Command       (pure "grep")       (RemainingArg "regular-expression")       "Set the persistent regular expression.\n\@@ -458,17 +526,17 @@       \\^B/grepi\^O is case-insensitive.\n"     $ ClientCommand (cmdGrep False) simpleClientTab --  ---  -- Network commands-  --   , Command-      (pure "quote")-      (RemainingArg "raw IRC command")-      "Send a raw IRC command.\n"-    $ NetworkCommand cmdQuote  simpleNetworkTab+      (pure "mentions")+      NoArg+      "Show a list of all message that were highlighted as important.\n"+    $ ClientCommand cmdMentions noClientTab -  , Command+  ------------------------------------------------------------------------+  ] , CommandSection "IRC commands"+  ------------------------------------------------------------------------++  [ Command       ("join" :| ["j"])       (ReqTokenArg "channels" (OptTokenArg "keys" NoArg))       "Join a chat channel.\n\@@ -478,33 +546,10 @@     $ NetworkCommand cmdJoin   simpleNetworkTab    , Command-      ("channel" :| ["c"])-      (ReqTokenArg "channel" NoArg)-      "Change the focused window.\n\-      \\n\-      \Changes the focus to the \^Bchannel\^B chat window on the current network.\n\-      \\n\-      \Nicknames can be specified in the \^Bchannel\^B parameter to switch to private chat.\n\-      \See also: /focus to switch to a channel on a different network.\n\-      \See also: /focus to switch to a channel on a different network.\n"-    $ NetworkCommand cmdChannel tabChannel--  , Command-      (pure "mode")-      (RemainingArg "modes and parameters")-      "Sets IRC modes.\n\-      \\n\-      \Examples:\n\-      \Setting a ban:           /mode +b *!*@hostname\n\-      \Removing a quiet:        /mode -q *!*@hostname\n\-      \Voicing two users:       /mode +vv user1 user2\n\-      \Demoting an op to voice: /mode +v-o user1 user1\n\-      \\n\-      \When executed in a network window, mode changes are applied to your user.\n\-      \When executed in a channel window, mode changes are applied to the channel.\n\-      \\n\-      \This command has parameter sensitive tab-completion.\n"-    $ NetworkCommand cmdMode   tabMode+      (pure "part")+      (RemainingArg "reason")+      "Part from the current channel.\n"+    $ ChannelCommand cmdPart simpleChannelTab    , Command       (pure "msg")@@ -515,6 +560,20 @@     $ NetworkCommand cmdMsg    simpleNetworkTab    , Command+      (pure "me")+      (RemainingArg "message")+      "Send an 'action' to the current chat window.\n"+    $ ChatCommand cmdMe simpleChannelTab++  , Command+      (pure "say")+      (RemainingArg "message")+      "Send a message to the current chat window.\n\+      \\n\+      \This can be useful for sending a chat message with a leading '/'.\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\@@ -543,24 +602,39 @@     $ NetworkCommand cmdNick   simpleNetworkTab    , Command-      (pure "quit")-      (RemainingArg "reason")-      "Gracefully disconnect the current network connection.\n\-      \\n\-      \\^Breason\^B: optional quit reason\n\+      (pure "away")+      (RemainingArg "message")+      "Set away status.\n\       \\n\-      \See also: /disconnect /exit\n"-    $ NetworkCommand cmdQuit   simpleNetworkTab+      \When \^Bmessage\^B is omitted away status is cleared.\n"+    $ NetworkCommand cmdAway simpleNetworkTab    , Command-      (pure "disconnect")+      (pure "users")       NoArg-      "Immediately terminate the current network connection.\n\+      "Show the user list for the current channel.\n\       \\n\-      \See also: /quit /exit\n"-    $ NetworkCommand cmdDisconnect noNetworkTab+      \Detailed view (F2) shows full hostmask.\n\+      \Hostmasks can be populated with /who #channel.\n"+    $ ChannelCommand cmdUsers  noChannelTab    , Command+      (pure "channelinfo")+      NoArg+      "Show information about the current channel.\n"+    $ ChannelCommand cmdChannelInfo noChannelTab++  , Command+      (pure "quote")+      (RemainingArg "raw IRC command")+      "Send a raw IRC command.\n"+    $ NetworkCommand cmdQuote  simpleNetworkTab++  ------------------------------------------------------------------------+  ] , CommandSection "IRC queries"+  ------------------------------------------------------------------------++  [ Command       (pure "who")       (RemainingArg "arguments")       "Send WHO query to server with given arguments.\n"@@ -591,14 +665,6 @@     $ NetworkCommand cmdUserhost simpleNetworkTab    , Command-      (pure "away")-      (RemainingArg "message")-      "Set away status.\n\-      \\n\-      \When \^Bmessage\^B is omitted away status is cleared.\n"-    $ NetworkCommand cmdAway simpleNetworkTab--  , Command       (pure "links")       (RemainingArg "arguments")       "Send LINKS query to server with given arguments.\n"@@ -616,30 +682,40 @@       "Send STATS query to server with given arguments.\n"     $ NetworkCommand cmdStats simpleNetworkTab -  , Command-      (pure "znc")-      (RemainingArg "arguments")-      "Send command directly to ZNC.\n\-      \\n\-      \The advantage of this over /msg is that responses are not broadcast to call clients.\n"-    $ NetworkCommand cmdZnc simpleNetworkTab+  ------------------------------------------------------------------------+  ] , CommandSection "IRC channel management"+  ------------------------------------------------------------------------ -  , Command-      (pure "znc-playback")-      (OptTokenArg "time" (OptTokenArg "date" NoArg))-      "Request playback from the ZNC 'playback' module.\n\+  [ Command+      (pure "mode")+      (RemainingArg "modes and parameters")+      "Sets IRC modes.\n\       \\n\-      \\^Btime\^B determines the time to playback since.\n\-      \\^Bdate\^B determines the date to playback since.\n\+      \Examples:\n\+      \Setting a ban:           /mode +b *!*@hostname\n\+      \Removing a quiet:        /mode -q *!*@hostname\n\+      \Voicing two users:       /mode +vv user1 user2\n\+      \Demoting an op to voice: /mode +v-o user1 user1\n\       \\n\-      \When both \^Btime\^B and \^Bdate\^B are omitted, all playback is requested.\n\-      \When both \^Bdate\^B is omitted it is defaulted the most recent date in the past that makes sense.\n\+      \When executed in a network window, mode changes are applied to your user.\n\+      \When executed in a channel window, mode changes are applied to the channel.\n\       \\n\-      \Time format: HOURS:MINUTES (example: 7:00)\n\-      \Date format: YEAR-MONTH-DAY (example: 2016-06-16)\n\+      \This command has parameter sensitive tab-completion.\n"+    $ NetworkCommand cmdMode   tabMode++  , Command+      (pure "masks")+      (ReqTokenArg "mode" NoArg)+      "Show mask lists for current channel.\n\       \\n\-      \Note that the playback module is not installed in ZNC by default!\n"-    $ NetworkCommand cmdZncPlayback noNetworkTab+      \Common \^Bmode\^B values:\n\+      \\^Bb\^B: bans\n\+      \\^Bq\^B: quiets\n\+      \\^BI\^B: invite exemptions (op view only)\n\+      \\^Be\^B: ban exemption (op view only)s\n\+      \\n\+      \To populate the mask lists for the first time use: /mode \^Bmode\^B\n"+    $ ChannelCommand cmdMasks noChannelTab    , Command       (pure "invite")@@ -681,56 +757,37 @@       \See also: /kick /kickban\n"     $ ChannelCommand cmdRemove simpleChannelTab -  , Command-      (pure "part")-      (RemainingArg "reason")-      "Part from the current channel.\n"-    $ ChannelCommand cmdPart simpleChannelTab+  ------------------------------------------------------------------------+  ] , CommandSection "ZNC Support"+  ------------------------------------------------------------------------ -  , Command-      (pure "users")-      NoArg-      "Show the user list for the current channel.\n\+  [ Command+      (pure "znc")+      (RemainingArg "arguments")+      "Send command directly to ZNC.\n\       \\n\-      \Detailed view (F2) shows full hostmask.\n\-      \Hostmasks can be populated with /who #channel.\n"-    $ ChannelCommand cmdUsers  noChannelTab--  , Command-      (pure "channelinfo")-      NoArg-      "Show information about the current channel.\n"-    $ ChannelCommand cmdChannelInfo noChannelTab+      \The advantage of this over /msg is that responses are not broadcast to call clients.\n"+    $ NetworkCommand cmdZnc simpleNetworkTab    , Command-      (pure "masks")-      (ReqTokenArg "mode" NoArg)-      "Show mask lists for current channel.\n\+      (pure "znc-playback")+      (OptTokenArg "time" (OptTokenArg "date" NoArg))+      "Request playback from the ZNC 'playback' module.\n\       \\n\-      \Common \^Bmode\^B values:\n\-      \\^Bb\^B: bans\n\-      \\^Bq\^B: quiets\n\-      \\^BI\^B: invite exemptions (op view only)\n\-      \\^Be\^B: ban exemption (op view only)s\n\+      \\^Btime\^B determines the time to playback since.\n\+      \\^Bdate\^B determines the date to playback since.\n\       \\n\-      \To populate the mask lists for the first time use: /mode \^Bmode\^B\n"-    $ ChannelCommand cmdMasks noChannelTab--  , Command-      (pure "me")-      (RemainingArg "message")-      "Send an 'action' to the current chat window.\n"-    $ ChatCommand cmdMe simpleChannelTab--  , Command-      (pure "say")-      (RemainingArg "message")-      "Send a message to the current chat window.\n\+      \When both \^Btime\^B and \^Bdate\^B are omitted, all playback is requested.\n\+      \When both \^Bdate\^B is omitted it is defaulted the most recent date in the past that makes sense.\n\       \\n\-      \This can be useful for sending a chat message with a leading '/'.\n"-    $ ChatCommand cmdSay simpleChannelTab-  ]+      \Time format: HOURS:MINUTES (example: 7:00)\n\+      \Date format: YEAR-MONTH-DAY (example: 2016-06-16)\n\+      \\n\+      \Note that the playback module is not installed in ZNC by default!\n"+    $ NetworkCommand cmdZncPlayback noNetworkTab +  ]]+ -- | Provides no tab completion for client commands noClientTab :: Bool -> ClientCommand String noClientTab _ st _ = commandFailure st@@ -970,11 +1027,13 @@   where     focus = FocusHelp (fmap (Text.pack . fst) mb) --- | Tab completion for @/splits@+-- | Tab completion for @/splits[+]@. When given no arguments this+-- populates the current list of splits, otherwise it tab completes+-- all of the currently available windows. tabSplits :: Bool -> ClientCommand String tabSplits isReversed st rest   | all (' '==) rest =-     do let cmd = "/splits " ++ unwords (Text.unpack . render <$> currentExtras)+     do let cmd = unwords ("/splits" : map (Text.unpack . renderFocus) currentExtras)             newline = Edit.endLine cmd         commandSuccess (set (clientTextBox . Edit.line) newline st) @@ -983,31 +1042,64 @@   where     currentExtras = view clientExtraFocus st -    completions = map render+    completions = map renderFocus                 $ Map.keys                 $ view clientWindows st -    render Unfocused          = "*"-    render (NetworkFocus x)   = x-    render (ChannelFocus x y) = x <> ":" <> idText y+-- | Tab completion for @/splits-@. This completes only from the list of active+-- entries in the splits list.+tabActiveSplits :: Bool -> ClientCommand String+tabActiveSplits isReversed st rest =+  simpleTabCompletion plainWordCompleteMode [] completions isReversed st+  where+    completions = renderFocus <$> view clientExtraFocus st ++-- | Parses a list of entries in the format used by @/splits[+-]@ to specify windows.+parseFocuses :: String -> [Focus]+parseFocuses = map parseFocus . words++-- | Parses a single entry in the format used by @/splits[+-]@ to specify windows.+parseFocus :: String -> Focus+parseFocus x =+  case break (==':') x of+    ("*","")     -> Unfocused+    (net,"")     -> NetworkFocus (Text.pack net)+    (net,_:chan) -> ChannelFocus (Text.pack net) (mkId (Text.pack chan))++-- | Render a entry from splits back to the textual format.+renderFocus :: Focus -> Text+renderFocus Unfocused          = "*"+renderFocus (NetworkFocus x)   = x+renderFocus (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))+    extras = nub (parseFocuses str) -    toFocus x =-      case break (==':') x of-        ("*","")     -> Unfocused-        (net,"")     -> NetworkFocus (Text.pack net)-        (net,_:chan) -> ChannelFocus (Text.pack net) (mkId (Text.pack chan)) +-- | Implementation of @/splits+@+cmdSplitsAdd :: ClientCommand String+cmdSplitsAdd st str = commandSuccess (over clientExtraFocus extras st)+  where+    extras prev = nub (parseFocuses str ++ prev)++-- | Implementation of @/splits-@+cmdSplitsDel :: ClientCommand String+cmdSplitsDel st str = commandSuccess (over clientExtraFocus extras st)+  where+    extras prev = prev \\ parseFocuses str++ tabHelp :: Bool -> ClientCommand String tabHelp isReversed st _ =   simpleTabCompletion plainWordCompleteMode [] commandNames isReversed st   where-    commandNames = fst <$> expandAliases commandsList+    commandNames = fst <$> expandAliases (concatMap cmdSectionCmds commandsList)  simpleTabCompletion ::   Prefix a =>
src/Client/EventLoop.hs view
@@ -409,7 +409,11 @@   ClientState {- ^ client state   -} ->   IO (Maybe ClientState) doKey vty key modifier st =-  let continue !out   = return (Just out)++  let continue !out -- detect when chains of M-a are broken+        | modifier == [MMeta] && key == KChar 'a' = return (Just out)+        | otherwise = return $! Just $! set clientActivityReturn (view clientFocus out) out+       changeEditor  f = continue (over clientTextBox f st)       changeContent f = changeEditor                       $ over Edit.content f
src/Client/Image.hs view
@@ -45,7 +45,7 @@   (Int, Image, ClientState) {- ^ text box cursor position, image, updated state -} clientImage st = (pos, img, st')   where-    (mainHeight, splitHeight) = clientWindowHeights (imageHeight activityBar) st+    (mainHeight, splitHeight) = clientWindowHeights (imageHeight statusLine) st     splitFocuses              = clientExtraFocuses st     focus                     = view clientFocus st     (pos , nextOffset, tbImg) = textboxImage st@@ -60,13 +60,10 @@      img = vertCat splits      <->           msgs                <->-          activityBar         <->-          statusLineImage st' <->+          statusLine          <->           tbImg -    activityBar = activityBarImage st-        -- must be st, not st', needed to compute window heights-        -- before rendering the message panes+    statusLine = statusLineImage st      renderExtra stIn focus1 = outImg       where@@ -111,23 +108,22 @@ scrollAmount st = max 1 (snd (clientWindowHeights actSize st))                -- extra will be equal to main or 1 smaller   where-    actSize = imageHeight (activityBarImage st)+    actSize = imageHeight (statusLineImage 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 ::-  Int         {- ^ activity bar height       -} ->+  Int         {- ^ status bar height         -} ->   ClientState {- ^ client state              -} ->   (Int,Int)   {- ^ main height, extra height -}-clientWindowHeights activityBar st =-  (max 0 (h - overhead - extras*d), max 0 (d-overhead))+clientWindowHeights statusBar st = (mainH, splitH)   where-    d        = h `quot` (1 + extras)--    h        = max 0 (view clientHeight st - activityBar) -- lines available+    h        = max 0 (view clientHeight st - overhead)+    splitH   = h `quot` (1+extras)+    mainH    = h - splitH*extras      extras   = length (clientExtraFocuses st)--    overhead = 2 -- status line and textbox/divider+    textbox  = 1+    overhead = textbox + statusBar + 2*extras
src/Client/Image/Message.hs view
@@ -45,6 +45,7 @@ import           Irc.Message import           Irc.RawIrcMsg import           Irc.UserInfo+import           Text.Read  -- | Parameters used when rendering messages data MessageRendererParams = MessageRendererParams@@ -288,13 +289,7 @@       parseIrcText reason      Reply code params ->-      renderReplyCode rm rp code <|>-      char defAttr ' ' <|>-      separatedParams (dropFst params)-      where-        dropFst = case rm of-                    DetailedRender -> id-                    NormalRender   -> drop 1+      renderReplyCode rm rp code params      UnknownMsg irc ->       maybe emptyImage (\ui -> coloredUserInfo pal rm myNicks ui <|> char defAttr ' ')@@ -343,15 +338,27 @@ ircWords :: [Text] -> Image ircWords = horizCat . intersperse (char defAttr ' ') . map parseIrcText -renderReplyCode :: RenderMode -> MessageRendererParams -> ReplyCode -> Image-renderReplyCode rm rp code@(ReplyCode w) =+renderReplyCode :: RenderMode -> MessageRendererParams -> ReplyCode -> [Text] -> Image+renderReplyCode rm rp code@(ReplyCode w) params =   case rm of-    DetailedRender -> string attr (show w)+    DetailedRender -> string attr (show w) <|> rawParamsImage     NormalRender   ->       rightPad rm (rendNickPadding rp)         (text' attr (Text.toLower (replyCodeText info))) <|>-      char defAttr ':'+      char defAttr ':' <|>++      case code of+        RPL_WHOISIDLE -> whoisIdleParamsImage+        _             -> rawParamsImage   where+    rawParamsImage =+      char defAttr ' ' <|>+      separatedParams params'++    params' = case rm of+                DetailedRender -> params+                NormalRender   -> drop 1 params+     info = replyCodeInfo code      color = case replyCodeType info of@@ -361,6 +368,40 @@               UnknownReply      -> yellow      attr = withForeColor defAttr color++    whoisIdleParamsImage =+      case params' of+        [name, idle, signon, _txt] ->+          char defAttr ' ' <|>+          text' defAttr name <|>+          text' defAttr " idle: " <|>+          string defAttr (prettySeconds (Text.unpack idle)) <|>+          text' defAttr " sign-on: " <|>+          string defAttr (prettyUnixTime (Text.unpack signon))++        _ -> rawParamsImage++-- | Transform string representing seconds in POSIX time to pretty format.+prettyUnixTime :: String -> String+prettyUnixTime str =+  case parseTimeM False defaultTimeLocale "%s" str of+    Nothing -> str+    Just t  -> formatTime defaultTimeLocale "%A %B %e, %Y %H:%M:%S %Z" (t :: UTCTime)++-- | Render string representing seconds into days, hours, minutes, and seconds.+prettySeconds :: String -> String+prettySeconds str =+  case readMaybe str of+    Nothing -> str+    Just n  -> intercalate " "+             $ map (\(u,i) -> show i ++ [u])+             $ dropWhile (\x -> snd x == 0)+             $ zip "dhms" [d,h,m,s]+      where+        (n1,s) = quotRem n  60+        (n2,m) = quotRem n1 60+        (d ,h) = quotRem n2 24+  data IdentifierColorMode   = PrivmsgIdentifier -- ^ An identifier in a PRIVMSG
src/Client/Image/StatusLine.hs view
@@ -14,7 +14,6 @@ module Client.Image.StatusLine   ( statusLineImage   , minorStatusLineImage-  , activityBarImage   ) where  import           Client.Image.Palette@@ -35,15 +34,14 @@  -- | Renders the status line between messages and the textbox. statusLineImage ::-  ClientState {- ^ client state             -} ->-  Image       {- ^ activity bar, status bar -}+  ClientState {- ^ client state -} ->+  Image       {- ^ status bar   -} statusLineImage st = content <|> charFill defAttr '─' fillSize 1   where     fillSize = max 0 (view clientWidth st - imageWidth content)-    content = horizCat+    contentSansActivity = horizCat       [ myNickImage st       , focusImage st-      , activitySummary st       , detailImage st       , nometaImage st       , scrollImage st@@ -51,7 +49,12 @@       , latencyImage st       ] +    content+      | view clientActivityBar st =+          makeLines (view clientWidth st) (contentSansActivity : activityBarImages st)+      | otherwise = contentSansActivity <|> activitySummary st + -- | The minor status line is used when rendering the @/splits@ and -- @/mentions@ views to show the associated window name. minorStatusLineImage :: Focus -> ClientState -> Image@@ -162,16 +165,14 @@              | otherwise         = view palActivity pal  -- | Multi-line activity information enabled by F3-activityBarImage :: ClientState -> Image-activityBarImage st-  | view clientActivityBar st = activityBar'-  | otherwise                 = emptyImage+activityBarImages :: ClientState -> [Image]+activityBarImages st+  = catMaybes+  $ zipWith baraux winNames+  $ Map.toList+  $ view clientWindows st+   where-    activityBar' = makeLines (view clientWidth st)-                 $ catMaybes-                 $ zipWith baraux winNames-                 $ Map.toList-                 $ view clientWindows st      winNames = clientWindowNames st ++ repeat '?' @@ -197,7 +198,11 @@             ChannelFocus _ chan -> idText chan  -+-- | Pack a list of images into a single image spanning possibly many lines.+-- The images will stack upward with the first element of the list being in+-- the bottom left corner of the image. Each line will have at least one+-- of the component images in it, which might truncate that image in extreme+-- cases. makeLines ::   Int     {- ^ window width       -} ->   [Image] {- ^ components to pack -} ->
src/Client/State.hs view
@@ -40,6 +40,7 @@   , clientExtensions   , clientRegex   , clientLogQueue+  , clientActivityReturn    -- * Client operations   , withClientState@@ -146,6 +147,7 @@ data ClientState = ClientState   { _clientWindows           :: !(Map Focus Window) -- ^ client message buffers   , _clientPrevFocus         :: !Focus              -- ^ previously focused buffer+  , _clientActivityReturn    :: !Focus              -- ^ focus prior to jumping to activity   , _clientFocus             :: !Focus              -- ^ currently focused buffer   , _clientSubfocus          :: !Subfocus           -- ^ current view mode   , _clientExtraFocus        :: ![Focus]            -- ^ extra messages windows to view@@ -239,6 +241,7 @@         , _clientHeight            = 25         , _clientEvents            = events         , _clientPrevFocus         = Unfocused+        , _clientActivityReturn    = Unfocused         , _clientFocus             = Unfocused         , _clientSubfocus          = FocusMessages         , _clientExtraFocus        = []@@ -779,14 +782,15 @@ -- Some events like errors or chat messages mentioning keywords are -- considered important and will be jumped to first. jumpToActivity :: ClientState -> ClientState-jumpToActivity st =-  case mplus highPriority lowPriority of-    Just (focus,_) -> changeFocus focus st-    Nothing        -> st+jumpToActivity st = changeFocus newFocus st   where     windowList   = views clientWindows Map.toAscList st     highPriority = find (view winMention . snd) windowList     lowPriority  = find (\x -> view winUnread (snd x) > 0) windowList+    newFocus =+      case mplus highPriority lowPriority of+        Just (focus,_) -> focus+        Nothing        -> view clientActivityReturn st  -- | Jump the focus directly to a window based on its zero-based index. jumpFocus ::@@ -799,12 +803,13 @@     windows   = view clientWindows st     (focus,_) = Map.elemAt i windows + -- | Change the window focus to the given value, reset the subfocus -- to message view, reset the scroll, remember the previous focus -- if it changed. changeFocus ::-  Focus       {- ^ new focus    -} ->-  ClientState {- ^ client state -} ->+  Focus       {- ^ new focus             -} ->+  ClientState {- ^ client state          -} ->   ClientState changeFocus focus st   = set clientScroll 0@@ -814,6 +819,7 @@   $ st   where     oldFocus = view clientFocus st+     updatePrevious       | focus == oldFocus = id       | otherwise         = set clientPrevFocus oldFocus
src/Client/View/Help.hs view
@@ -22,7 +22,7 @@ import           Client.Commands.Recognizer import           Control.Lens import           Data.Foldable (toList)-import           Data.List (delete)+import           Data.List (delete, intercalate) import           Data.List.NonEmpty (NonEmpty((:|))) import           Data.Monoid ((<>)) import           Data.Text (Text)@@ -57,17 +57,17 @@     Exact Command{cmdNames = names, cmdImplementation = impl,                   cmdArgumentSpec = spec, cmdDocumentation = doc} ->       reverse $ commandSummary pal (pure cmdName) spec-              : emptyImage+              : emptyLine               : aliasLines              ++ explainContext impl-              : emptyImage+              : emptyLine               : map parseIrcText (Text.lines doc)       where         aliasLines =           case delete cmdName (toList names) of             [] -> []             ns -> [ text' defAttr (Text.unwords ("Aliases:":ns))-                  , emptyImage ]+                  , emptyLine ]  -- | Generate an explanation of the context where the given command -- implementation will be valid.@@ -88,11 +88,24 @@ listAllCommands ::   Palette {- ^ palette    -} ->   [Image] {- ^ help lines -}-listAllCommands pal =-  reverse-    [ commandSummary pal names spec-    | Command{ cmdNames = names, cmdArgumentSpec = spec } <- commandsList ]+listAllCommands pal+  = intercalate [emptyLine]+  $ map reverse+  $ listCommandSection pal <$> commandsList +listCommandSection ::+  Palette        {- ^ palette         -} ->+  CommandSection {- ^ command section -} ->+  [Image]        {- ^ help lines      -}+listCommandSection pal sec+  = text' (withStyle defAttr bold) (cmdSectionName sec)+  : [ commandSummary pal names spec+    | -- pattern needed due to existential quantification+      Command { cmdNames        = names+              , cmdArgumentSpec = spec+              } <- cmdSectionCmds sec+    ]+ -- | Generate the help line for the given command and its -- specification for use in the list of commands. commandSummary ::@@ -107,3 +120,7 @@    where     pal' = set palCommandPlaceholder defAttr pal++-- Empty line used as a separator+emptyLine :: Image+emptyLine = text' defAttr " "