packages feed

glirc 2.13 → 2.14

raw patch · 28 files changed

+690/−142 lines, 28 filesdep ~vtyPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: vty

API changes (from Hackage documentation)

- Client.Commands.Exec: emptyExecCmd :: ExecCmd
- Client.Commands.Exec: execArguments :: Lens' ExecCmd [String]
- Client.Commands.Exec: execCommand :: Lens' ExecCmd String
- Client.Commands.Exec: execStdIn :: Lens' ExecCmd String
- Client.Commands.Exec: options :: [OptDescr (ExecCmd -> ExecCmd)]
- Client.Image.Palette: [_palCommandOptional] :: Palette -> Attr
- Client.Image.Palette: [_palCommandRemaining] :: Palette -> Attr
- Client.Image.Palette: [_palCommandRequired] :: Palette -> Attr
- Client.Image.Palette: palCommandOptional :: Lens' Palette Attr
- Client.Image.Palette: palCommandRemaining :: Lens' Palette Attr
- Client.Image.Palette: palCommandRequired :: Lens' Palette Attr
+ Client.Commands: ChannelCommand :: (ChannelCommand a) -> (Bool -> ChannelCommand String) -> CommandImpl a
+ Client.Commands: ChatCommand :: (ChannelCommand a) -> (Bool -> ChannelCommand String) -> CommandImpl a
+ Client.Commands: ClientCommand :: (ClientCommand a) -> (Bool -> ClientCommand String) -> CommandImpl a
+ Client.Commands: NetworkCommand :: (NetworkCommand a) -> (Bool -> NetworkCommand String) -> CommandImpl a
+ Client.Commands: commandsList :: [(NonEmpty Text, Command)]
+ Client.Commands: data CommandImpl a
+ Client.Commands.WordCompletion: isPrefix :: Prefix a => a -> a -> Bool
+ Client.Commands.WordCompletion: toString :: Prefix a => a -> String
+ Client.Configuration: [_configActivityBar] :: Configuration -> Bool
+ Client.Configuration: configActivityBar :: Lens' Configuration Bool
+ Client.Image.Help: helpImageLines :: Maybe Text -> Palette -> [Image]
+ Client.Image.Palette: [_palCommandPlaceholder] :: Palette -> Attr
+ Client.Image.Palette: palCommandPlaceholder :: Lens' Palette Attr
+ Client.Image.Palette: paletteMap :: [(Text, ReifiedLens' Palette Attr)]
+ Client.Image.PaletteView: paletteViewLines :: Palette -> [Image]
+ Client.State: [_clientActivityBar] :: ClientState -> !Bool
+ Client.State: clientActivityBar :: Lens' ClientState Bool
+ Client.State.Focus: FocusHelp :: (Maybe Text) -> Subfocus
+ Client.State.Focus: FocusPalette :: Subfocus
+ Config.FromConfig: instance Config.FromConfig.FromConfig GHC.Types.Bool
- Client.Commands: Command :: (ArgumentSpec a) -> (CommandImpl a) -> Command
+ Client.Commands: Command :: (ArgumentSpec a) -> Text -> (CommandImpl a) -> Command
- Client.Configuration: Configuration :: ServerSettings -> (HashMap Text ServerSettings) -> Palette -> Text -> HashSet Identifier -> Maybe Integer -> Maybe FilePath -> HashMap Text [[ExpansionChunk]] -> [FilePath] -> Maybe FilePath -> HashSet Identifier -> Configuration
+ Client.Configuration: Configuration :: ServerSettings -> (HashMap Text ServerSettings) -> Palette -> Text -> HashSet Identifier -> Maybe Integer -> Maybe FilePath -> HashMap Text [[ExpansionChunk]] -> [FilePath] -> Maybe FilePath -> HashSet Identifier -> Bool -> Configuration
- Client.Image.Palette: Palette :: Vector Attr -> Attr -> Maybe Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Attr -> Palette
+ Client.Image.Palette: Palette :: Vector Attr -> Attr -> Maybe 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) -> !Vty -> !EditBox -> !Int -> !Int -> !Configuration -> !Int -> !Bool -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ClientState
+ Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Subfocus -> !(IntMap NetworkState) -> !Int -> !ConnectionContext -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Vty -> !EditBox -> !Int -> !Int -> !Configuration -> !Int -> !Bool -> !Bool -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ClientState
- Client.State.EditBox: class HasLine c_aHQ2 where pos = (.) line pos text = (.) line text
+ Client.State.EditBox: class HasLine c_aiUU where pos = (.) line pos text = (.) line text
- Client.State.EditBox: line :: HasLine c_aHQ2 => Lens' c_aHQ2 Line
+ Client.State.EditBox: line :: HasLine c_aiUU => Lens' c_aiUU Line
- Client.State.EditBox: pos :: HasLine c_aHQ2 => Lens' c_aHQ2 Int
+ Client.State.EditBox: pos :: HasLine c_aiUU => Lens' c_aiUU Int
- Client.State.EditBox: text :: HasLine c_aHQ2 => Lens' c_aHQ2 String
+ Client.State.EditBox: text :: HasLine c_aiUU => Lens' c_aiUU String
- Client.State.EditBox.Content: class HasLine c_aHQ2 where pos = (.) line pos text = (.) line text
+ Client.State.EditBox.Content: class HasLine c_aiUU where pos = (.) line pos text = (.) line text
- Client.State.EditBox.Content: line :: HasLine c_aHQ2 => Lens' c_aHQ2 Line
+ Client.State.EditBox.Content: line :: HasLine c_aiUU => Lens' c_aiUU Line
- Client.State.EditBox.Content: pos :: HasLine c_aHQ2 => Lens' c_aHQ2 Int
+ Client.State.EditBox.Content: pos :: HasLine c_aiUU => Lens' c_aiUU Int
- Client.State.EditBox.Content: text :: HasLine c_aHQ2 => Lens' c_aHQ2 String
+ Client.State.EditBox.Content: text :: HasLine c_aiUU => Lens' c_aiUU String

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Revision history for glirc2 +## 2.14++* Add `/help`+* Add `/palette`+* Add F3 to toggle activity detail bar+ ## 2.13  * Add disconnect expansion, support expansions in connect-cmds
README.md view
@@ -164,6 +164,7 @@ * `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 --------@@ -207,9 +208,7 @@ * `mention` - attr - attr for mention notification * `command` - attr - attr for recognized command * `command-ready` - attr - attr for command with successful parse-* `command-required` - attr - attr for required command argument placeholder-* `command-optional` - attr - attr for optional command argument placeholder-* `command-remaining` - attr - attr for remaining command text placeholder+* `command-placeholder` - attr - attr for command argument placeholder  Text Attributes ---------------@@ -240,6 +239,7 @@  Client commands +* `/help [command]` - Show in-client help * `/exit` - Terminate the client * `/quit` - Gracefully terminate connection to the current server * `/connect <name>` - Connect to the given server@@ -340,6 +340,7 @@ * `M-D` delete word forwards * `TAB` nickname completion * `F2` toggle detailed view+* `F3` toggle detailed activity bar * `Page Up` scroll up * `Page Down` scroll down * `^B` bold
glirc.cabal view
@@ -1,5 +1,5 @@ name:                glirc-version:             2.13+version:             2.14 synopsis:            Console IRC client description:         Console IRC client license:             ISC@@ -69,10 +69,12 @@                        Client.Image                        Client.Image.Arguments                        Client.Image.ChannelInfo+                       Client.Image.Help                        Client.Image.MaskList                        Client.Image.Message                        Client.Image.MircFormatting                        Client.Image.Palette+                       Client.Image.PaletteView                        Client.Image.StatusLine                        Client.Image.Textbox                        Client.Image.UserList@@ -122,7 +124,7 @@                        unix                 >=2.7    && <2.8,                        unordered-containers >=0.2.7  && <0.3,                        vector               >=0.11   && <0.12,-                       vty                  >=5.10   && <5.11,+                       vty                  >=5.10   && <5.12,                        x509                 >=1.6.3  && <1.7,                        x509-store           >=1.6.1  && <1.7,                        x509-system          >=1.6.3  && <1.7
src/Client/CApi.hs view
@@ -12,7 +12,10 @@ -}  module Client.CApi-  ( ActiveExtension(..)+  ( -- * Extension type+    ActiveExtension(..)++  -- * Extension callbacks   , extensionSymbol   , activateExtension   , deactivateExtension@@ -122,6 +125,7 @@             then go rest msgPtr             else return False +-- | Notify an extension of a client command with the given parameters. commandExtension ::   Ptr ()          {- ^ client state stableptr -} ->   [Text]          {- ^ parameters             -} ->
src/Client/CommandArguments.hs view
@@ -14,6 +14,8 @@   (   -- * Command-line argument type     CommandArguments(..)++  -- * Lenses   , cmdArgConfigFile   , cmdArgInitialNetworks 
src/Client/Commands.hs view
@@ -19,7 +19,9 @@   , tabCompletion   -- * Commands   , Command(..)+  , CommandImpl(..)   , commands+  , commandsList   ) where  import           Client.CApi@@ -44,6 +46,7 @@ import           Data.Foldable import           Data.HashMap.Strict (HashMap) import           Data.HashSet (HashSet)+import           Data.List.NonEmpty (NonEmpty((:|))) import           Data.List.Split import qualified Data.HashMap.Strict as HashMap import           Data.Text (Text)@@ -94,8 +97,8 @@   -- | requires an active channel window   | ChannelCommand (ChannelCommand a) (Bool -> ChannelCommand String) --- | Pair of a command and it's argument specification-data Command = forall a.  Command (ArgumentSpec a) (CommandImpl a)+-- | A command is an argument specification, implementation, and documentation+data Command = forall a. Command (ArgumentSpec a) Text (CommandImpl a)  -- | Consider the text entry successful and resume the client commandSuccess :: Monad m => ClientState -> m CommandResult@@ -150,7 +153,12 @@            CommandFailure st1 -> process cs st1 -- ?            CommandQuit st1    -> return (CommandQuit st1) -commandExpansion :: Maybe Text -> ClientState -> Text -> Maybe Text+-- | Compute the replacement value for the given expansion variable.+commandExpansion ::+  Maybe Text  {- ^ disconnect time    -} ->+  ClientState {- ^ client state       -} ->+  Text        {- ^ expansion variable -} ->+  Maybe Text  {- ^ expansion value    -} commandExpansion discoTime st v =   case v of     "network" -> views clientFocus focusNetwork st@@ -219,7 +227,7 @@         Nothing         -> commandFailureMsg "Unknown command" st         Just isReversed -> nickTabCompletion isReversed st -    Just (Command argSpec impl) ->+    Just (Command argSpec _docs impl) ->       case impl of         ClientCommand exec tab ->           finish argSpec exec tab@@ -243,219 +251,397 @@               finish argSpec (exec channelId cs) (\x -> tab x channelId cs)           | otherwise -> commandFailureMsg "This command requires an active chat window" st --- Expands each alias to have its own copy of the command callbacks-expandAliases :: [([a],b)] -> [(a,b)]-expandAliases xs = [ (a,b) | (as,b) <- xs, a <- as ]+-- | Expands each alias to have its own copy of the command callbacks+expandAliases :: [(NonEmpty a,b)] -> [(a,b)]+expandAliases xs = [ (a,b) | (as,b) <- xs, a <- toList as ] ++-- | Map of built-in client commands to their implementations, tab completion+-- logic, and argument structures. commands :: HashMap Text Command-commands = HashMap.fromList-         $ expandAliases+commands = HashMap.fromList (expandAliases commandsList) +commandsList :: [(NonEmpty Text,Command)]+commandsList =   --   -- Client commands   ---  [ ( ["connect"]+  [ ( pure "connect"     , Command (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     )-  , ( ["exit"]+  , ( pure "exit"     , Command NoArg+      "Exit the client immediately.\n"     $ ClientCommand cmdExit noClientTab     )-  , ( ["focus"]+  , ( pure "focus"     , Command (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     )-  , ( ["clear"]+  , ( pure "clear"     , Command (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\+      \\n\+      \If a window is cleared and no longer active that window will be removed from the client.\n"     $ ClientCommand cmdClear noClientTab     )-  , ( ["reconnect"]+  , ( pure "reconnect"     , Command NoArg+      "Reconnect to the current network.\n"     $ ClientCommand cmdReconnect noClientTab     )-  , ( ["ignore"]+  , ( pure "ignore"     , Command (RemainingArg "nicks")+      "Toggle the soft-ignore on each of the space-delimited given nicknames.\n"     $ ClientCommand cmdIgnore simpleClientTab     )-  , ( ["reload"]+  , ( pure "reload"     , Command (OptTokenArg "filename" NoArg)+      "Reload the client configuration file.\n\+      \\n\+      \If \^Bfilename\^B is provided it will be used to reload.\n\+      \Otherwise the previously loaded configuration file will be reloaded.\n"     $ ClientCommand cmdReload tabReload     )-  , ( ["extension"]+  , ( pure "extension"     , Command (ReqTokenArg "extension" (RemainingArg "arguments"))+      "Calls the process_command callback of the given extension.\n\+      \\n\+      \\^Bextension\^B should be the name of the loaded extension.\n"     $ ClientCommand cmdExtension simpleClientTab     )-  , ( ["windows"]+  , ( pure "windows"     , Command NoArg+      "Show a list of all client message windows.\n"     $ ClientCommand cmdWindows noClientTab     )-  , ( ["exec"]+  , ( pure "palette"+    , Command NoArg+      "Show the current palette settings and a color chart to help pick new colors.\n"+    $ ClientCommand cmdPalette noClientTab+    )+  , ( pure "exec"     , Command (RemainingArg "arguments")+      "Execute a command synchnonously sending the to a configuration destination.\n\+      \\n\+      \\^Barguments\^B: [-n network] [-c channel] [-i input] command [command arguments...]\n\+      \\n\+      \When \^Binput\^B is specified it is sent to the stdin.\n\+      \\n\+      \When neither \^Bnetwork\^B nor \^Bchannel\^B are specified output goes to client window (*)\n\+      \When \^Bnetwork\^B is specified output is sent as raw IRC traffic to the network.\n\+      \When \^Bchannel\^B is specified output is sent as chat to the given channel on the current network.\n\+      \When \^Bnetwork\^B and \^Bchannel\^B are specified output is sent as chat to the given channel on the given network.\n\+      \\n"     $ ClientCommand cmdExec simpleClientTab     )-  , ( ["url"]+  , ( pure "url"     , Command (OptTokenArg "number" NoArg)+      "Open a URL seen in chat.\n\+      \\n\+      \The URL is opened using the executable configured under \^Burl-opener\^B.\n\+      \\n\+      \When this command is active in the textbox, chat messages are filtered to only show ones with URLs.\n\+      \\n\+      \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\+      \\n\+      \When \^Bcommand\^B is omitted a list of all commands is displayed.\n\+      \When \^Bcommand\^B is specified detailed help for that command is shown.\n"+    $ ClientCommand cmdHelp tabHelp+    )    --   -- Network commands   ---  , ( ["quote"]+  , ( pure "quote"     , Command (RemainingArg "raw IRC command")+      "Send a raw IRC command.\n"     $ NetworkCommand cmdQuote  simpleNetworkTab     )-  , ( ["j","join"]+  , ( "join" :| ["j"]     , Command (ReqTokenArg "channels" (OptTokenArg "keys" NoArg))+      "Join a chat channel.\n\+      \\n\+      \\^Bchannels\^B: comma-separated list of channels\n\+      \\^Bkeys\^B: comma-separated list of keys\n"     $ NetworkCommand cmdJoin   simpleNetworkTab     )-  , ( ["c","channel"]+  , ( "channel" :| ["c"]     , Command (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 simpleNetworkTab     )-  , ( ["mode"]+  , ( pure "mode"     , Command (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     )-  , ( ["msg"]+  , ( pure "msg"     , Command (ReqTokenArg "target" (RemainingArg "message"))+      "Send a chat message to a user or a channel.\n\+      \\n\+      \\^Btarget\^B can be a channel or nickname.\n"     $ NetworkCommand cmdMsg    simpleNetworkTab     )-  , ( ["notice"]+  , ( pure "notice"     , Command (ReqTokenArg "target" (RemainingArg "message"))+      "Send a notice message to a user or a channel. Notices are typically used by bots.\n\+      \\n\+      \\^Btarget\^B can be a channel or nickname.\n"     $ NetworkCommand cmdNotice simpleNetworkTab     )-  , ( ["ctcp"]+  , ( pure "ctcp"     , Command (ReqTokenArg "target" (ReqTokenArg "command" (RemainingArg "arguments")))+      "Send a CTCP command to a user or a channel.\n\+      \\n\+      \Examples:\n\+      \Version query:    /ctcp user1 version\n\+      \Local-time query: /ctcp user1 time\n\+      \\n\+      \\^Btarget\^B can be a channel or nickname.\n\+      \\^Bcommand\^B can be any CTCP command.\n\+      \\^Barguments\^B are specific to a particular command.\n"     $ NetworkCommand cmdCtcp simpleNetworkTab     )-  , ( ["nick"]+  , ( pure "nick"     , Command (ReqTokenArg "nick" NoArg)+      "Change your nickname.\n"     $ NetworkCommand cmdNick   simpleNetworkTab     )-  , ( ["quit"]-    , Command (RemainingArg "quit message")+  , ( pure "quit"+    , Command (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     )-  , ( ["disconnect"]+  , ( pure "disconnect"     , Command NoArg+      "Immediately terminate the current network connection.\n\+      \\n\+      \See also: /quit /exit\n"     $ NetworkCommand cmdDisconnect noNetworkTab     )-  , ( ["who"]+  , ( pure "who"     , Command (RemainingArg "arguments")+      "Send WHO query to server with given arguments.\n"     $ NetworkCommand cmdWho simpleNetworkTab     )-  , ( ["whois"]+  , ( pure "whois"     , Command (RemainingArg "arguments")+      "Send WHOIS query to server with given arguments.\n"     $ NetworkCommand cmdWhois simpleNetworkTab     )-  , ( ["whowas"]+  , ( pure "whowas"     , Command (RemainingArg "arguments")+      "Send WHOWAS query to server with given arguments.\n"     $ NetworkCommand cmdWhowas simpleNetworkTab     )-  , ( ["ison"]+  , ( pure "ison"     , Command (RemainingArg "arguments")+      "Send ISON query to server with given arguments.\n"     $ NetworkCommand cmdIson   simpleNetworkTab     )-  , ( ["userhost"]+  , ( pure "userhost"     , Command (RemainingArg "arguments")+      "Send USERHOST query to server with given arguments.\n"     $ NetworkCommand cmdUserhost simpleNetworkTab     )-  , ( ["away"]-    , Command (RemainingArg "arguments")+  , ( pure "away"+    , Command (RemainingArg "message")+      "Set away status.\n\+      \\n\+      \When \^Bmessage\^B is omitted away status is cleared.\n"     $ NetworkCommand cmdAway   simpleNetworkTab     )-  , ( ["links"]+  , ( pure "links"     , Command (RemainingArg "arguments")+      "Send LINKS query to server with given arguments.\n"     $ NetworkCommand cmdLinks  simpleNetworkTab     )-  , ( ["time"]+  , ( pure "time"     , Command (RemainingArg "arguments")+      "Send TIME query to server with given arguments.\n"     $ NetworkCommand cmdTime   simpleNetworkTab     )-  , ( ["stats"]+  , ( pure "stats"     , Command (RemainingArg "arguments")+      "Send STATS query to server with given arguments.\n"     $ NetworkCommand cmdStats  simpleNetworkTab     )-  , ( ["znc"]+  , ( pure "znc"     , Command (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     )-  , ( ["znc-playback"]-    , Command (RemainingArg "arguments")+  , ( pure "znc-playback"+    , Command (OptTokenArg "time" (OptTokenArg "date" NoArg))+      "Request playback from the ZNC 'playback' module.\n\+      \\n\+      \\^Btime\^B determines the time to playback since.\n\+      \\^Bdate\^B determines the date to playback since.\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\+      \\n\+      \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     ) -  , ( ["invite"]+  , ( pure "invite"     , Command (ReqTokenArg "nick" NoArg)+      "Invite a user to the current channel.\n"     $ ChannelCommand cmdInvite simpleChannelTab     )-  , ( ["topic"]+  , ( pure "topic"     , Command (RemainingArg "message")+      "Set the topic on the current channel.\n\+      \\n\+      \Tab-completion with no \^Bmessage\^B specified will load the current topic for editing.\n"     $ ChannelCommand cmdTopic tabTopic     )-  , ( ["kick"]+  , ( pure "kick"     , Command (ReqTokenArg "nick" (RemainingArg "reason"))+      "Kick a user from the current channel.\n\+      \\n\+      \See also: /kickban /remove\n"     $ ChannelCommand cmdKick   simpleChannelTab     )-  , ( ["kickban"]+  , ( pure "kickban"     , Command (ReqTokenArg "nick" (RemainingArg "reason"))+      "Ban and kick a user from the current channel.\n\+      \\n\+      \Users are banned by hostname match.\n\+      \See also: /kick /remove\n"     $ ChannelCommand cmdKickBan simpleChannelTab     )-  , ( ["remove"]+  , ( pure "remove"     , Command (ReqTokenArg "nick" (RemainingArg "reason"))+      "Remove a user from the current channel.\n\+      \\n\+      \Remove works like /kick except it results in a PART.\n\+      \See also: /kick /kickban\n"     $ ChannelCommand cmdRemove simpleChannelTab     )-  , ( ["part"]+  , ( pure "part"     , Command (RemainingArg "reason")+      "Part from the current channel.\n"     $ ChannelCommand cmdPart simpleChannelTab     ) -  , ( ["users"]+  , ( pure "users"     , Command NoArg+      "Show the user list for the current channel.\n\+      \\n\+      \Detailed view (F2) shows full hostmask.\n\+      \Hostmasks can be populated with /who #channel.\n"     $ ChannelCommand cmdUsers  noChannelTab     )-  , ( ["channelinfo"]+  , ( pure "channelinfo"     , Command NoArg+      "Show information about the current channel.\n"     $ ChannelCommand cmdChannelInfo noChannelTab     )-  , ( ["masks"]+  , ( pure "masks"     , Command (ReqTokenArg "mode" NoArg)+      "Show mask lists for current channel.\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\+      \\n\+      \To populate the mask lists for the first time use: /mode \^Bmode\^B\n"     $ ChannelCommand cmdMasks noChannelTab     ) -  , ( ["me"]+  , ( pure "me"     , Command (RemainingArg "message")+      "Send an 'action' to the current chat window.\n"     $ ChatCommand cmdMe simpleChannelTab     )-  , ( ["say"]+  , ( pure "say"     , Command (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     )   ] +-- | Provides no tab completion for client commands noClientTab :: Bool -> ClientCommand String noClientTab _ st _ = commandFailure st +-- | Provides no tab completion for network commands noNetworkTab :: Bool -> NetworkCommand String noNetworkTab _ _ st _ = commandFailure st +-- | Provides no tab completion for channel commands noChannelTab :: Bool -> ChannelCommand String noChannelTab _ _ _ st _ = commandFailure st +-- | Provides nickname based tab completion for client commands simpleClientTab :: Bool -> ClientCommand String simpleClientTab isReversed st _ =   nickTabCompletion isReversed st +-- | Provides nickname based tab completion for network commands simpleNetworkTab :: Bool -> NetworkCommand String simpleNetworkTab isReversed _ st _ =   nickTabCompletion isReversed st +-- | Provides nickname based tab completion for channel commands simpleChannelTab :: Bool -> ChannelCommand String simpleChannelTab isReversed _ _ st _ =   nickTabCompletion isReversed st +-- | Implementation of @/exit@ command. cmdExit :: ClientCommand () cmdExit st _ = return (CommandQuit st) @@ -490,7 +676,8 @@             ChannelFocus network channel -> has (clientConnection network                                                 .csChannels . ix channel) st -+-- | Implementation of @/quote@. Parses arguments as a raw IRC command and+-- sends to the current network. cmdQuote :: NetworkCommand String cmdQuote cs st rest =   case parseRawIrcMsg (Text.pack rest) of@@ -615,6 +802,21 @@ cmdWindows :: ClientCommand () cmdWindows st _ = commandSuccess (changeSubfocus FocusWindows st) +-- | Implementation of @/palette@ command. Set subfocus to Windows.+cmdPalette :: ClientCommand ()+cmdPalette st _ = commandSuccess (changeSubfocus FocusPalette st)++-- | Implementation of @/help@ command. Set subfocus to Windows.+cmdHelp :: ClientCommand (Maybe (String, ()))+cmdHelp st mb = commandSuccess (changeSubfocus focus st)+  where+    focus = FocusHelp (fmap (Text.pack . fst) mb)++tabHelp :: Bool -> ClientCommand String+tabHelp isReversed st _ = simpleTabCompletion id [] commandNames isReversed st+  where+    commandNames = [ cmd | (cmd :| _, _) <- commandsList ]+ simpleTabCompletion ::   Prefix a =>   (String -> String) {- ^ leading transform -} ->@@ -701,16 +903,15 @@   do sendMsg cs (ircZnc (Text.words (Text.pack rest)))      commandSuccess st --- TODO: support time ranges-cmdZncPlayback :: NetworkCommand String-cmdZncPlayback cs st rest =-  case words rest of+cmdZncPlayback :: NetworkCommand (Maybe (String, Maybe (String, ())))+cmdZncPlayback cs st args =+  case args of      -- request everything-    [] -> success "0"+    Nothing -> success "0"      -- current date explicit time-    [timeStr]+    Just (timeStr, Nothing)        | Just tod <- parse timeFormats timeStr ->           do now <- getZonedTime              let (nowTod,t) = (zonedTimeLocalTime . localTimeTimeOfDay <<.~ tod) now@@ -721,7 +922,7 @@              successZoned (fixDay t)      -- explicit date and time-    [dateStr,timeStr]+    Just (dateStr, Just (timeStr, _))        | Just day  <- parse dateFormats dateStr        , Just tod  <- parse timeFormats timeStr ->           do tz <- getCurrentTimeZone
src/Client/Commands/Exec.hs view
@@ -11,24 +11,49 @@ can show channel bans, quiets, invites, and exceptions. -} -module Client.Commands.Exec where+module Client.Commands.Exec+  ( -- * Exec command configuration+    ExecCmd(..) +  -- * Lenses+  , execOutputNetwork+  , execOutputChannel++  -- * Operations+  , parseExecCmd+  , runExecCmd+  ) where+ import           Control.Exception import           Control.Lens import           System.Console.GetOpt import           System.Process +-- | Settings for @/exec@ command.+--+-- When no network or channel are specified the output is sent to the client+-- window.+--+-- When only a network is specified the output is sent as raw IRC commands to+-- that network.+--+-- When only a channel is specified the output is sent as messages on the+-- current network to the given channel.+--+-- When the network and channel are specified the output is sent as messages+-- to the given channel on the given network. data ExecCmd = ExecCmd-  { _execOutputNetwork :: Maybe String-  , _execOutputChannel :: Maybe String-  , _execCommand       :: String-  , _execStdIn         :: String-  , _execArguments     :: [String]+  { _execOutputNetwork :: Maybe String -- ^ output network+  , _execOutputChannel :: Maybe String -- ^ output channel+  , _execCommand       :: String       -- ^ command filename+  , _execStdIn         :: String       -- ^ stdin source+  , _execArguments     :: [String]     -- ^ command arguments   }   deriving (Read,Show)  makeLenses ''ExecCmd +-- | Default values for @/exec@ to be overridden by flags. emptyExecCmd :: ExecCmd emptyExecCmd = ExecCmd   { _execOutputNetwork = Nothing@@ -51,8 +76,13 @@         "Use string as stdin"   ] +-- | Parse the arguments to @/exec@ looking for various flags+-- and the command and its arguments.+-- -- TODO: support quoted strings-parseExecCmd :: String -> Either [String] ExecCmd+parseExecCmd ::+  String                  {- ^ exec arguments          -} ->+  Either [String] ExecCmd {- ^ error or parsed command -} parseExecCmd str =   case getOpt RequireOrder options (words str) of     (_, [] , errs) -> Left ("No command specified":errs)@@ -63,7 +93,11 @@                         $ emptyExecCmd     (_,_, errs) -> Left errs -runExecCmd :: ExecCmd -> IO (Either [String] [String])+-- | Execute the requested command synchronously and return+-- the output.+runExecCmd ::+  ExecCmd                       {- ^ exec configuration          -} ->+  IO (Either [String] [String]) {- ^ error lines or output lines -} runExecCmd e =   do res <- try (readProcess (view execCommand e)                              (view execArguments e)
src/Client/Commands/Interpolation.hs view
@@ -35,6 +35,8 @@   | DefaultChunk ExpansionChunk Text   deriving Show +-- | Parse a 'Text' searching for the expansions as specified in+-- 'ExpansionChunk'. @$$@ is used to escape a single @$@. parseExpansion :: Text -> Maybe [ExpansionChunk] parseExpansion txt =   case parseOnly (many parseChunk <* endOfInput) txt of@@ -63,11 +65,14 @@ parseVariable = IntegerChunk  <$> P.decimal             <|> VariableChunk <$> P.takeWhile1 isAlpha +-- | Attempt to expand all of the elements in the given list using+-- the two expansion functions. If the expansion of any chunk+-- fails the whole expansion fails. resolveMacroExpansions ::   (Text    -> Maybe Text) {- ^ variable resolution       -} ->   (Integer -> Maybe Text) {- ^ argument index resolution -} ->-  [ExpansionChunk]                                          ->-  Maybe Text+  [ExpansionChunk]        {- ^ chunks                    -} ->+  Maybe Text              {- ^ concatenated, expanded chunks -} resolveMacroExpansions var arg xs = Text.concat <$> traverse resolve1 xs   where     resolve1 (LiteralChunk lit) = Just lit
src/Client/Commands/WordCompletion.hs view
@@ -9,7 +9,7 @@  -} module Client.Commands.WordCompletion-  ( Prefix+  ( Prefix(..)   , wordComplete   ) where @@ -77,8 +77,19 @@   $ take n txt  where Edit.Line n txt = view Edit.line box +-- | Class for types that are isomorphic to 'String'+-- and which can support a total order and a prefix+-- predicate.+--+-- @+-- 'Prefix.toString' ('fromString' x) == x+-- 'fromString' ('Prefix.toString' x) == x+-- 'Prefix.isPrefix' x y ==> x '<=' y+-- @ class (IsString a, Ord a) => Prefix a where+  -- | Check if the first argument is a lexicographic prefix of the second.   isPrefix :: a -> a -> Bool+  -- | Convert to a 'String'.   toString :: a -> String  instance Prefix Identifier where@@ -88,6 +99,7 @@ instance Prefix Text where   isPrefix = Text.isPrefixOf   toString = Text.unpack+  tabSearch :: Prefix a => Bool -> a -> a -> [a] -> Maybe a tabSearch isReversed pat cur vals
src/Client/Configuration.hs view
@@ -18,6 +18,8 @@   -- * Configuration type     Configuration(..)   , ConfigurationFailure(..)++  -- * Lenses   , configDefaults   , configServers   , configPalette@@ -29,6 +31,7 @@   , configExtraHighlights   , configUrlOpener   , configIgnores+  , configActivityBar    -- * Loading configuration   , loadConfiguration@@ -79,17 +82,26 @@   , _configExtensions       :: [FilePath] -- ^ paths to shared library   , _configUrlOpener        :: Maybe FilePath -- ^ paths to url opening executable   , _configIgnores          :: HashSet Identifier -- ^ initial ignore list+  , _configActivityBar      :: Bool -- ^ initially visibility of the activity bar   }   deriving Show  makeLenses ''Configuration +-- | Failure cases when loading a configuration file. data ConfigurationFailure-  = ConfigurationParseFailed String++  -- | Error message from reading configuration file+  = ConfigurationReadFailed String++  -- | Error message from parser or lexer+  | ConfigurationParseFailed String++  -- | Error message from loading parsed configuration   | ConfigurationMalformed String-  | ConfigurationReadFailed String   deriving Show +-- | default instance instance Exception ConfigurationFailure  defaultWindowNames :: Text@@ -201,6 +213,8 @@      _configIgnores <- maybe HashSet.empty HashSet.fromList                     <$> sectionOptWith (parseList parseIdentifier) "ignores" +     _configActivityBar <- fromMaybe False <$> sectionOpt  "activity-bar"+      for_ _configNickPadding (\padding ->        when (padding < 0)             (liftConfigParser $@@ -232,9 +246,7 @@     "mention"           -> setAttr palMention     "command"           -> setAttr palCommand     "command-ready"     -> setAttr palCommandReady-    "command-required"  -> setAttr palCommandRequired-    "command-optional"  -> setAttr palCommandOptional-    "command-remaining" -> setAttr palCommandRemaining+    "command-placeholder" -> setAttr palCommandPlaceholder     _                   -> failure "Unknown palette entry"   where     setAttr l =
src/Client/Configuration/Colors.hs view
@@ -72,14 +72,18 @@ parseColor v =   case v of     _ | Just i <- parseInteger v -> parseColorNumber i+     Atom a | Just c <- HashMap.lookup (atomName a) namedColors -> return c+     List [r,g,b]       | Just r' <- parseInteger r       , Just g' <- parseInteger g       , Just b' <- parseInteger b ->          parseRgb r' g' b'+     _ -> failure "Expected a color number, name, or RBG list" +-- | Match integers between 0 and 255 as Terminal colors. parseColorNumber :: Integer -> ConfigParser Color parseColorNumber i   | i < 0 = failure "Negative color not supported"@@ -87,6 +91,8 @@   | i < 256 = return (Color240 (fromInteger (i - 16)))   | otherwise = failure "Color value too high" +-- | Accepts any integer literal or floating literal which can+-- be losslessly converted to an integer. parseInteger :: Value -> Maybe Integer parseInteger v =   case v of
src/Client/Configuration/ServerSettings.hs view
@@ -16,6 +16,8 @@   (   -- * Server settings type     ServerSettings(..)++  -- * Lenses   , ssNicks   , ssUser   , ssReal
src/Client/EventLoop.hs view
@@ -399,7 +399,8 @@         KChar '\t' -> doCommandResult False =<< tabCompletion False st          KChar c    -> changeEditor (Edit.insert c)-        KFun 2     -> eventLoop (over clientDetailView not st)+        KFun 2     -> eventLoop (over clientDetailView  not st)+        KFun 3     -> eventLoop (over clientActivityBar not st)         _          -> eventLoop st      _ -> eventLoop st -- unsupported modifier
src/Client/Image.hs view
@@ -13,9 +13,11 @@  import           Client.Configuration import           Client.Image.ChannelInfo+import           Client.Image.Help import           Client.Image.MaskList import           Client.Image.Message import           Client.Image.Palette+import           Client.Image.PaletteView import           Client.Image.StatusLine import           Client.Image.Textbox import           Client.Image.UserList@@ -49,11 +51,7 @@   where     (mp, st') = messagePane st     (pos, tbImg) = textboxImage st'-    img = vertCat-            [ mp-            , statusLineImage st'-            , tbImg-            ]+    img = mp <-> statusLineImage st' <-> tbImg  messagePaneImages :: ClientState -> [Image] messagePaneImages !st =@@ -66,8 +64,12 @@     (ChannelFocus network channel, FocusMasks mode) ->       maskListImages mode network channel st     (_, FocusWindows) -> windowsImages st+    (_, FocusPalette) -> paletteViewLines pal+    (_, FocusHelp mb) -> helpImageLines mb pal      _ -> chatMessageImages st+  where+    pal = view (clientConfig . configPalette) st  chatMessageImages :: ClientState -> [Image] chatMessageImages st = windowLineProcessor focusedMessages@@ -105,7 +107,12 @@      scroll = view clientScroll st     vh     = h + scroll-    h      = view clientHeight st - 2++    reservedLines+      | view clientActivityBar st = 3+      | otherwise                 = 2++    h      = view clientHeight st - reservedLines     w      = view clientWidth st  windowLinesToImages :: ClientState -> [WindowLine] -> [Image]
src/Client/Image/Arguments.hs view
@@ -53,12 +53,12 @@   case arg of     NoArg           -> emptyImage     ReqTokenArg n a -> leader-                   <|> string (view palCommandRequired pal) n+                   <|> string (view palCommandPlaceholder pal) n                    <|> mkPlaceholders pal a     OptTokenArg n a -> leader-                   <|> string (view palCommandOptional pal) n+                   <|> string (view palCommandPlaceholder pal) (n ++ "?")                    <|> mkPlaceholders pal a     RemainingArg n  -> leader-                   <|> string (view palCommandRemaining pal) n+                   <|> string (view palCommandPlaceholder pal) (n ++ "…")   where     leader = char defAttr ' '
+ src/Client/Image/Help.hs view
@@ -0,0 +1,75 @@+{-|+Module      : Client.Image.Help+Description : Renderer for help lines+Copyright   : (c) Eric Mertens, 2016+License     : ISC+Maintainer  : emertens@gmail.com++This module provides the rendering used for the @/help@ command.++-}+module Client.Image.Help+  ( helpImageLines+  ) where++import           Client.Image.Arguments+import           Client.Image.Palette+import           Client.Image.MircFormatting+import           Client.Commands+import           Client.Commands.Arguments+import           Control.Lens+import           Data.List.NonEmpty (NonEmpty((:|)))+import           Data.Text (Text)+import qualified Data.Text as Text+import           Graphics.Vty.Image++-- | Generate either the list of all commands and their arguments,+-- or when given a command name generate the detailed help text+-- for that command.+helpImageLines ::+  Maybe Text {- ^ optional command name -} ->+  Palette    {- ^ palette               -} ->+  [Image]    {- ^ lines                 -}+helpImageLines mbCmd pal =+  case mbCmd of+    Nothing  -> listAllCommands pal+    Just cmd -> commandHelpLines cmd pal++commandHelpLines :: Text -> Palette -> [Image]+commandHelpLines cmdName pal =+  case view (at cmdName) commands of+    Nothing -> [string (view palError pal) "Unknown command, try /help"]+    Just (Command args doc impl) ->+      reverse $ commandSummary pal (pure cmdName) args+              : emptyImage+              : explainContext impl+              : emptyImage+              : map parseIrcText docs+      where+        docs = Text.lines doc++explainContext :: CommandImpl a -> Image+explainContext impl =+  case impl of+    ClientCommand {} -> go "client command" "works everywhere"+    NetworkCommand{} -> go "network command" "works when focused on active network"+    ChannelCommand{} -> go "channel command" "works when focused on active channel"+    ChatCommand   {} -> go "chat command" "works when focused on an active channel or private message"+  where+    go x y = string (withStyle defAttr bold) x <|>+             string defAttr (": " ++ y)++listAllCommands :: Palette -> [Image]+listAllCommands pal =+  reverse+    [ commandSummary pal name args+    | (name, Command args _ _) <- commandsList ]++commandSummary :: Palette -> NonEmpty Text -> ArgumentSpec a -> Image+commandSummary pal (cmd :| _) args  =+  char defAttr '/' <|>+  text' (view palCommand pal) cmd <|>+  argumentsImage pal' args ""++  where+    pal' = set palCommandPlaceholder defAttr pal
src/Client/Image/Message.hs view
@@ -302,7 +302,7 @@       string (view palSigil pal) sigils <|>       coloredUserInfo pal rm myNicks nick <|>       string defAttr " set mode: " <|>-      separatedParams params+      ircWords params      Authenticate{} -> string defAttr "AUTHENTICATE ***"     BatchStart{}   -> string defAttr "BATCH +"@@ -322,8 +322,15 @@ separatorImage :: Image separatorImage = char (withForeColor defAttr blue) '·' +-- | Process list of 'Text' as individual IRC formatted words+-- separated by a special separator to distinguish parameters+-- from words within parameters. separatedParams :: [Text] -> Image separatedParams = horizCat . intersperse separatorImage . map parseIrcText++-- | Process list of 'Text' as individual IRC formatted words+ircWords :: [Text] -> Image+ircWords = horizCat . intersperse (char defAttr ' ') . map parseIrcText  renderReplyCode :: RenderMode -> MessageRendererParams -> ReplyCode -> Image renderReplyCode rm rp code@(ReplyCode w) =
src/Client/Image/Palette.hs view
@@ -1,4 +1,4 @@-{-# Language TemplateHaskell, OverloadedLists #-}+{-# Language TemplateHaskell, OverloadedLists, OverloadedStrings #-} {-| Module      : Client.Image.Palette Description : Palette of colors used to render the UI@@ -12,6 +12,8 @@   (   -- * Palette type     Palette(..)++  -- * Lenses   , palNicks   , palSelf   , palSelfHighlight@@ -27,15 +29,16 @@   , palMention   , palCommand   , palCommandReady-  , palCommandRequired-  , palCommandOptional-  , palCommandRemaining+  , palCommandPlaceholder +  , paletteMap+   -- * Defaults   , defaultPalette   ) where  import           Control.Lens+import           Data.Text (Text) import           Data.Vector (Vector) import           Graphics.Vty.Attributes @@ -56,9 +59,7 @@   , _palMention       :: Attr -- ^ window name with mention   , _palCommand       :: Attr -- ^ known command   , _palCommandReady  :: Attr -- ^ known command with complete arguments-  , _palCommandRequired  :: Attr -- ^ required argument placeholder-  , _palCommandOptional  :: Attr -- ^ optional argument placeholder-  , _palCommandRemaining :: Attr -- ^ remaining command text placeholder+  , _palCommandPlaceholder :: Attr -- ^ command argument placeholder   }   deriving Show @@ -82,9 +83,7 @@   , _palMention                 = withForeColor defAttr red   , _palCommand                 = withForeColor defAttr yellow   , _palCommandReady            = withForeColor defAttr green-  , _palCommandRequired         = withStyle defAttr reverseVideo-  , _palCommandOptional         = withStyle defAttr reverseVideo-  , _palCommandRemaining        = withStyle defAttr reverseVideo+  , _palCommandPlaceholder      = withStyle defAttr reverseVideo   }  -- | Default nick highlighting colors that look nice in my dark solarized@@ -94,3 +93,21 @@   withForeColor defAttr <$>     [cyan, magenta, green, yellow, blue,      brightCyan, brightMagenta, brightGreen, brightBlue]++paletteMap :: [(Text, ReifiedLens' Palette Attr)]+paletteMap =+  [ ("self"             , Lens palSelf)+  , ("time"             , Lens palTime)+  , ("meta"             , Lens palMeta)+  , ("sigil"            , Lens palSigil)+  , ("label"            , Lens palLabel)+  , ("latency"          , Lens palLatency)+  , ("window-name"      , Lens palWindowName)+  , ("error"            , Lens palError)+  , ("textbox"          , Lens palTextBox)+  , ("activity"         , Lens palActivity)+  , ("mention"          , Lens palMention)+  , ("command"          , Lens palCommand)+  , ("command-ready"    , Lens palCommandReady)+  , ("command-placeholder", Lens palCommandPlaceholder)+  ]
+ src/Client/Image/PaletteView.hs view
@@ -0,0 +1,61 @@+{-|+Module      : Client.Image.PaletteView+Description : View current palette and to see all terminal colors+Copyright   : (c) Eric Mertens, 2016+License     : ISC+Maintainer  : emertens@gmail.com++Lines for the @/palette@ command. This view shows all the colors of+the current palette as well as the colors available in the terminal.++-}++module Client.Image.PaletteView+  ( paletteViewLines+  ) where++import           Client.Image.Palette+import           Control.Lens+import           Data.List+import           Graphics.Vty.Image++digits :: String+digits = "0123456789ABCDEF"++digitImage :: Char -> Image+digitImage d = string defAttr [' ',d,' ']++columns :: [Image] -> Image+columns = horizCat . intersperse (char defAttr ' ')++-- | Generate lines used for @/palette@. These lines show+-- all the colors used in the current palette as well as+-- the colors available for use in palettes.+paletteViewLines :: Palette -> [Image]+paletteViewLines pal =+  [ columns+  $ digitImage digit+  : [ string (withBackColor defAttr c) "   "+    | col <- [0 .. 15]+    , let c = Color240 (row * 16 + col)+    ]+  | (digit,row) <- reverse $ take 15 $ zip (drop 1 digits) [0 ..]+  ] +++  [ columns+  $ digitImage '0'+  : [ string (withBackColor defAttr (ISOColor c)) "   "+    | c <- [0..15]+    ]++  , columns (map digitImage (' ':digits))+  , emptyImage+  , columns+    [ text' (view l pal) name+    | (name, Lens l) <- paletteMap+    ]+  , emptyImage+  , columns+    [ string attr "nicks"+    | attr <- toListOf (palNicks . folded) pal+    ]+  ]
src/Client/Image/StatusLine.hs view
@@ -32,13 +32,15 @@ -- | Renders the status line between messages and the textbox. statusLineImage :: ClientState -> Image statusLineImage st-  = content <|> charFill defAttr '─' fillSize 1+  = activityBar <->+    content <|> charFill defAttr '─' fillSize 1   where     fillSize = max 0 (view clientWidth st - imageWidth content)+    (activitySummary, activityBar) = activityImages st     content = horizCat       [ myNickImage st       , focusImage st-      , activityImage st+      , activitySummary       , detailImage st       , scrollImage st       , latencyImage st@@ -86,21 +88,56 @@   where     attr = view (clientConfig . configPalette . palLabel) st -activityImage :: ClientState -> Image-activityImage st-  | null indicators = emptyImage-  | otherwise       = string defAttr "─[" <|>-                      horizCat indicators <|>-                      string defAttr "]"+activityImages :: ClientState -> (Image, Image)+activityImages st = (summary, activityBar)   where-    windows = views clientWindows Map.elems st+    activityBar+      | view clientActivityBar st = activityBar' <|> activityFill+      | otherwise                 = emptyImage++    summary+      | null indicators = emptyImage+      | otherwise       = string defAttr "─[" <|>+                          horizCat indicators <|>+                          string defAttr "]"++    activityFill = charFill defAttr '─'+                        (max 0 (view clientWidth st - imageWidth activityBar'))+                        1++    activityBar' = foldr baraux emptyImage+                 $ zip winNames+                 $ Map.toList+                 $ view clientWindows st++    baraux (i,(focus,w)) rest+      | n == 0 = rest+      | otherwise = string defAttr "─[" <|>+                    char (view palWindowName pal) i <|>+                    char defAttr              ':' <|>+                    text' (view palLabel pal) focusText <|>+                    char defAttr              ':' <|>+                    string attr               (show n) <|>+                    string defAttr "]" <|> rest+      where+        n   = view winUnread w+        pal = view (clientConfig . configPalette) st+        attr | view winMention w = view palMention pal+             | otherwise         = view palActivity pal+        focusText =+          case focus of+            Unfocused           -> Text.pack "*"+            NetworkFocus net    -> net+            ChannelFocus _ chan -> idText chan++    windows     = views clientWindows Map.elems st     windowNames = view (clientConfig . configWindowNames) st-    winNames = Text.unpack windowNames ++ repeat '?'-    indicators = aux (zip winNames windows)-    aux [] = []-    aux ((i,w):ws)-      | view winUnread w == 0 = aux ws-      | otherwise = char attr i : aux ws+    winNames    = Text.unpack windowNames ++ repeat '?'++    indicators  = foldr aux [] (zip winNames windows)+    aux (i,w) rest+      | view winUnread w == 0 = rest+      | otherwise = char attr i : rest       where         pal = view (clientConfig . configPalette) st         attr | view winMention w = view palMention pal@@ -154,6 +191,10 @@         FocusWindows  -> Just $ string (view palLabel pal) "windows"         FocusInfo     -> Just $ string (view palLabel pal) "info"         FocusUsers    -> Just $ string (view palLabel pal) "users"+        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 ':'
src/Client/Image/Textbox.hs view
@@ -104,8 +104,8 @@ renderLine :: Palette -> String -> Image  renderLine pal ('/':xs)-  | (cmd,rest)            <- break isSpace xs-  , Just (Command spec _) <- view (at (Text.pack cmd)) commands+  | (cmd,rest)              <- break isSpace xs+  , Just (Command spec _ _) <- view (at (Text.pack cmd)) commands   , let attr =           case parseArguments spec rest of             Nothing -> view palCommand      pal
src/Client/Image/UserList.hs view
@@ -8,7 +8,10 @@  This module renders the lines used in the channel user list. -}-module Client.Image.UserList where+module Client.Image.UserList+  ( userListImages+  , userInfoImages+  ) where  import           Client.Configuration import           Client.Image.Message@@ -27,7 +30,9 @@ import           Irc.Identifier import           Irc.UserInfo --- | Render the lines used in a simple user list window.+-- | Render the lines used by the @/users@ command in normal mode.+-- These lines show the count of users having each channel mode+-- in addition to the nicknames of the users. userListImages ::   Text        {- ^ network -} ->   Identifier  {- ^ channel -} ->@@ -77,7 +82,9 @@     usersHashMap =       view (csChannels . ix channel . chanUsers) cs --- | Render lines for detailed channel user list which shows full user info.+-- | Render lines for the @/users@ command in detailed view.+-- Each user will be rendered on a separate line with username+-- and host visible when known. userInfoImages ::   Text        {- ^ network -} ->   Identifier  {- ^ channel -} ->
src/Client/Image/Windows.hs view
@@ -8,7 +8,9 @@ This module implements the rendering of the client window list.  -}-module Client.Image.Windows (windowsImages) where+module Client.Image.Windows+  ( windowsImages+  ) where  import           Client.Configuration import           Client.Image.Palette@@ -22,6 +24,7 @@ import           Graphics.Vty.Image import           Irc.Identifier +-- | Draw the image lines associated with the @/windows@ command. windowsImages :: ClientState -> [Image] windowsImages st   = reverse
src/Client/State.hs view
@@ -14,6 +14,8 @@   (   -- * Client state type     ClientState(..)++  -- * Lenses   , clientWindows   , clientTextBox   , clientConnections@@ -26,6 +28,7 @@   , clientConfig   , clientScroll   , clientDetailView+  , clientActivityBar   , clientSubfocus   , clientNextConnectionId   , clientNetworkMap@@ -33,12 +36,12 @@   , clientConnection   , clientBell   , clientExtensions++  -- * Client operations   , withClientState-  , clientShutdown   , clientStartExtensions+  , clientShutdown   , clientPark--  -- * Client operations   , clientMatcher   , urlPattern @@ -143,6 +146,7 @@   , _clientConfig            :: !Configuration            -- ^ client configuration   , _clientScroll            :: !Int                      -- ^ buffer scroll lines   , _clientDetailView        :: !Bool                     -- ^ use detailed rendering mode+  , _clientActivityBar       :: !Bool                     -- ^ visible activity bar   , _clientBell              :: !Bool                     -- ^ sound a bell next draw    , _clientIgnores           :: !(HashSet Identifier)     -- ^ ignored nicknames@@ -214,6 +218,7 @@         , _clientConfig            = cfg         , _clientScroll            = 0         , _clientDetailView        = False+        , _clientActivityBar       = view configActivityBar cfg         , _clientNextConnectionId  = 0         , _clientBell              = False         , _clientExtensions        = exts@@ -669,7 +674,13 @@     windows = view clientWindows st     (focus,_) = Map.elemAt i windows -changeFocus :: Focus -> ClientState -> ClientState+-- | 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 -} ->+  ClientState changeFocus focus st   = set clientScroll 0   . updatePrevious@@ -682,7 +693,12 @@       | focus == oldFocus = id       | otherwise         = set clientPrevFocus oldFocus -changeSubfocus :: Subfocus -> ClientState -> ClientState+-- | Change the subfocus to the given value, preserve the focus, reset+-- the scroll.+changeSubfocus ::+  Subfocus    {- ^ new subfocus -} ->+  ClientState {- ^ client state -} ->+  ClientState changeSubfocus focus   = set clientScroll 0   . set clientSubfocus focus@@ -718,13 +734,23 @@     isForward = not isReversed     (l,r)     = Map.split (view clientFocus st) (view clientWindows st) -clientHighlights :: NetworkState -> ClientState -> HashSet Identifier+-- | Compute the set of extra identifiers that should be highlighted given+-- a particular network state.+clientHighlights ::+  NetworkState       {- ^ network state               -} ->+  ClientState        {- ^ client state                -} ->+  HashSet Identifier {- ^ extra highlight identifiers -} clientHighlights cs st =   HashSet.insert     (view csNick cs)     (view (clientConfig . configExtraHighlights) st) -clientHighlightsNetwork :: Text -> ClientState -> HashSet Identifier+-- | Compute the set of extra identifiers that should be highlighted given+-- a particular network.+clientHighlightsNetwork ::+  Text               {- ^ network                     -} ->+  ClientState        {- ^ client state                -} ->+  HashSet Identifier {- ^ extra highlight identifiers -} clientHighlightsNetwork network st =   case preview (clientConnection network) st of     Just cs -> clientHighlights cs st
src/Client/State/EditBox/Content.hs view
@@ -66,7 +66,7 @@ endLine s = Line (length s) s  -- | Zipper-ish view of the multi-line content of an 'EditBox'.--- Lines '_above' the '_currentLine' are stored in reverse order.+-- Lines 'above' the 'currentLine' are stored in reverse order. data Content = Content   { _above       :: ![String]   , _currentLine :: !Line
src/Client/State/Focus.hs view
@@ -54,6 +54,8 @@   | FocusUsers       -- ^ Show channel user list   | FocusMasks !Char -- ^ Show channel mask list for given mode   | FocusWindows     -- ^ Show client windows+  | FocusPalette     -- ^ Show current palette+  | FocusHelp (Maybe Text) -- ^ Show help window with optional command   deriving (Eq,Show)  makePrisms ''Subfocus
src/Client/State/Network.hs view
@@ -22,6 +22,7 @@     NetworkState(..)   , newNetworkState +  -- * Lenses   , csNick   , csChannels   , csSocket@@ -143,6 +144,9 @@ csNick :: Lens' NetworkState Identifier csNick = csUserInfo . uiNick +-- | Transmit a 'RawIrcMsg' on the connection associated+-- with the given network. For @PRIVMSG@ and @NOTICE@ overlong+-- commands are detected and transmitted as multiple messages. sendMsg :: NetworkState -> RawIrcMsg -> IO () sendMsg cs msg =   case (view msgCommand msg, view msgParams msg) of@@ -185,13 +189,15 @@           case Text.splitAt (charIx - startChar) currentTxt of             (a,b) -> a : search byteIx charIx b xs' +-- | Construct a new network state using the given settings and+-- default values as specified by the IRC specification. newNetworkState ::-  NetworkId ->-  Text ->-  ServerSettings ->-  NetworkConnection ->-  PingStatus ->-  NetworkState+  NetworkId         {- ^ unique network ID         -} ->+  Text              {- ^ network name              -} ->+  ServerSettings    {- ^ server settings           -} ->+  NetworkConnection {- ^ active network connection -} ->+  PingStatus        {- ^ initial ping status       -} ->+  NetworkState      {- ^ new network state         -} newNetworkState networkId network settings sock ping = NetworkState   { _csNetworkId    = networkId   , _csUserInfo     = UserInfo "*" "" ""@@ -213,7 +219,9 @@   }  -+-- | Used for updates to a 'NetworkState' that require no reply.+--+-- @noReply x = ([], x)@ noReply :: NetworkState -> ([RawIrcMsg], NetworkState) noReply x = ([], x) 
src/Config/FromConfig.hs view
@@ -85,6 +85,12 @@   parseConfig (Text x)          = return x   parseConfig _                 = failure "expected text" +-- | Matches @yes@ as 'True' and @no@ as 'False.+instance FromConfig Bool where+  parseConfig (Atom "yes")      = return True+  parseConfig (Atom "no")       = return False+  parseConfig _                 = failure "expected yes or no"+ -- | Matches 'Number' values ignoring the base instance FromConfig Integer where   parseConfig (Number _ n)      = return n