glirc 2.17 → 2.18
raw patch · 26 files changed
+2139/−164 lines, 26 filesdep +memorydep ~irc-corePVP ok
version bump matches the API change (PVP)
Dependencies added: memory
Dependency ranges changed: irc-core
API changes (from Hackage documentation)
- Client.State: ircIgnorable :: IrcMsg -> ClientState -> Maybe Identifier
- Client.State.Window: [_wlBody] :: WindowLine -> !MessageBody
- Client.State.Window: [_wlFullImage] :: WindowLine -> !Image
- Client.State.Window: [_wlImage] :: WindowLine -> !Image
- Client.State.Window: wlBody :: Lens' WindowLine MessageBody
+ Client.Authentication.Ecdsa: authenticationMode :: Text
+ Client.Authentication.Ecdsa: computeResponse :: FilePath -> Text -> IO (Either String Text)
+ Client.Authentication.Ecdsa: encodeUsername :: Text -> Text
+ Client.Configuration.ServerSettings: [_ssLogDir] :: ServerSettings -> Maybe FilePath
+ Client.Configuration.ServerSettings: [_ssSaslEcdsaFile] :: ServerSettings -> !(Maybe FilePath)
+ Client.Configuration.ServerSettings: ssLogDir :: Lens' ServerSettings (Maybe FilePath)
+ Client.Configuration.ServerSettings: ssSaslEcdsaFile :: Lens' ServerSettings (Maybe FilePath)
+ Client.Image.Message: cleanChar :: Char -> Char
+ Client.Image.PackedImage: _Image' :: Iso' Image' Image
+ Client.Image.PackedImage: data Image'
+ Client.Image.PackedImage: instance (Client.Image.PackedImage.GMirror f1 g1, Client.Image.PackedImage.GMirror f2 g2) => Client.Image.PackedImage.GMirror (f1 GHC.Generics.:*: f2) (g1 GHC.Generics.:*: g2)
+ Client.Image.PackedImage: instance (Client.Image.PackedImage.GMirror f1 g1, Client.Image.PackedImage.GMirror f2 g2) => Client.Image.PackedImage.GMirror (f1 GHC.Generics.:+: f2) (g1 GHC.Generics.:+: g2)
+ Client.Image.PackedImage: instance Client.Image.PackedImage.GMirror GHC.Generics.U1 GHC.Generics.U1
+ Client.Image.PackedImage: instance Client.Image.PackedImage.GMirror f g => Client.Image.PackedImage.GMirror (GHC.Generics.M1 i c f) (GHC.Generics.M1 j d g)
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror Client.Image.PackedImage.Image' Graphics.Vty.Image.Internal.Image
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror Data.Text.Internal.Lazy.Text Data.Text.Internal.Text
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror Data.Text.Internal.Text Data.Text.Internal.Lazy.Text
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror GHC.Types.Int GHC.Types.Int
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror Graphics.Vty.Attributes.Attr Graphics.Vty.Attributes.Attr
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror Graphics.Vty.Image.Internal.Image Client.Image.PackedImage.Image'
+ Client.Image.PackedImage: instance Client.Image.PackedImage.Mirror a b => Client.Image.PackedImage.GMirror (GHC.Generics.K1 i a) (GHC.Generics.K1 j b)
+ Client.Image.PackedImage: instance GHC.Generics.Generic Client.Image.PackedImage.Image'
+ Client.Image.PackedImage: instance GHC.Generics.Generic Graphics.Vty.Image.Internal.Image
+ Client.Image.PackedImage: instance GHC.Show.Show Client.Image.PackedImage.Image'
+ Client.Log: LogLine :: FilePath -> Day -> Text -> Text -> LogLine
+ Client.Log: [logBaseDir] :: LogLine -> FilePath
+ Client.Log: [logDay] :: LogLine -> Day
+ Client.Log: [logLine] :: LogLine -> Text
+ Client.Log: [logTarget] :: LogLine -> Text
+ Client.Log: data LogLine
+ Client.Log: ignoreProblems :: IO () -> IO ()
+ Client.Log: renderLogLine :: ClientMessage -> FilePath -> Identifier -> Maybe LogLine
+ Client.Log: writeLogLine :: LogLine -> IO ()
+ Client.Message: ChatSummary :: {-# UNPACK #-} !Identifier -> IrcSummary
+ Client.Message: CtcpSummary :: {-# UNPACK #-} !Identifier -> IrcSummary
+ Client.Message: JoinSummary :: {-# UNPACK #-} !Identifier -> IrcSummary
+ Client.Message: NickSummary :: {-# UNPACK #-} !Identifier -> {-# UNPACK #-} !Identifier -> IrcSummary
+ Client.Message: NoSummary :: IrcSummary
+ Client.Message: PartSummary :: {-# UNPACK #-} !Identifier -> IrcSummary
+ Client.Message: QuitSummary :: {-# UNPACK #-} !Identifier -> IrcSummary
+ Client.Message: ReplySummary :: {-# UNPACK #-} !ReplyCode -> IrcSummary
+ Client.Message: data IrcSummary
+ Client.Message: instance GHC.Classes.Eq Client.Message.IrcSummary
+ Client.Message: instance GHC.Show.Show Client.Message.IrcSummary
+ Client.Message: msgSummary :: MessageBody -> IrcSummary
+ Client.State: [_clientLogQueue] :: ClientState -> ![LogLine]
+ Client.State: advanceNetworkFocus :: ClientState -> ClientState
+ Client.State: clientLogQueue :: Lens' ClientState [LogLine]
+ Client.State: identIgnored :: Identifier -> ClientState -> Bool
+ Client.State: urlMatches :: Text -> [Text]
+ Client.State.EditBox: insertDigraph :: EditBox -> Maybe EditBox
+ Client.State.EditBox.Content: digraph :: Content -> Maybe Content
+ Client.State.Focus: FocusDigraphs :: Subfocus
+ Client.State.Network: AS_EcdsaStarted :: AuthenticateState
+ Client.State.Network: AS_EcdsaWaitChallenge :: AuthenticateState
+ Client.State.Network: AS_None :: AuthenticateState
+ Client.State.Network: AS_PlainStarted :: AuthenticateState
+ Client.State.Network: [_csAuthenticationState] :: NetworkState -> !AuthenticateState
+ Client.State.Network: _PingConnecting :: Prism' PingStatus (Int, Maybe UTCTime)
+ Client.State.Network: csAuthenticationState :: Lens' NetworkState AuthenticateState
+ Client.State.Network: data AuthenticateState
+ Client.State.Network: instance GHC.Show.Show Client.State.Network.AuthenticateState
+ Client.State.Window: [_wlFullImage'] :: WindowLine -> !Image'
+ Client.State.Window: [_wlImage'] :: WindowLine -> !Image'
+ Client.State.Window: [_wlSummary] :: WindowLine -> !IrcSummary
+ Client.State.Window: instance Control.Lens.Each.Each Client.State.Window.WindowLines Client.State.Window.WindowLines Client.State.Window.WindowLine Client.State.Window.WindowLine
+ Client.State.Window: wlSummary :: Lens' WindowLine IrcSummary
+ Client.View.Digraphs: digraphLines :: ClientState -> [Image]
- Client.Configuration.ServerSettings: ServerSettings :: !(NonEmpty Text) -> !Text -> !Text -> !Text -> !(Maybe Text) -> !(Maybe Text) -> !(Maybe Text) -> !HostName -> !(Maybe PortNumber) -> !UseTls -> !(Maybe FilePath) -> !(Maybe FilePath) -> ![[ExpansionChunk]] -> !(Maybe HostName) -> !PortNumber -> ![FilePath] -> ![Identifier] -> !Rational -> !Rational -> ![Text] -> !(Maybe Text) -> !Int -> Bool -> WordCompletionMode -> ServerSettings
+ Client.Configuration.ServerSettings: ServerSettings :: !(NonEmpty Text) -> !Text -> !Text -> !Text -> !(Maybe Text) -> !(Maybe Text) -> !(Maybe Text) -> !(Maybe FilePath) -> !HostName -> !(Maybe PortNumber) -> !UseTls -> !(Maybe FilePath) -> !(Maybe FilePath) -> ![[ExpansionChunk]] -> !(Maybe HostName) -> !PortNumber -> ![FilePath] -> ![Identifier] -> !Rational -> !Rational -> ![Text] -> !(Maybe Text) -> !Int -> Bool -> WordCompletionMode -> Maybe FilePath -> ServerSettings
- Client.Image.Message: metadataImg :: IrcMsg -> Maybe (Image, Identifier, Maybe Identifier)
+ Client.Image.Message: metadataImg :: IrcSummary -> Maybe (Image, Identifier, Maybe Identifier)
- Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Subfocus -> ![Focus] -> !(IntMap NetworkState) -> !Int -> !ConnectionContext -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !Vty -> !EditBox -> !Int -> !Int -> !Int -> !Bool -> !Bool -> !Bool -> (Maybe Regex) -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ClientState
+ Client.State: ClientState :: !(Map Focus Window) -> !Focus -> !Focus -> !Subfocus -> ![Focus] -> !(IntMap NetworkState) -> !Int -> !ConnectionContext -> !(TQueue NetworkEvent) -> !(HashMap Text NetworkId) -> !Configuration -> !Vty -> !EditBox -> !Int -> !Int -> !Int -> !Bool -> !Bool -> !Bool -> (Maybe Regex) -> !Bool -> !(HashSet Identifier) -> !ExtensionState -> ![LogLine] -> ClientState
- Client.State.EditBox: class HasLine c_aJgF where pos = (.) line pos text = (.) line text
+ Client.State.EditBox: class HasLine c_aO7k where pos = (.) line pos text = (.) line text
- Client.State.EditBox: line :: HasLine c_aJgF => Lens' c_aJgF Line
+ Client.State.EditBox: line :: HasLine c_aO7k => Lens' c_aO7k Line
- Client.State.EditBox: pos :: HasLine c_aJgF => Lens' c_aJgF Int
+ Client.State.EditBox: pos :: HasLine c_aO7k => Lens' c_aO7k Int
- Client.State.EditBox: text :: HasLine c_aJgF => Lens' c_aJgF String
+ Client.State.EditBox: text :: HasLine c_aO7k => Lens' c_aO7k String
- Client.State.EditBox.Content: class HasLine c_aJgF where pos = (.) line pos text = (.) line text
+ Client.State.EditBox.Content: class HasLine c_aO7k where pos = (.) line pos text = (.) line text
- Client.State.EditBox.Content: line :: HasLine c_aJgF => Lens' c_aJgF Line
+ Client.State.EditBox.Content: line :: HasLine c_aO7k => Lens' c_aO7k Line
- Client.State.EditBox.Content: pos :: HasLine c_aJgF => Lens' c_aJgF Int
+ Client.State.EditBox.Content: pos :: HasLine c_aO7k => Lens' c_aO7k Int
- Client.State.EditBox.Content: text :: HasLine c_aJgF => Lens' c_aJgF String
+ Client.State.EditBox.Content: text :: HasLine c_aO7k => Lens' c_aO7k String
- Client.State.Network: NetworkState :: !NetworkId -> !(HashMap Identifier ChannelState) -> !NetworkConnection -> !ModeTypes -> ![Char] -> !Transaction -> ![Char] -> ![Char] -> !ServerSettings -> !UserInfo -> !(HashMap Identifier UserAndHost) -> !Int -> !Text -> !(Maybe UTCTime) -> !PingStatus -> !(Maybe UTCTime) -> ![Text] -> NetworkState
+ Client.State.Network: NetworkState :: !NetworkId -> !(HashMap Identifier ChannelState) -> !NetworkConnection -> !ModeTypes -> ![Char] -> !Transaction -> ![Char] -> ![Char] -> !ServerSettings -> !UserInfo -> !(HashMap Identifier UserAndHost) -> !Int -> !Text -> !(Maybe UTCTime) -> !PingStatus -> !(Maybe UTCTime) -> ![Text] -> !AuthenticateState -> NetworkState
- Client.State.Window: Window :: ![WindowLine] -> !Int -> !Int -> !Bool -> Window
+ Client.State.Window: Window :: !WindowLines -> !Int -> !Int -> !Bool -> Window
- Client.State.Window: WindowLine :: !MessageBody -> {-# UNPACK #-} !Text -> !Image -> !Image -> !WindowLineImportance -> {-# UNPACK #-} !UTCTime -> WindowLine
+ Client.State.Window: WindowLine :: !IrcSummary -> {-# UNPACK #-} !Text -> !Image' -> !Image' -> !WindowLineImportance -> {-# UNPACK #-} !UTCTime -> WindowLine
- Client.State.Window: [_winMessages] :: Window -> ![WindowLine]
+ Client.State.Window: [_winMessages] :: Window -> !WindowLines
- Client.State.Window: winMessages :: Lens' Window [WindowLine]
+ Client.State.Window: winMessages :: Lens' Window WindowLines
- Client.View.UrlSelection: urlSelectionView :: Focus -> ClientState -> [Image]
+ Client.View.UrlSelection: urlSelectionView :: Focus -> String -> ClientState -> [Image]
Files
- ChangeLog.md +8/−0
- README.md +7/−3
- exec/Main.hs +4/−4
- glirc.cabal +9/−3
- src/Client/Authentication/Ecdsa.hs +45/−0
- src/Client/Commands.hs +63/−29
- src/Client/Configuration.hs +2/−0
- src/Client/Configuration/ServerSettings.hs +7/−1
- src/Client/EventLoop.hs +77/−29
- src/Client/Image/Message.hs +13/−15
- src/Client/Image/PackedImage.hs +121/−0
- src/Client/Image/StatusLine.hs +1/−0
- src/Client/Log.hs +90/−0
- src/Client/Message.hs +42/−1
- src/Client/State.hs +85/−32
- src/Client/State/EditBox.hs +8/−0
- src/Client/State/EditBox/Content.hs +15/−0
- src/Client/State/Focus.hs +1/−0
- src/Client/State/Network.hs +48/−12
- src/Client/State/Window.hs +30/−8
- src/Client/View.hs +4/−1
- src/Client/View/Digraphs.hs +56/−0
- src/Client/View/Mentions.hs +2/−2
- src/Client/View/Messages.hs +11/−11
- src/Client/View/UrlSelection.hs +31/−13
- src/Digraphs.hs +1359/−0
ChangeLog.md view
@@ -1,5 +1,13 @@ # Revision history for glirc2 +## 2.18++* Add digraph support under `M-k` and `/digraphs`+* Add ECDSA-NIST256P-CHALLENGE support for Freenode via Tor+* Load mask list on `/masks`+* Add `C-x` to change to next network window+* Allow `/clear NETWORK *` to clear all windows for the given network+ ## 2.17 * Add `reconnect-attempts` setting
README.md view
@@ -194,7 +194,8 @@ | `realname` | text | real name / GECOS | | `password` | text | server password | | `sasl-username` | text | SASL username |-| `sasl-password` | text | SASL password |+| `sasl-password` | text | SASL password (PLAIN mode) |+| `sasl-ecdsa-key` | text | Path ecdsa private key file (ECDSA-NIST256P-CHALLENGE mode) | | `tls` | yes/yes-insecure/no | use TLS to connect (insecure mode disables certificate checks) | | `tls-client-cert` | text | path to TLS client certificate | | `tls-client-key` | text | path to TLS client key |@@ -272,6 +273,7 @@ * `/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.@@ -355,8 +357,9 @@ Window navigation -* `C-n` next channel-* `C-p` previous channel+* `C-n` next window+* `C-p` previous window+* `C-x` next network * `M-#` jump to window - `1234567890qwertyuiop!@#$%^&*()QWERTYUIOP` * `M-a` jump to activity * `M-s` jump to previous window@@ -379,6 +382,7 @@ * `M-Backspace` delete word backwards * `M-d` delete word forwards * `M-Enter` insert newline+* `M-k` insert digraph (2-characters before cursor) * `Tab` nickname completion
exec/Main.hs view
@@ -36,11 +36,11 @@ eventLoop initialNetworkLogic :: Options -> ClientState -> IO ClientState-initialNetworkLogic opts st- | view optNoConnect opts = return st- | otherwise = addInitialNetworks networks st+initialNetworkLogic opts st = addInitialNetworks (nub networks) st where- networks = nub (clientAutoconnects st ++ view optInitialNetworks opts)+ networks+ | view optNoConnect opts = view optInitialNetworks opts+ | otherwise = view optInitialNetworks opts ++ clientAutoconnects st -- | Load configuration and handle errors along the way. loadConfiguration' :: Maybe FilePath -> IO Configuration
glirc.cabal view
@@ -1,5 +1,5 @@ name: glirc-version: 2.17+version: 2.18 synopsis: Console IRC client description: Console IRC client .@@ -54,7 +54,8 @@ default-language: Haskell2010 build-tools: hsc2hs - exposed-modules: Client.CApi+ exposed-modules: Client.Authentication.Ecdsa+ Client.CApi Client.CApi.Exports Client.CApi.Types Client.Commands@@ -75,9 +76,11 @@ Client.Image.Arguments Client.Image.Message Client.Image.MircFormatting+ Client.Image.PackedImage Client.Image.Palette Client.Image.StatusLine Client.Image.Textbox+ Client.Log Client.Message Client.Network.Async Client.Network.Connect@@ -91,6 +94,7 @@ Client.State.Window Client.View Client.View.ChannelInfo+ Client.View.Digraphs Client.View.Help Client.View.MaskList Client.View.Mentions@@ -103,6 +107,7 @@ other-modules: LensUtils StrictUnit+ Digraphs Paths_glirc build-depends: base >=4.9 && <4.10,@@ -118,8 +123,9 @@ filepath >=1.4.1 && <1.5, gitrev >=1.2 && <1.3, hashable >=1.2.4 && <1.3,- irc-core >=2.1.1 && <2.2,+ irc-core >=2.2 && <2.3, lens >=4.14 && <4.15,+ memory >=0.13 && <0.14, network >=2.6.2 && <2.7, process >=1.4.2 && <1.5, regex-tdfa >=1.2 && <1.3,
+ src/Client/Authentication/Ecdsa.hs view
@@ -0,0 +1,45 @@+{-# Language OverloadedStrings #-}+{-|+Module : Client.Authentication.Ecdsa+Description : Binding to ecdsatool+Copyright : (c) Eric Mertens, 2016+License : ISC+Maintainer : emertens@gmail.com++Implementation of ECDSA-NIST256P-CHALLENGE SASL authentication mode+as implemented at <https://github.com/kaniini/ecdsatool> and used+on Freenode.++Using this mode requires that the @ecdsa@ utility program is available+in your search path.++-}+module Client.Authentication.Ecdsa+ ( authenticationMode+ , encodeUsername+ , computeResponse+ ) where++import Client.Configuration (resolveConfigurationPath)+import Control.Exception (displayException, try)+import Data.ByteArray.Encoding+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import System.IO.Error (IOError)+import System.Process (readProcess)++authenticationMode :: Text+authenticationMode = "ECDSA-NIST256P-CHALLENGE"++encodeUsername :: Text -> Text+encodeUsername = Text.decodeUtf8 . convertToBase Base64 . Text.encodeUtf8++computeResponse :: FilePath -> Text -> IO (Either String Text)+computeResponse privateKeyFile challenge =+ do path <- resolveConfigurationPath privateKeyFile+ res <- try (readProcess "ecdsatool" ["sign", path, Text.unpack challenge] "")+ return $! case words <$> res of+ Right [resp] -> Right $! Text.pack resp+ Right _ -> Left "bad sasl ecdsa response message"+ Left e -> Left (displayException (e :: IOError))
src/Client/Commands.hs view
@@ -103,8 +103,8 @@ -- and documentation. The arguments and implementation must match so that -- the parsed arguments will match what the implementation expects. data Command = forall a. Command- -- | Names of this command, first in the list is the "primary" name- { cmdNames :: NonEmpty Text+ { -- | Names of this command, first in the list is the "primary" name+ cmdNames :: NonEmpty Text -- | Specification of the arguments of the command , cmdArgumentSpec :: ArgumentSpec a -- | Multi-line IRC-formatted documentation text used for @/help@@@ -201,7 +201,7 @@ | Just !cs <- preview (clientConnection network) st -> do now <- getZonedTime let msgTxt = Text.pack $ takeWhile (/='\n') msg- ircMsg = ircPrivmsg channel msgTxt+ ircMsg = ircPrivmsg (idText channel) msgTxt myNick = UserInfo (view csNick cs) "" "" entry = ClientMessage { _msgTime = now@@ -209,7 +209,7 @@ , _msgBody = IrcBody (Privmsg myNick channel msgTxt) } sendMsg cs ircMsg- commandSuccess $ recordChannelMessage network channel entry st+ commandSuccess $! recordChannelMessage network channel entry st _ -> commandFailureMsg "This command requires an active channel" st @@ -315,6 +315,7 @@ \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@@ -370,6 +371,14 @@ $ ClientCommand cmdPalette noClientTab , Command+ (pure "digraphs")+ NoArg+ "Show the table of digraphs.\n\+ \\n\+ \To enter a digraph type the two-character entry and press M-k.\n"+ $ ClientCommand cmdDigraphs noClientTab++ , Command (pure "exec") (RemainingArg "arguments") "Execute a command synchnonously sending the to a configuration destination.\n\@@ -761,17 +770,26 @@ Nothing -> clearFocus (view clientFocus st) Just ("*", Nothing) -> clearFocus Unfocused Just (network, Nothing) -> clearFocus (NetworkFocus (Text.pack network))+ Just (network, Just ("*", _)) -> clearNetworkWindows network Just (network, Just (channel, _)) -> clearFocus (ChannelFocus (Text.pack network) (mkId (Text.pack channel))) where- clearFocus focus = commandSuccess (focusEffect (windowEffect st))+ clearNetworkWindows network+ = commandSuccess+ $ foldl' (flip clearFocus1) st+ $ filter (\x -> focusNetwork x == Just (Text.pack network))+ $ views clientWindows Map.keys st++ clearFocus focus = commandSuccess (clearFocus1 focus st)++ clearFocus1 focus st' = focusEffect (windowEffect st') where windowEffect | isActive = setWindow (Just emptyWindow) | otherwise = setWindow Nothing focusEffect- | not isActive && view clientFocus st == focus = advanceFocus+ | not isActive && view clientFocus st' == focus = advanceFocus | otherwise = id setWindow = set (clientWindows . at focus)@@ -779,9 +797,9 @@ isActive = case focus of Unfocused -> False- NetworkFocus network -> has (clientConnection network) st+ NetworkFocus network -> has (clientConnection network) st' ChannelFocus network channel -> has (clientConnection network- .csChannels . ix channel) st+ .csChannels . ix channel) st' -- | Implementation of @/quote@. Parses arguments as a raw IRC command and -- sends to the current network.@@ -805,9 +823,9 @@ , _msgNetwork = network , _msgBody = IrcBody (Ctcp myNick channelId "ACTION" (Text.pack rest)) }- sendMsg cs (ircPrivmsg channelId actionTxt)+ sendMsg cs (ircPrivmsg (idText channelId) actionTxt) commandSuccess- $ recordChannelMessage network channelId entry st+ $! recordChannelMessage network channelId entry st -- | Implementation of @/ctcp@ cmdCtcp :: NetworkCommand (String, (String, String))@@ -816,7 +834,7 @@ argTxt = Text.pack args tgtTxt = Text.pack target - sendMsg cs (ircPrivmsg (mkId tgtTxt) ("\^A" <> cmdTxt <> " " <> argTxt <> "\^A"))+ sendMsg cs (ircPrivmsg tgtTxt ("\^A" <> cmdTxt <> " " <> argTxt <> "\^A")) chatCommand (\src tgt -> Ctcp src tgt cmdTxt argTxt) tgtTxt cs st@@ -829,7 +847,7 @@ do let restTxt = Text.pack rest tgtTxt = Text.pack target - sendMsg cs (ircNotice (mkId tgtTxt) restTxt)+ sendMsg cs (ircNotice tgtTxt restTxt) chatCommand (\src tgt -> Notice src tgt restTxt) tgtTxt cs st@@ -842,7 +860,7 @@ do let restTxt = Text.pack rest tgtTxt = Text.pack target - sendMsg cs (ircPrivmsg (mkId tgtTxt) restTxt)+ sendMsg cs (ircPrivmsg tgtTxt restTxt) chatCommand (\src tgt -> Privmsg src tgt restTxt) tgtTxt cs st@@ -879,7 +897,7 @@ | targetId <- targetIds ] return $! foldl' (\acc (targetId, entry) ->- recordChannelMessage network targetId entry acc)+ recordChannelMessage network targetId entry acc) st entries @@ -927,15 +945,19 @@ success x = commandSuccess (changeSubfocus (FocusWindows x) st) --- | Implementation of @/mentions@ command. Set subfocus to Windows.+-- | Implementation of @/mentions@ command. Set subfocus to Mentions. cmdMentions :: ClientCommand () cmdMentions st _ = commandSuccess (changeSubfocus FocusMentions st) --- | Implementation of @/palette@ command. Set subfocus to Windows.+-- | Implementation of @/palette@ command. Set subfocus to Palette. cmdPalette :: ClientCommand () cmdPalette st _ = commandSuccess (changeSubfocus FocusPalette st) --- | Implementation of @/help@ command. Set subfocus to Windows.+-- | Implementation of @/digraphs@ command. Set subfocus to Digraphs.+cmdDigraphs :: ClientCommand ()+cmdDigraphs st _ = commandSuccess (changeSubfocus FocusDigraphs st)++-- | Implementation of @/help@ command. Set subfocus to Help. cmdHelp :: ClientCommand (Maybe (String, ())) cmdHelp st mb = commandSuccess (changeSubfocus focus st) where@@ -1036,7 +1058,7 @@ cmdIson :: NetworkCommand String cmdIson cs st rest =- do sendMsg cs (ircIson [Text.pack (unwords (words rest))])+ do sendMsg cs (ircIson (Text.pack <$> words rest)) commandSuccess st cmdUserhost :: NetworkCommand String@@ -1118,7 +1140,7 @@ cmdNick :: NetworkCommand (String, ()) cmdNick cs st (nick,_) =- do sendMsg cs (ircNick (mkId (Text.pack nick)))+ do sendMsg cs (ircNick (Text.pack nick)) commandSuccess st cmdPart :: ChannelCommand String@@ -1172,10 +1194,17 @@ cmdChannelInfo _ _ st _ = commandSuccess (changeSubfocus FocusInfo st) cmdMasks :: ChannelCommand (String,())-cmdMasks _ cs st (rest,_) =+cmdMasks channel cs st (rest,_) = case rest of [mode] | mode `elem` view (csModeTypes . modesLists) cs ->- commandSuccess (changeSubfocus (FocusMasks mode) st)++ do let connecting = has (csPingStatus . _PingConnecting) cs+ listLoaded = has (csChannels . ix channel . chanLists . ix mode) cs+ unless (connecting || listLoaded)+ (sendMsg cs (ircMode channel [Text.singleton mode]))++ commandSuccess (changeSubfocus (FocusMasks mode) st)+ _ -> commandFailureMsg "Unknown mask mode" st cmdKick :: ChannelCommand (String, String)@@ -1378,9 +1407,8 @@ focus@(ChannelFocus network channel) -> toListOf ( clientWindows . ix focus- . winMessages . folded- . wlBody . _IrcBody- . folding msgActor . to userNick+ . winMessages . each+ . wlSummary . folding summaryActor . filtered isActive . filtered isNotSelf ) st where@@ -1395,6 +1423,15 @@ _ -> [] + where+ -- Returns the 'Identifier' of the nickname responsible for+ -- the window line when that action was significant enough to+ -- be considered a hint for tab completion.+ summaryActor :: IrcSummary -> Maybe Identifier+ summaryActor (ChatSummary who) = Just who+ summaryActor (JoinSummary who) = Just who+ summaryActor _ = Nothing+ -- | Use the *!*@host masks of users for channel lists when setting list modes -- -- Use the channel's mask list for removing modes@@ -1509,7 +1546,7 @@ sendToChannel cs channel msgs = commandSuccess =<< foldM (\st1 msg ->- do sendMsg cs (ircPrivmsg (mkId channel) msg)+ do sendMsg cs (ircPrivmsg channel msg) chatCommand' (\src tgt -> Privmsg src tgt msg) channel@@ -1553,10 +1590,7 @@ where focus = view clientFocus st - urlMatches :: Text -> [Text]- urlMatches = getAllTextMatches . match urlPattern-- urls = toListOf ( clientWindows . ix focus . winMessages . folded . wlText+ urls = toListOf ( clientWindows . ix focus . winMessages . each . wlText . folding urlMatches) st doUrlOpen opener n =
src/Client/Configuration.hs view
@@ -260,6 +260,7 @@ "password" -> setFieldMb ssPassword "sasl-username" -> setFieldMb ssSaslUsername "sasl-password" -> setFieldMb ssSaslPassword+ "sasl-ecdsa-key" -> setFieldWithMb ssSaslEcdsaFile parseString "hostname" -> setFieldWith ssHostName parseString "port" -> setFieldWithMb ssPort parseNum "tls" -> setFieldWith ssTls parseUseTls@@ -277,6 +278,7 @@ "reconnect-attempts" -> setField ssReconnectAttempts "autoconnect" -> setField ssAutoconnect "nick-completion" -> setFieldWith ssNickCompletion parseNickCompletion+ "log-dir" -> setFieldWithMb ssLogDir parseString _ -> failure "Unknown setting" where setField l = setFieldWith l parseConfig
src/Client/Configuration/ServerSettings.hs view
@@ -25,6 +25,7 @@ , ssPassword , ssSaslUsername , ssSaslPassword+ , ssSaslEcdsaFile , ssHostName , ssPort , ssTls@@ -42,6 +43,7 @@ , ssReconnectAttempts , ssAutoconnect , ssNickCompletion+ , ssLogDir -- * Load function , loadDefaultServerSettings@@ -71,7 +73,8 @@ , _ssUserInfo :: !Text -- ^ CTCP userinfo , _ssPassword :: !(Maybe Text) -- ^ server password , _ssSaslUsername :: !(Maybe Text) -- ^ SASL username- , _ssSaslPassword :: !(Maybe Text) -- ^ SASL password+ , _ssSaslPassword :: !(Maybe Text) -- ^ SASL plain password+ , _ssSaslEcdsaFile :: !(Maybe FilePath) -- ^ SASL ecdsa private key , _ssHostName :: !HostName -- ^ server hostname , _ssPort :: !(Maybe PortNumber) -- ^ server port , _ssTls :: !UseTls -- ^ use TLS to connect@@ -89,6 +92,7 @@ , _ssReconnectAttempts:: !Int -- ^ The number of reconnect attempts to make on error , _ssAutoconnect :: Bool -- ^ Connect to this network on server startup , _ssNickCompletion :: WordCompletionMode -- ^ Nick completion mode for this server+ , _ssLogDir :: Maybe FilePath -- ^ Directory to save logs of chat } deriving Show @@ -116,6 +120,7 @@ , _ssPassword = Text.pack <$> lookup "IRCPASSWORD" env , _ssSaslUsername = Nothing , _ssSaslPassword = Text.pack <$> lookup "SASLPASSWORD" env+ , _ssSaslEcdsaFile = Nothing , _ssHostName = "" , _ssPort = Nothing , _ssTls = UseInsecure@@ -133,4 +138,5 @@ , _ssReconnectAttempts= 6 -- six feels great , _ssAutoconnect = False , _ssNickCompletion = defaultNickWordCompleteMode+ , _ssLogDir = Nothing }
src/Client/EventLoop.hs view
@@ -15,6 +15,7 @@ ( eventLoop ) where +import qualified Client.Authentication.Ecdsa as Ecdsa import Client.CApi import Client.Commands import Client.Commands.Interpolation@@ -23,6 +24,7 @@ import Client.Hook import Client.Hooks import Client.Image+import Client.Log import Client.Message import Client.Network.Async import Client.State@@ -47,6 +49,7 @@ import GHC.IO.Exception (IOErrorType(..), ioe_type) import Graphics.Vty import Irc.Codes+import Irc.Commands import Irc.Message import Irc.RawIrcMsg import LensUtils@@ -96,6 +99,7 @@ eventLoop st = do let vty = view clientVty st when (view clientBell st) (beep vty)+ processLogEntries st let (pic, st') = clientPicture (clientTick st) update vty pic@@ -117,6 +121,10 @@ beep :: Vty -> IO () beep = ringTerminalBell . outputIface +processLogEntries :: ClientState -> IO ()+processLogEntries =+ traverse_ writeLogLine . reverse . view clientLogQueue+ -- | Respond to a network connection successfully connecting. doNetworkOpen :: NetworkId {- ^ network id -} ->@@ -237,41 +245,75 @@ case stateHook (cookIrcMsg raw) of Nothing -> return st1 -- Message ignored- Just irc -> do traverse_ (sendMsg cs) replies- clientResponse time' irc cs st2- where- -- state with message recorded- recSt = case viewHook irc of- Nothing -> st1 -- Message hidden- Just irc' -> recordIrcMessage network target msg st1- where- myNick = view csNick cs- target = msgTarget myNick irc- msg = ClientMessage- { _msgTime = time'- , _msgNetwork = network- , _msgBody = IrcBody irc'- }+ Just irc ->+ do -- state with message recorded+ -- record messages *before* applying state changes+ let st2 =+ case viewHook irc of+ Nothing -> st1 -- Message hidden+ Just irc' -> recordIrcMessage network target msg st1+ where+ myNick = view csNick cs+ target = msgTarget myNick irc+ msg = ClientMessage+ { _msgTime = time'+ , _msgNetwork = network+ , _msgBody = IrcBody irc'+ } - -- record messages *before* applying the changes- --- -- Note: it's important to do this before 'clientResponse'- -- as $nick won't be set until 'doWelcome' happens.- (replies, st2) = applyMessageToClientState time irc networkId cs recSt+ let (replies, st3) = applyMessageToClientState time irc networkId cs st2 + traverse_ (sendMsg cs) replies+ clientResponse time' irc cs st3++ -- | Client-level responses to specific IRC messages. -- This is in contrast to the connection state tracking logic in--- "Client.NetworkState "+-- "Client.NetworkState" clientResponse :: ZonedTime -> IrcMsg -> NetworkState -> ClientState -> IO ClientState clientResponse now irc cs st = case irc of Reply RPL_WELCOME _ ->- foldM- (processConnectCmd now cs)- st- (view (csSettings . ssConnectCmds) cs)+ -- run connection commands with the network focused and restore it afterward+ do let focus = NetworkFocus (view csNetwork cs)+ st' <- foldM (processConnectCmd now cs)+ (set clientFocus focus st)+ (view (csSettings . ssConnectCmds) cs)+ return $! set clientFocus (view clientFocus st) st'++ Authenticate challenge+ | AS_EcdsaWaitChallenge <- view csAuthenticationState cs ->+ processSaslEcdsa now challenge cs st+ _ -> return st ++processSaslEcdsa ::+ ZonedTime {- ^ message time -} ->+ Text {- ^ challenge -} ->+ NetworkState {- ^ network state -} ->+ ClientState {- ^ client state -} ->+ IO ClientState+processSaslEcdsa now challenge cs st =+ case view ssSaslEcdsaFile ss of+ Nothing ->+ do sendMsg cs ircCapEnd+ return $! recordError now cs "panic: ecdsatool malformed output" st++ Just path ->+ do res <- Ecdsa.computeResponse path challenge+ case res of+ Left e ->+ do sendMsg cs ircCapEnd+ return $! recordError now cs (Text.pack e) st+ Right resp ->+ do sendMsg cs (ircAuthenticate resp)+ return $! set asLens AS_None st+ where+ ss = view csSettings cs+ asLens = clientConnections . ix (view csNetworkId cs) . csAuthenticationState++ processConnectCmd :: ZonedTime {- ^ now -} -> NetworkState {- ^ current network -} ->@@ -341,7 +383,6 @@ EvKey k modifier -> doKey k modifier st EvResize{} -> -- ignore event parameters due to raw TChan use do let vty = view clientVty st- refresh vty (w,h) <- displayBounds (outputIface vty) return $! Just $! set clientWidth w $ set clientHeight h st@@ -363,6 +404,11 @@ changeContent f = changeEditor $ over Edit.content f . set Edit.lastOperation Edit.OtherOperation++ mbChangeEditor f =+ case clientTextBox f st of+ Nothing -> continue $! set clientBell True st+ Just st' -> continue st' in case modifier of [MCtrl] ->@@ -383,12 +429,16 @@ KChar 'v' -> changeEditor (Edit.insert '\^V') KChar 'p' -> continue (retreatFocus st) KChar 'n' -> continue (advanceFocus st)+ KChar 'x' -> continue (advanceNetworkFocus st) KChar 'l' -> do refresh (view clientVty st) continue st _ -> continue st [MMeta] -> case key of+ KChar c | let names = clientWindowNames st+ , Just i <- elemIndex c names ->+ continue (jumpFocus i st) KEnter -> changeEditor (Edit.insert '\^J') KBS -> changeEditor (Edit.killWordBackward True) KChar 'd' -> changeEditor (Edit.killWordForward True)@@ -398,9 +448,7 @@ KRight -> changeContent Edit.rightWord KChar 'a' -> continue (jumpToActivity st) KChar 's' -> continue (returnFocus st)- KChar c | let names = clientWindowNames st- , Just i <- elemIndex c names ->- continue (jumpFocus i st)+ KChar 'k' -> mbChangeEditor Edit.insertDigraph _ -> continue st [] -> -- no modifier
src/Client/Image/Message.hs view
@@ -21,6 +21,7 @@ , coloredUserInfo , coloredIdentifier , cleanText+ , cleanChar ) where import Client.Image.MircFormatting@@ -72,7 +73,6 @@ MessageRendererParams -> MessageBody -> Image msgImage rm when params body = horizCat [ renderTime rm (rendPalette params) when- , char defAttr ' ' , statusMsgImage (rendStatusMsg params) , bodyImage rm params body ]@@ -140,7 +140,7 @@ timeImage :: Palette -> ZonedTime -> Image timeImage palette = string (view palTime palette)- . formatTime defaultTimeLocale "%R"+ . formatTime defaultTimeLocale "%R " -- | Render a 'ZonedTime' as full date and time user quiet attributes --@@ -150,7 +150,7 @@ datetimeImage :: Palette -> ZonedTime -> Image datetimeImage palette = string (view palTime palette)- . formatTime defaultTimeLocale "%F %T"+ . formatTime defaultTimeLocale "%F %T " -- | Level of detail to use when rendering data RenderMode@@ -161,9 +161,8 @@ -- the minimum width. rightPad :: RenderMode -> Maybe Integer -> Image -> Image rightPad NormalRender (Just minWidth) i =- let h = 1- w = max 0 (fromIntegral minWidth - imageWidth i)- in i <|> backgroundFill w h+ let w = max 0 (fromIntegral minWidth - imageWidth i)+ in i <|> string defAttr (replicate w ' ') rightPad _ _ i = i -- | Render a chat message given a rendering mode, the sigils of the user@@ -407,7 +406,7 @@ quietAttr = view palMeta palette aux x xs | Text.null xs = emptyImage- | otherwise = char defAttr x <|> text' quietAttr xs+ | otherwise = char quietAttr x <|> text' quietAttr xs -- | Render an identifier without using colors. This is useful for metadata. quietIdentifier :: Palette -> Identifier -> Image@@ -446,16 +445,15 @@ -- | Returns image and identifier to be used when collapsing metadata -- messages.-metadataImg :: IrcMsg -> Maybe (Image, Identifier, Maybe Identifier)+metadataImg :: IrcSummary -> Maybe (Image, Identifier, Maybe Identifier) metadataImg msg = case msg of- Quit who _ -> Just (char (withForeColor defAttr red ) 'x', userNick who, Nothing)- Part who _ _ -> Just (char (withForeColor defAttr red ) '-', userNick who, Nothing)- Join who _ -> Just (char (withForeColor defAttr green) '+', userNick who, Nothing)- Ctcp who _ cmd _ | cmd /= "ACTION" ->- Just (char (withForeColor defAttr white) 'C', userNick who, Nothing)- Nick old new -> Just (char (withForeColor defAttr yellow) '>', userNick old, Just new)- _ -> Nothing+ QuitSummary who -> Just (char (withForeColor defAttr red ) 'x', who, Nothing)+ PartSummary who -> Just (char (withForeColor defAttr red ) '-', who, Nothing)+ JoinSummary who -> Just (char (withForeColor defAttr green ) '+', who, Nothing)+ CtcpSummary who -> Just (char (withForeColor defAttr white ) 'C', who, Nothing)+ NickSummary old new -> Just (char (withForeColor defAttr yellow) '>', old, Just new)+ _ -> Nothing -- | Image used when treating ignored chat messages as metadata ignoreImage :: Image
+ src/Client/Image/PackedImage.hs view
@@ -0,0 +1,121 @@+{-# Language TypeOperators, MultiParamTypeClasses, StandaloneDeriving, DeriveGeneric #-}+{-# OPTIONS_GHC -Wno-orphans -funfolding-creation-threshold=1500 -funfolding-use-threshold=5000 #-}+{-|+Module : Client.Image.PackedImage+Description : Packed vty Image type+Copyright : (c) Eric Mertens, 2016+License : ISC+Maintainer : emertens@gmail.com++This module provides a more memory efficient way to store images.++-}+module Client.Image.PackedImage+ ( Image'+ , _Image'+ ) where++import Control.Lens (Iso', iso)+import qualified Data.Text as S+import qualified Data.Text.Lazy as L+import Data.List+import GHC.Generics+import Graphics.Vty.Image+import Graphics.Vty.Image.Internal++deriving instance Generic Image+++-- | Isomorphism between packed images and normal images.+_Image' :: Iso' Image' Image+_Image' = iso mirror (mirror . compress)+{-# INLINE _Image' #-}+++-- | Attempts to locate adjacent text sections with equal attributes+-- so that they can be merged.+compress :: Image -> Image+compress = horizCat . map horizCat . groupBy textsWithEqAttr . flip horizList []+++textsWithEqAttr :: Image -> Image -> Bool+textsWithEqAttr (HorizText a _ _ _) (HorizText b _ _ _) = a == b+textsWithEqAttr _ _ = False+++horizList :: Image -> [Image] -> [Image]+horizList (HorizJoin x y _ _) = horizList x . horizList y+horizList x = (x:)+++-- | Packed, strict version of 'Image' used for long-term storage of images.+data Image'+ = HorizText'+ Attr -- don't unpack, these get reused from the palette+ {-# UNPACK #-} !S.Text+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | HorizJoin'+ !Image'+ !Image'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | VertJoin'+ !Image'+ !Image'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | BGFill'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | CropRight'+ !Image'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | CropLeft'+ !Image'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | CropBottom'+ !Image'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | CropTop'+ !Image'+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ | EmptyImage'+ deriving (Show, Generic)++------------------------------------------------------------------------++class Mirror a b where mirror :: a -> b+instance Mirror Attr Attr where mirror = id+instance Mirror Int Int where mirror = id+instance Mirror L.Text S.Text where mirror = L.toStrict+instance Mirror S.Text L.Text where mirror = L.fromStrict+instance Mirror Image Image' where mirror = to . gmirror . from+instance Mirror Image' Image where mirror = to . gmirror . from++------------------------------------------------------------------------++class GMirror f g where+ gmirror :: f p -> g q++instance GMirror f g => GMirror (M1 i c f) (M1 j d g) where+ gmirror (M1 x) = M1 (gmirror x)++instance (GMirror f1 g1, GMirror f2 g2) => GMirror (f1 :*: f2) (g1 :*: g2) where+ gmirror (x :*: y) = gmirror x :*: gmirror y++instance (GMirror f1 g1, GMirror f2 g2) => GMirror (f1 :+: f2) (g1 :+: g2) where+ gmirror (L1 x) = L1 (gmirror x)+ gmirror (R1 x) = R1 (gmirror x)++instance GMirror U1 U1 where+ gmirror _ = U1++instance Mirror a b => GMirror (K1 i a) (K1 j b) where+ gmirror (K1 x) = K1 (mirror x)
src/Client/Image/StatusLine.hs view
@@ -275,6 +275,7 @@ FocusUsers -> Just $ string (view palLabel pal) "users" FocusMentions -> Just $ string (view palLabel pal) "mentions" FocusPalette -> Just $ string (view palLabel pal) "palette"+ FocusDigraphs -> Just $ string (view palLabel pal) "digraphs" FocusHelp mb -> Just $ string (view palLabel pal) "help" <|> opt mb FocusMasks m -> Just $ horizCat
+ src/Client/Log.hs view
@@ -0,0 +1,90 @@+{-# Language OverloadedStrings, BangPatterns #-}+{-|+Module : Client.Log+Description : Support for logging IRC traffic+Copyright : (c) Eric Mertens, 2016+License : ISC+Maintainer : emertens@gmail.com++This module provides provides logging functionality for IRC traffic.++-}+module Client.Log where++import Client.Configuration+import Client.Image.Message (cleanText)+import Client.Message+import Control.Exception+import Control.Lens hiding ((<.>))+import qualified Data.ByteString as B+import Data.Monoid+import Data.Time+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.IO as L+import qualified Data.Vector.Primitive as PV+import Irc.Identifier+import Irc.Message+import Irc.UserInfo+import System.Directory+import System.FilePath++data LogLine = LogLine+ { logBaseDir :: FilePath+ , logDay :: Day+ , logTarget :: Text+ , logLine :: L.Text+ }++writeLogLine ::+ LogLine {- ^ log line -} ->+ IO ()+writeLogLine ll = ignoreProblems $+ do dir' <- resolveConfigurationPath+ (logBaseDir ll </> Text.unpack (logTarget ll))+ let file = dir' </> formatTime defaultTimeLocale "%F" (logDay ll) <.> "log"++ let recursiveFlag = True+ createDirectoryIfMissing recursiveFlag dir'+ L.appendFile file (logLine ll)++ignoreProblems :: IO () -> IO ()+ignoreProblems m = () <$ (try m :: IO (Either IOError ()))++renderLogLine ::+ ClientMessage {- ^ message -} ->+ FilePath {- ^ log directory -} ->+ Identifier {- ^ target -} ->+ Maybe LogLine+renderLogLine !msg dir target =+ case view msgBody msg of+ NormalBody{} -> Nothing+ ErrorBody {} -> Nothing+ IrcBody irc ->+ case irc of+ Privmsg who _ txt ->+ success (L.fromChunks ["<", idText (userNick who), "> ", cleanText txt])+ Notice who _ txt ->+ success (L.fromChunks ["-", idText (userNick who), "- ", cleanText txt])+ Ctcp who _ "ACTION" txt ->+ success (L.fromChunks ["* ", idText (userNick who), " ", cleanText txt])+ _ -> Nothing++ where+ localtime = zonedTimeToLocalTime (view msgTime msg)+ day = localDay localtime+ tod = localTimeOfDay localtime+ todStr = formatTime defaultTimeLocale "%T" tod++ success txt = Just LogLine+ { logBaseDir = dir+ , logDay = day+ , logTarget = Text.toLower+ $ Text.decodeUtf8+ $ B.pack+ $ PV.toList+ $ idDenote target+ , logLine = L.fromChunks ["[", Text.pack todStr, "] "] <> txt <> "\n"+ }
src/Client/Message.hs view
@@ -1,4 +1,4 @@-{-# Language TemplateHaskell #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Message Description : Messages to be added to buffers@@ -25,6 +25,10 @@ , _NormalBody -- * Client message operations+ , IrcSummary(..)+ , msgSummary++ -- * Client message operations , msgText ) where @@ -32,6 +36,9 @@ import Data.Text (Text) import Data.Time (ZonedTime) import Irc.Message+import Irc.Identifier+import Irc.UserInfo+import Irc.Codes data MessageBody = IrcBody !IrcMsg@@ -48,8 +55,42 @@ makeLenses ''ClientMessage +data IrcSummary+ = JoinSummary {-# UNPACK #-} !Identifier+ | QuitSummary {-# UNPACK #-} !Identifier+ | PartSummary {-# UNPACK #-} !Identifier+ | NickSummary {-# UNPACK #-} !Identifier {-# UNPACK #-} !Identifier+ | ReplySummary {-# UNPACK #-} !ReplyCode+ | ChatSummary {-# UNPACK #-} !Identifier+ | CtcpSummary {-# UNPACK #-} !Identifier+ | NoSummary+ deriving (Eq, Show)++ -- | Compute a searchable text representation of the message msgText :: MessageBody -> Text msgText (IrcBody irc) = ircMsgText irc msgText (ErrorBody txt) = txt msgText (NormalBody txt) = txt+++msgSummary :: MessageBody -> IrcSummary+msgSummary (IrcBody irc) = ircSummary irc+msgSummary (ErrorBody _ ) = NoSummary+msgSummary (NormalBody _ ) = NoSummary+++ircSummary :: IrcMsg -> IrcSummary+ircSummary msg =+ case msg of+ Join who _ -> JoinSummary (userNick who)+ Part who _ _ -> PartSummary (userNick who)+ Quit who _ -> QuitSummary (userNick who)+ Nick who who' -> NickSummary (userNick who) who'+ Privmsg who _ _ -> ChatSummary (userNick who)+ Notice who _ _ -> ChatSummary (userNick who)+ Ctcp who _ "ACTION" _ -> ChatSummary (userNick who)+ Ctcp who _ _ _ -> CtcpSummary (userNick who)+ CtcpNotice who _ _ _ -> ChatSummary (userNick who)+ Reply code _ -> ReplySummary code+ _ -> NoSummary
src/Client/State.hs view
@@ -39,6 +39,7 @@ , clientBell , clientExtensions , clientRegex+ , clientLogQueue -- * Client operations , withClientState@@ -50,7 +51,7 @@ , consumeInput , currentCompletionList- , ircIgnorable+ , identIgnored , clientFirstLine , clientLine , abortNetwork@@ -77,6 +78,7 @@ , changeSubfocus , returnFocus , advanceFocus+ , advanceNetworkFocus , retreatFocus , jumpToActivity , jumpFocus@@ -90,6 +92,7 @@ -- * URL view , urlPattern+ , urlMatches ) where @@ -98,7 +101,9 @@ import Client.Configuration import Client.Configuration.ServerSettings import Client.Image.Message+import Client.Image.PackedImage import Client.Image.Palette+import Client.Log import Client.Message import Client.Network.Async import Client.State.Channel@@ -106,6 +111,7 @@ import Client.State.Focus import Client.State.Network import Client.State.Window+import Control.Applicative import Control.Concurrent.MVar import Control.Concurrent.STM import Control.DeepSeq@@ -129,7 +135,7 @@ import Data.Time import Foreign.Ptr import Foreign.StablePtr-import Graphics.Vty+import Graphics.Vty hiding ((<|>)) import Irc.Codes import Irc.Identifier import Irc.Message@@ -172,7 +178,8 @@ , _clientIgnores :: !(HashSet Identifier) -- ^ ignored nicknames - , _clientExtensions :: !ExtensionState+ , _clientExtensions :: !ExtensionState -- ^ state of loaded extensions+ , _clientLogQueue :: ![LogLine] -- ^ log lines ready to write } data ExtensionState = ExtensionState@@ -246,6 +253,7 @@ , _clientNextConnectionId = 0 , _clientBell = False , _clientExtensions = exts+ , _clientLogQueue = [] } -- | Initialize a 'Vty' value and run a continuation. Shutdown the 'Vty'@@ -272,16 +280,18 @@ case preview (clientConnection network) st of Nothing -> return st Just cs -> do abortConnection ForcedDisconnect (view csSocket cs)- return $ set (clientNetworkMap . at network) Nothing st+ return $! over clientNetworkMap (sans network) st -- | Add a message to the window associated with a given channel recordChannelMessage :: Text {- ^ network -} -> Identifier {- ^ channel -} -> ClientMessage ->- ClientState -> ClientState-recordChannelMessage network channel msg st =- recordWindowLine focus wl st+ ClientState ->+ ClientState+recordChannelMessage network channel msg st+ = recordLogLine msg channel+ $ recordWindowLine focus wl st where focus = ChannelFocus network channel' wl = toWindowLine rendParams importance msg@@ -302,6 +312,20 @@ highlights = clientHighlightsNetwork network st +recordLogLine ::+ ClientMessage {- ^ message -} ->+ Identifier {- ^ target -} ->+ ClientState {- ^ client state -} ->+ ClientState+recordLogLine msg target st =+ case view (clientConnection (view msgNetwork msg) . csSettings . ssLogDir) st of+ Nothing -> st+ Just dir ->+ case renderLogLine msg dir target of+ Nothing -> st+ Just ll -> over clientLogQueue (cons ll) st++ -- | Extract the status mode sigils from a message target. splitStatusMsgModes :: [Char] {- ^ possible modes -} ->@@ -358,7 +382,7 @@ -- | Predicate for messages that should be ignored based on the -- configurable ignore list ircIgnorable :: IrcMsg -> ClientState -> Maybe Identifier-ircIgnorable msg st =+ircIgnorable msg !st = case msg of Privmsg who _ _ -> checkUser who Notice who _ _ -> checkUser who@@ -366,14 +390,16 @@ Ctcp who _ "ACTION" _ -> checkUser who -- notice ctcp responses are not already metadata CtcpNotice who _ _ _ -> checkUser who- _ -> Nothing+ _ -> Nothing where- ignores = view clientIgnores st checkUser !who- | HashSet.member (userNick who) ignores = Just (userNick who)- | otherwise = Nothing+ | identIgnored (userNick who) st = Just (userNick who)+ | otherwise = Nothing +identIgnored :: Identifier -> ClientState -> Bool+identIgnored who st = HashSet.member who (view clientIgnores st) + -- | Record a message in the windows corresponding to the given target recordIrcMessage :: Text {- ^ network -} ->@@ -439,17 +465,18 @@ recordWindowLine :: Focus -> WindowLine ->- ClientState -> ClientState+ ClientState ->+ ClientState recordWindowLine focus wl = over (clientWindows . at focus) (\w -> Just $! addToWindow wl (fromMaybe emptyWindow w)) toWindowLine :: MessageRendererParams -> WindowLineImportance -> ClientMessage -> WindowLine toWindowLine params importance msg = WindowLine- { _wlBody = view msgBody msg+ { _wlSummary = msgSummary (view msgBody msg) , _wlText = msgText (view msgBody msg)- , _wlImage = mkImage NormalRender- , _wlFullImage = mkImage DetailedRender+ , _wlImage' = _Image' # mkImage NormalRender+ , _wlFullImage' = _Image' # mkImage DetailedRender , _wlImportance = importance , _wlTimestamp = zonedTimeToUTC (view msgTime msg) }@@ -469,7 +496,9 @@ -- | Function applied to the client state every redraw. clientTick :: ClientState -> ClientState-clientTick = set clientBell False . markSeen+clientTick = set clientBell False+ . markSeen+ . set clientLogQueue [] -- | Mark the messages on the current window (and any splits) as seen.@@ -560,8 +589,17 @@ compile defaultCompOpt defaultExecOpt{captureGroups=False}- "https?://([[:alnum:]-]+\\.)*([[:alnum:]-]+)(:[[:digit:]]+)?(/[^[:cntrl:][:space:]]*)"+ "https?://([[:alnum:]-]+\\.)*([[:alnum:]-]+)(:[[:digit:]]+)?(/[^[:cntrl:][:space:]]*)|\+ \<https?://[^>]*>" +urlMatches :: Text -> [Text]+urlMatches = map removeBrackets . getAllTextMatches . match urlPattern+ where+ removeBrackets t =+ case Text.uncons t of+ Just ('<',t') | not (Text.null t') -> Text.init t'+ _ -> t+ -- | Remove a network connection and unlink it from the network map. -- This operation assumes that the networkconnection exists and should -- only be applied once per connection.@@ -771,29 +809,44 @@ -- | Step focus to the next window when on message view. Otherwise -- switch to message view. advanceFocus :: ClientState -> ClientState-advanceFocus = stepFocus False+advanceFocus = stepFocus $ \l r ->+ fst . fst <$> Map.minViewWithKey r <|>+ fst . fst <$> Map.minViewWithKey l -- | Step focus to the previous window when on message view. Otherwise -- switch to message view. retreatFocus :: ClientState -> ClientState-retreatFocus = stepFocus True+retreatFocus = stepFocus $ \l r ->+ fst . fst <$> Map.maxViewWithKey l <|>+ fst . fst <$> Map.maxViewWithKey r -- | Step focus to the next window when on message view. Otherwise--- switch to message view. Reverse the step order when argument is 'True'.-stepFocus :: Bool {- ^ reversed -} -> ClientState -> ClientState-stepFocus isReversed st- | view clientSubfocus st /= FocusMessages = changeSubfocus FocusMessages st-- | isReversed, Just ((k,_),_) <- Map.maxViewWithKey l = changeFocus k st- | isReversed, Just ((k,_),_) <- Map.maxViewWithKey r = changeFocus k st+-- switch to message view.+advanceNetworkFocus :: ClientState -> ClientState+advanceNetworkFocus = stepFocus $ \l r ->+ fst . fst <$> Map.minViewWithKey (Map.filterWithKey isNetwork r) <|>+ fst . fst <$> Map.minViewWithKey (Map.filterWithKey isNetwork l)+ where+ isNetwork k _ = has _NetworkFocus k - | isForward , Just ((k,_),_) <- Map.minViewWithKey r = changeFocus k st- | isForward , Just ((k,_),_) <- Map.minViewWithKey l = changeFocus k st+-- | Selection function used in 'stepFocus'+type FocusSelector =+ Map Focus Window {- ^ windows before current window -} ->+ Map Focus Window {- ^ windows after current window -} ->+ Maybe Focus {- ^ window to focus -} - | otherwise = st+-- | Step focus to the next window when on message view. Otherwise+-- switch to message view. Reverse the step order when argument is 'True'.+stepFocus ::+ FocusSelector {- ^ selection function -} ->+ ClientState {- ^ client state -} ->+ ClientState+stepFocus selector st =+ case selector l r of+ Just k -> changeFocus k st+ Nothing -> st where- isForward = not isReversed- (l,r) = Map.split (view clientFocus st) (view clientWindows st)+ (l,r) = Map.split (view clientFocus st) (view clientWindows st) -- | Compute the set of extra identifiers that should be highlighted given -- a particular network state.
src/Client/State/EditBox.hs view
@@ -54,6 +54,7 @@ , earlier , later , success+ , insertDigraph -- * Last operation , LastOperation(..)@@ -241,7 +242,14 @@ = set lastOperation OtherOperation . over content (insertChar c) + insertPaste :: String -> EditBox -> EditBox insertPaste paste = over content (insertPastedString paste)+ . set lastOperation OtherOperation+++insertDigraph :: EditBox -> Maybe EditBox+insertDigraph+ = content digraph . set lastOperation OtherOperation
src/Client/State/EditBox/Content.hs view
@@ -43,11 +43,14 @@ , insertString , insertChar , toggle+ , digraph ) where import Control.Lens hiding (below)+import Control.Monad (guard) import Data.Char (isAlphaNum) import Data.List (find)+import Digraphs (lookupDigraph) data Line = Line { _pos :: !Int@@ -269,3 +272,15 @@ swapAt 0 (x:y:z) = y:x:z swapAt i (x:xs) = x:swapAt (i-1) xs swapAt _ _ = error "toggle: PANIC! Invalid argument"+++-- | Use the two characters preceeding the cursor as a digraph and replace+-- them with the corresponding character.+digraph :: Content -> Maybe Content+digraph !c =+ do let Line n txt = view line c+ guard (2 <= n)+ let (pfx,x:y:sfx) = splitAt (n - 2) txt+ d <- lookupDigraph x y+ let line' = Line (n-1) (pfx++d:sfx)+ Just $! set line line' c
src/Client/State/Focus.hs view
@@ -50,6 +50,7 @@ | FocusWindows WindowsFilter -- ^ Show client windows | FocusPalette -- ^ Show current palette | FocusMentions -- ^ Show all mentions+ | FocusDigraphs -- ^ Show all digraphs | FocusHelp (Maybe Text) -- ^ Show help window with optional command deriving (Eq,Show)
src/Client/State/Network.hs view
@@ -20,6 +20,7 @@ ( -- * Connection state NetworkState(..)+ , AuthenticateState(..) , newNetworkState -- * Lenses@@ -42,6 +43,7 @@ , csPingStatus , csLastReceived , csMessageHooks+ , csAuthenticationState -- * User information , UserAndHost(..)@@ -61,6 +63,7 @@ -- * Timer information , PingStatus(..)+ , _PingConnecting , TimedAction(..) , nextTimedAction , applyTimedAction@@ -71,6 +74,7 @@ , sendTopic ) where +import qualified Client.Authentication.Ecdsa as Ecdsa import Client.Configuration.ServerSettings import Client.Network.Async import Client.State.Channel@@ -119,9 +123,18 @@ , _csPingStatus :: !PingStatus -- ^ state of ping timer , _csLastReceived :: !(Maybe UTCTime) -- ^ time of last message received , _csMessageHooks :: ![Text] -- ^ names of message hooks to apply to this connection+ , _csAuthenticationState :: !AuthenticateState } deriving Show +-- | State of the authentication transaction+data AuthenticateState+ = AS_None -- ^ no active transaction+ | AS_PlainStarted -- ^ PLAIN mode initiated+ | AS_EcdsaStarted -- ^ ECDSA-NIST mode initiated+ | AS_EcdsaWaitChallenge -- ^ ECDSA_NIST user sent waiting for challenge+ deriving Show+ -- | Pair of username and hostname. Empty strings represent missing information. data UserAndHost = UserAndHost {-# UNPACK #-} !Text {-# UNPACK #-} !Text@@ -136,6 +149,7 @@ | PingConnecting !Int !(Maybe UTCTime) -- ^ number of attempts, last known connection time deriving Show + data Transaction = NoTransaction | NamesTransaction [Text]@@ -145,6 +159,7 @@ makeLenses ''NetworkState makePrisms ''Transaction+makePrisms ''PingStatus csNick :: Lens' NetworkState Identifier csNick = csUserInfo . uiNick@@ -221,6 +236,7 @@ , _csNextPingTime = Nothing , _csLastReceived = Nothing , _csMessageHooks = view ssMessageHooks settings+ , _csAuthenticationState = AS_None } @@ -261,7 +277,7 @@ noReply $ forgetUser' (userNick user) -- possibly forget $ if userNick user == view csNick cs- then set (csChannels . at chan) Nothing cs+ then over csChannels (sans chan) cs else overChannel chan (partChannel (userNick user)) cs Nick oldNick newNick ->@@ -307,7 +323,7 @@ ([RawIrcMsg], NetworkState) {- ^ replies, updated state -} doBadNick badNick cs = case NonEmpty.dropWhile (badNick/=) (view (csSettings . ssNicks) cs) of- _:next:_ -> ([ircNick (mkId next)], cs)+ _:next:_ -> ([ircNick next], cs) _ -> ([], cs) doTopic :: ZonedTime -> UserInfo -> Identifier -> Text -> NetworkState -> NetworkState@@ -577,7 +593,7 @@ in setStrict (chanLists . ix mode . at arg) entry c | polarity = set (chanModes . at mode) (Just arg) c- | otherwise = set (chanModes . at mode) Nothing c+ | otherwise = over chanModes (sans mode) c setPrefixMode polarity sigil sigils | not polarity = delete sigil sigils@@ -602,17 +618,32 @@ where ss = view csSettings cs sasl = ["sasl" | isJust (view ssSaslUsername ss)- , isJust (view ssSaslPassword ss) ]+ , isJust (view ssSaslPassword ss) ||+ isJust (view ssSaslEcdsaFile ss) ] doAuthenticate :: Text -> NetworkState -> ([RawIrcMsg], NetworkState)-doAuthenticate "+" cs- | Just user <- view ssSaslUsername ss- , Just pass <- view ssSaslPassword ss- = ([ircAuthenticate (encodePlainAuthentication user pass)], cs)+doAuthenticate param cs =+ case view csAuthenticationState cs of+ AS_PlainStarted+ | "+" <- param+ , Just user <- view ssSaslUsername ss+ , Just pass <- view ssSaslPassword ss+ -> ([ircAuthenticate (encodePlainAuthentication user pass)],+ set csAuthenticationState AS_None cs)++ AS_EcdsaStarted+ | "+" <- param+ , Just user <- view ssSaslUsername ss+ -> ([ircAuthenticate (Ecdsa.encodeUsername user)],+ set csAuthenticationState AS_EcdsaWaitChallenge cs)++ AS_EcdsaWaitChallenge -> ([], cs) -- handled in Client.EventLoop!++ _ -> ([ircCapEnd], cs) -- really shouldn't happen+ where ss = view csSettings cs -doAuthenticate _ cs = ([ircCapEnd], cs) doCap :: CapCmd -> [Text] -> NetworkState -> ([RawIrcMsg], NetworkState) doCap cmd args cs =@@ -626,8 +657,13 @@ (CapAck,[capsTxt]) | "sasl" `elem` caps ->- ([ircAuthenticate plainAuthenticationMode], cs)+ if isJust (view ssSaslEcdsaFile ss)+ then ([ircAuthenticate Ecdsa.authenticationMode], set csAuthenticationState AS_EcdsaStarted cs)+ else if isJust (view ssSaslUsername ss)+ then ([ircAuthenticate plainAuthenticationMode], set csAuthenticationState AS_PlainStarted cs)+ else ([ircCapEnd], cs) where+ ss = view csSettings cs caps = Text.words capsTxt _ -> ([ircCapEnd], cs)@@ -637,7 +673,7 @@ initialMessages cs = [ ircCapLs ] ++ [ ircPass pass | Just pass <- [view ssPassword ss]]- ++ [ ircNick (mkId (views ssNicks NonEmpty.head ss))+ ++ [ ircNick (views ssNicks NonEmpty.head ss) , ircUser (view ssUser ss) False True (view ssReal ss) ] where@@ -751,7 +787,7 @@ (Just (UserAndHost user host)) forgetUser :: Identifier -> NetworkState -> NetworkState-forgetUser nick = set (csUsers . at nick) Nothing+forgetUser = over csUsers . sans renameUser :: Identifier -> Identifier -> NetworkState -> NetworkState renameUser old new cs = set (csUsers . at new) entry cs'
src/Client/State/Window.hs view
@@ -1,4 +1,4 @@-{-# Language BangPatterns, TemplateHaskell #-}+{-# Language MultiParamTypeClasses, BangPatterns, TemplateHaskell #-} {-| Module : Client.State.Window@@ -22,7 +22,7 @@ -- * Window lines , WindowLine(..)- , wlBody+ , wlSummary , wlText , wlImage , wlFullImage@@ -38,6 +38,7 @@ , windowSeen ) where +import Client.Image.PackedImage import Client.Message import Control.Lens import Data.Text (Text)@@ -46,18 +47,22 @@ -- | A single message to be displayed in a window data WindowLine = WindowLine- { _wlBody :: !MessageBody -- ^ Original Haskell value+ { _wlSummary :: !IrcSummary -- ^ Summary value , _wlText :: {-# UNPACK #-} !Text -- ^ Searchable text form- , _wlImage :: !Image -- ^ Normal rendered image- , _wlFullImage :: !Image -- ^ Detailed rendered image+ , _wlImage' :: !Image' -- ^ Normal rendered image+ , _wlFullImage' :: !Image' -- ^ Detailed rendered image , _wlImportance :: !WindowLineImportance -- ^ Importance of message , _wlTimestamp :: {-# UNPACK #-} !UTCTime } +data WindowLines+ = {-# UNPACK #-} !WindowLine :- WindowLines+ | Nil+ -- | A 'Window' tracks all of the messages and metadata for a particular -- message buffer. data Window = Window- { _winMessages :: ![WindowLine] -- ^ Messages to display, newest first+ { _winMessages :: !WindowLines -- ^ Messages to display, newest first , _winUnread :: !Int -- ^ Messages added since buffer was visible , _winTotal :: !Int -- ^ Messages in buffer , _winMention :: !Bool -- ^ Indicates an important event is unread@@ -73,10 +78,22 @@ makeLenses ''Window makeLenses ''WindowLine ++-- | Lens for the '_wlImage' field viewed in unpacked form.+wlImage :: Lens' WindowLine Image+wlImage = wlImage' . _Image'+{-# INLINE wlImage #-}++-- | Lens for the '_wlFullImage' field viewed in unpacked form.+wlFullImage :: Lens' WindowLine Image+wlFullImage = wlFullImage' . _Image'+{-# INLINE wlFullImage #-}++ -- | A window with no messages emptyWindow :: Window emptyWindow = Window- { _winMessages = []+ { _winMessages = Nil , _winUnread = 0 , _winTotal = 0 , _winMention = False@@ -86,7 +103,7 @@ -- unread count will be updated according to the given importance. addToWindow :: WindowLine -> Window -> Window addToWindow !msg !win = Window- { _winMessages = msg : view winMessages win+ { _winMessages = msg :- view winMessages win , _winTotal = view winTotal win + 1 , _winUnread = view winUnread win + (if view wlImportance msg == WLBoring then 0 else 1)@@ -98,3 +115,8 @@ windowSeen :: Window -> Window windowSeen = set winUnread 0 . set winMention False+++instance Each WindowLines WindowLines WindowLine WindowLine where+ each _ Nil = pure Nil+ each f (x :- xs) = (:-) <$> f x <*> each f xs
src/Client/View.hs view
@@ -16,6 +16,7 @@ import Client.State import Client.State.Focus import Client.View.ChannelInfo+import Client.View.Digraphs import Client.View.Help import Client.View.MaskList import Client.View.Mentions@@ -30,7 +31,8 @@ viewLines :: Focus -> Subfocus -> ClientState -> [Image] viewLines focus subfocus !st = case (focus, subfocus) of- _ | Just ("url",_) <- clientActiveCommand st -> urlSelectionView focus st+ _ | Just ("url",arg) <- clientActiveCommand st ->+ urlSelectionView focus arg st (ChannelFocus network channel, FocusInfo) -> channelInfoImages network channel st (ChannelFocus network channel, FocusUsers)@@ -41,6 +43,7 @@ (_, FocusWindows filt) -> windowsImages filt st (_, FocusMentions) -> mentionsViewLines st (_, FocusPalette) -> paletteViewLines pal+ (_, FocusDigraphs) -> digraphLines st (_, FocusHelp mb) -> helpImageLines mb pal _ -> chatMessageImages focus st where
+ src/Client/View/Digraphs.hs view
@@ -0,0 +1,56 @@+{-# Language OverloadedStrings #-}+{-|+Module : Client.View.Digraphs+Description : Character mnemonics+Copyright : (c) Eric Mertens, 2016+License : ISC+Maintainer : emertens@gmail.com++This module provides an view of the built-in digraph list.++-}+module Client.View.Digraphs (digraphLines) where++import Client.Image.Message (cleanChar)+import Client.State+import Control.Lens+import Data.List+import Data.List.Split+import qualified Data.Text as Text+import Digraphs+import Graphics.Vty.Image++-- | Render the lines of a table showing all of the available digraph entries+digraphLines ::+ ClientState {- ^ client state -} ->+ [Image] {- ^ output lines -}+digraphLines st+ = map (horizCat . intersperse sep)+ $ chunksOf entriesPerLine+ $ map (text' defAttr)+ $ filter matcher+ $ map (Text.pack . drawEntry)+ $ digraphListToList digraphs+ where+ w = view clientWidth st+ matcher = clientMatcher st+ entriesPerLine = max 1 -- just in case?+ $ (w + sepWidth) `quot` (entryWidth + sepWidth)++entryWidth :: Int+entryWidth = 5 -- "Ka カ"++sepWidth :: Int+sepWidth = imageWidth sep++sep :: Image+sep = text' defAttr " "++drawEntry :: (Char,Char,Char) -> String+drawEntry (x,y,z) = output ++ replicate (entryWidth - wcswidth output) ' '+ where+ output = x:y:z2+ dottedCircle = '\x25cc'+ z1 = cleanChar z+ z2 | wcwidth z1 == 0 = [' ', dottedCircle, z1]+ | otherwise = [' ', z1]
src/Client/View/Mentions.hs view
@@ -73,8 +73,8 @@ , mlFocus = focus , mlImage = if detailed then view wlFullImage l else view wlImage l }- | l <- view winMessages w- , WLImportant == view wlImportance l+ | let p x = WLImportant == view wlImportance x+ , l <- toListOf (winMessages . each . filtered p) w ] -- | Merge a list of sorted lists of mention lines into a single sorted list
src/Client/View/Messages.hs view
@@ -22,7 +22,9 @@ import Client.State.Window import Client.Message import Control.Lens+import Control.Monad import Irc.Identifier+import Irc.Message import Graphics.Vty.Image chatMessageImages :: Focus -> ClientState -> [Image]@@ -31,8 +33,9 @@ matcher = clientMatcher st focusedMessages- = filter (views wlText matcher)- $ view (clientWindows . ix focus . winMessages) st+ = toListOf ( clientWindows . ix focus+ . winMessages . each+ . filtered (views wlText matcher)) st windowLineProcessor @@ -44,9 +47,9 @@ | otherwise = windowLinesToImages st . filter (not . isNoisy) isNoisy msg =- case view wlBody msg of- IrcBody irc -> squelchIrcMsg irc- _ -> False+ case view wlSummary msg of+ ReplySummary code -> squelchIrcMsg (Reply code [])+ _ -> False detailedImagesWithoutMetadata :: ClientState -> [WindowLine] -> [Image] detailedImagesWithoutMetadata st wwls =@@ -127,9 +130,6 @@ Maybe (Image, Identifier, Maybe Identifier) {- ^ Image, incoming identifier, outgoing identifier if changed -} metadataWindowLine st wl =- case view wlBody wl of- IrcBody irc- | Just who <- ircIgnorable irc st -> Just (ignoreImage, who, Nothing)- | otherwise -> metadataImg irc- _ -> Nothing-+ case view wlSummary wl of+ ChatSummary who -> (ignoreImage, who, Nothing) <$ guard (identIgnored who st)+ summary -> metadataImg summary
src/Client/View/UrlSelection.hs view
@@ -1,4 +1,3 @@-{-# Language BangPatterns #-} {-| Module : Client.View.UrlSelection Description : URL selection module@@ -16,22 +15,41 @@ import Client.State import Client.State.Window-import Client.State.Focus import Client.Image.Message+import Client.State.Focus import Control.Lens-import Graphics.Vty.Image-import Text.Regex.TDFA import Data.Text (Text)+import Graphics.Vty.Image+import Text.Read (readMaybe) -urlSelectionView :: Focus -> ClientState -> [Image]-urlSelectionView focus st =- zipWith draw [1..]- $ toListOf (clientWindows . ix focus . winMessages . folded . wlText . folding textUrls) st+-- | Generate the lines used for the view when typing @/url@+urlSelectionView ::+ Focus {- ^ window to search -} ->+ String {- ^ argument to command -} ->+ ClientState {- ^ client state -} ->+ [Image] {- ^ image lines -}+urlSelectionView focus arg st =+ zipWith (draw selected) [1..] (toListOf urled st)+ where+ urled = clientWindows . ix focus+ . winMessages . each+ . wlText . folding urlMatches -textUrls :: Text -> [Text]-textUrls = getAllTextMatches . match urlPattern+ selected+ | all (==' ') arg = 1+ | Just i <- readMaybe arg = i+ | otherwise = 0 -- won't match -draw :: Int -> Text -> Image-draw i url = string defAttr (shows i ". ")- <|> text' defAttr (cleanText url)++-- | Render one line of the url list+draw ::+ Int {- ^ selected index -} ->+ Int {- ^ url index -} ->+ Text {- ^ url text -} ->+ Image {- ^ rendered line -}+draw selected i url = string attr (shows i ". ") <|>+ text' attr (cleanText url)+ where+ attr | selected == i = withStyle defAttr reverseVideo+ | otherwise = defAttr
+ src/Digraphs.hs view
@@ -0,0 +1,1359 @@+{-# Language OverloadedStrings #-}+{-|+Module : Digraphs+Description : Character mnemonics+Copyright : (c) Eric Mertens, 2016+License : ISC+Maintainer : emertens@gmail.com++This module provides an implementation of /digraphs/ as implemented+in Vim and as specified in RFC 1345 (2-character only).++<https://tools.ietf.org/html/rfc1345>+<http://vimdoc.sourceforge.net/htmldoc/digraph.html>++-}+module Digraphs+ ( lookupDigraph+ , digraphs+ , digraphListToList+ ) where++import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Data.Char+++-- | Look up 2-character digraph.+lookupDigraph :: Char -> Char -> Maybe Char+lookupDigraph x y+ | isAscii x, isAscii y = digraphToInt x y `IntMap.lookup` digraphMap+ | otherwise = Nothing+++digraphMap :: IntMap Char+digraphMap = IntMap.fromList+ [ (digraphToInt x y, z) | (x,y,z) <- digraphListToList digraphs ]+++-- | Injective mapping from 2-character ASCII digraph to 'Int'+digraphToInt :: Char -> Char -> Int+digraphToInt x y = ord x * 127 + ord y++------------------------------------------------------------------------++digraphListToList :: DigraphList -> [(Char,Char,Char)]+digraphListToList (Entry x y z xs) = (x,y,z) : digraphListToList xs+digraphListToList Nil = []+++-- | Packed list of digraph entries+data DigraphList+ = Entry {-# UNPACK #-}!Char {-# UNPACK #-}!Char {-# UNPACK #-}!Char DigraphList+ | Nil+++digraphs :: DigraphList+digraphs+ = Entry 'N' 'U' '\x00' -- NULL (NUL)+ $ Entry 'S' 'H' '\x01' -- START OF HEADING (SOH)+ $ Entry 'S' 'X' '\x02' -- START OF TEXT (STX)+ $ Entry 'E' 'X' '\x03' -- END OF TEXT (ETX)+ $ Entry 'E' 'T' '\x04' -- END OF TRANSMISSION (EOT)+ $ Entry 'E' 'Q' '\x05' -- ENQUIRY (ENQ)+ $ Entry 'A' 'K' '\x06' -- ACKNOWLEDGE (ACK)+ $ Entry 'B' 'L' '\x07' -- BELL (BEL)+ $ Entry 'B' 'S' '\x08' -- BACKSPACE (BS)+ $ Entry 'H' 'T' '\x09' -- CHARACTER TABULATION (HT)+-- $ Entry 'L' 'F' '\x0a' -- LINE FEED (LF)+ $ Entry 'V' 'T' '\x0b' -- LINE TABULATION (VT)+ $ Entry 'F' 'F' '\x0c' -- FORM FEED (FF)+-- $ Entry 'C' 'R' '\x0d' -- CARRIAGE RETURN (CR)+ $ Entry 'S' 'O' '\x0e' -- SHIFT OUT (SO)+ $ Entry 'S' 'I' '\x0f' -- SHIFT IN (SI)+ $ Entry 'D' 'L' '\x10' -- DATALINK ESCAPE (DLE)+ $ Entry 'D' '1' '\x11' -- DEVICE CONTROL ONE (DC1)+ $ Entry 'D' '2' '\x12' -- DEVICE CONTROL TWO (DC2)+ $ Entry 'D' '3' '\x13' -- DEVICE CONTROL THREE (DC3)+ $ Entry 'D' '4' '\x14' -- DEVICE CONTROL FOUR (DC4)+ $ Entry 'N' 'K' '\x15' -- NEGATIVE ACKNOWLEDGE (NAK)+ $ Entry 'S' 'Y' '\x16' -- SYNCHRONOUS IDLE (SYN)+ $ Entry 'E' 'B' '\x17' -- END OF TRANSMISSION BLOCK (ETB)+ $ Entry 'C' 'N' '\x18' -- CANCEL (CAN)+ $ Entry 'E' 'M' '\x19' -- END OF MEDIUM (EM)+ $ Entry 'S' 'B' '\x1a' -- SUBSTITUTE (SUB)+ $ Entry 'E' 'C' '\x1b' -- ESCAPE (ESC)+ $ Entry 'F' 'S' '\x1c' -- FILE SEPARATOR (IS4)+ $ Entry 'G' 'S' '\x1d' -- GROUP SEPARATOR (IS3)+ $ Entry 'R' 'S' '\x1e' -- RECORD SEPARATOR (IS2)+ $ Entry 'U' 'S' '\x1f' -- UNIT SEPARATOR (IS1)+ $ Entry 'S' 'P' '\x20' -- SPACE+ $ Entry 'N' 'b' '\x23' -- NUMBER SIGN+ $ Entry 'D' 'O' '\x24' -- DOLLAR SIGN+ $ Entry 'A' 't' '\x40' -- COMMERCIAL AT+ $ Entry '<' '(' '\x5b' -- LEFT SQUARE BRACKET+ $ Entry '/' '/' '\x5c' -- REVERSE SOLIDUS+ $ Entry ')' '>' '\x5d' -- RIGHT SQUARE BRACKET+ $ Entry '\'' '>' '\x5e' -- CIRCUMFLEX ACCENT+ $ Entry '\'' '!' '\x60' -- GRAVE ACCENT+ $ Entry '(' '!' '\x7b' -- LEFT CURLY BRACKET+ $ Entry '!' '!' '\x7c' -- VERTICAL LINE+ $ Entry '!' ')' '\x7d' -- RIGHT CURLY BRACKET+ $ Entry '\'' '?' '\x7e' -- TILDE+ $ Entry 'D' 'T' '\x7f' -- DELETE (DEL)+ $ Entry 'P' 'A' '\x80' -- PADDING CHARACTER (PAD)+ $ Entry 'H' 'O' '\x81' -- HIGH OCTET PRESET (HOP)+ $ Entry 'B' 'H' '\x82' -- BREAK PERMITTED HERE (BPH)+ $ Entry 'N' 'H' '\x83' -- NO BREAK HERE (NBH)+ $ Entry 'I' 'N' '\x84' -- INDEX (IND)+ $ Entry 'N' 'L' '\x85' -- NEXT LINE (NEL)+ $ Entry 'S' 'A' '\x86' -- START OF SELECTED AREA (SSA)+ $ Entry 'E' 'S' '\x87' -- END OF SELECTED AREA (ESA)+ $ Entry 'H' 'S' '\x88' -- CHARACTER TABULATION SET (HTS)+ $ Entry 'H' 'J' '\x89' -- CHARACTER TABULATION WITH JUSTIFICATION (HTJ)+ $ Entry 'V' 'S' '\x8a' -- LINE TABULATION SET (VTS)+ $ Entry 'P' 'D' '\x8b' -- PARTIAL LINE FORWARD (PLD)+ $ Entry 'P' 'U' '\x8c' -- PARTIAL LINE BACKWARD (PLU)+ $ Entry 'R' 'I' '\x8d' -- REVERSE LINE FEED (RI)+ $ Entry 'S' '2' '\x8e' -- SINGLE-SHIFT TWO (SS2)+ $ Entry 'S' '3' '\x8f' -- SINGLE-SHIFT THREE (SS3)+ $ Entry 'D' 'C' '\x90' -- DEVICE CONTROL STRING (DCS)+ $ Entry 'P' '1' '\x91' -- PRIVATE USE ONE (PU1)+ $ Entry 'P' '2' '\x92' -- PRIVATE USE TWO (PU2)+ $ Entry 'T' 'S' '\x93' -- SET TRANSMIT STATE (STS)+ $ Entry 'C' 'C' '\x94' -- CANCEL CHARACTER (CCH)+ $ Entry 'M' 'W' '\x95' -- MESSAGE WAITING (MW)+ $ Entry 'S' 'G' '\x96' -- START OF GUARDED AREA (SPA)+ $ Entry 'E' 'G' '\x97' -- END OF GUARDED AREA (EPA)+ $ Entry 'S' 'S' '\x98' -- START OF STRING (SOS)+ $ Entry 'G' 'C' '\x99' -- SINGLE GRAPHIC CHARACTER INTRODUCER (SGCI)+ $ Entry 'S' 'C' '\x9a' -- SINGLE CHARACTER INTRODUCER (SCI)+ $ Entry 'C' 'I' '\x9b' -- CONTROL SEQUENCE INTRODUCER (CSI)+ $ Entry 'S' 'T' '\x9c' -- STRING TERMINATOR (ST)+ $ Entry 'O' 'C' '\x9d' -- OPERATING SYSTEM COMMAND (OSC)+ $ Entry 'P' 'M' '\x9e' -- PRIVACY MESSAGE (PM)+ $ Entry 'A' 'C' '\x9f' -- APPLICATION PROGRAM COMMAND (APC)+ $ Entry 'N' 'S' '\xa0' -- NO-BREAK SPACE+ $ Entry '!' 'I' '\xa1' -- INVERTED EXCLAMATION MARK+ $ Entry 'C' 't' '\xa2' -- CENT SIGN+ $ Entry 'P' 'd' '\xa3' -- POUND SIGN+ $ Entry 'C' 'u' '\xa4' -- CURRENCY SIGN+ $ Entry 'Y' 'e' '\xa5' -- YEN SIGN+ $ Entry 'B' 'B' '\xa6' -- BROKEN BAR+ $ Entry 'S' 'E' '\xa7' -- SECTION SIGN+ $ Entry '\'' ':' '\xa8' -- DIAERESIS+ $ Entry 'C' 'o' '\xa9' -- COPYRIGHT SIGN+ $ Entry '-' 'a' '\xaa' -- FEMININE ORDINAL INDICATOR+ $ Entry '<' '<' '\xab' -- LEFT-POINTING DOUBLE ANGLE QUOTATION MARK+ $ Entry 'N' 'O' '\xac' -- NOT SIGN+ $ Entry '-' '-' '\xad' -- SOFT HYPHEN+ $ Entry 'R' 'g' '\xae' -- REGISTERED SIGN+ $ Entry '\'' 'm' '\xaf' -- MACRON+ $ Entry 'D' 'G' '\xb0' -- DEGREE SIGN+ $ Entry '+' '-' '\xb1' -- PLUS-MINUS SIGN+ $ Entry '2' 'S' '\xb2' -- SUPERSCRIPT TWO+ $ Entry '3' 'S' '\xb3' -- SUPERSCRIPT THREE+ $ Entry '\'' '\'' '\xb4' -- ACUTE ACCENT+ $ Entry 'M' 'y' '\xb5' -- MICRO SIGN+ $ Entry 'P' 'I' '\xb6' -- PILCROW SIGN+ $ Entry '.' 'M' '\xb7' -- MIDDLE DOT+ $ Entry '\'' ',' '\xb8' -- CEDILLA+ $ Entry '1' 'S' '\xb9' -- SUPERSCRIPT ONE+ $ Entry '-' 'o' '\xba' -- MASCULINE ORDINAL INDICATOR+ $ Entry '>' '>' '\xbb' -- RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK+ $ Entry '1' '4' '\xbc' -- VULGAR FRACTION ONE QUARTER+ $ Entry '1' '2' '\xbd' -- VULGAR FRACTION ONE HALF+ $ Entry '3' '4' '\xbe' -- VULGAR FRACTION THREE QUARTERS+ $ Entry '?' 'I' '\xbf' -- INVERTED QUESTION MARK+ $ Entry 'A' '!' '\xc0' -- LATIN CAPITAL LETTER A WITH GRAVE+ $ Entry 'A' '\'' '\xc1' -- LATIN CAPITAL LETTER A WITH ACUTE+ $ Entry 'A' '>' '\xc2' -- LATIN CAPITAL LETTER A WITH CIRCUMFLEX+ $ Entry 'A' '?' '\xc3' -- LATIN CAPITAL LETTER A WITH TILDE+ $ Entry 'A' ':' '\xc4' -- LATIN CAPITAL LETTER A WITH DIAERESIS+ $ Entry 'A' 'A' '\xc5' -- LATIN CAPITAL LETTER A WITH RING ABOVE+ $ Entry 'A' 'E' '\xc6' -- LATIN CAPITAL LETTER AE+ $ Entry 'C' ',' '\xc7' -- LATIN CAPITAL LETTER C WITH CEDILLA+ $ Entry 'E' '!' '\xc8' -- LATIN CAPITAL LETTER E WITH GRAVE+ $ Entry 'E' '\'' '\xc9' -- LATIN CAPITAL LETTER E WITH ACUTE+ $ Entry 'E' '>' '\xca' -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX+ $ Entry 'E' ':' '\xcb' -- LATIN CAPITAL LETTER E WITH DIAERESIS+ $ Entry 'I' '!' '\xcc' -- LATIN CAPITAL LETTER I WITH GRAVE+ $ Entry 'I' '\'' '\xcd' -- LATIN CAPITAL LETTER I WITH ACUTE+ $ Entry 'I' '>' '\xce' -- LATIN CAPITAL LETTER I WITH CIRCUMFLEX+ $ Entry 'I' ':' '\xcf' -- LATIN CAPITAL LETTER I WITH DIAERESIS+ $ Entry 'D' '-' '\xd0' -- LATIN CAPITAL LETTER ETH (Icelandic)+ $ Entry 'N' '?' '\xd1' -- LATIN CAPITAL LETTER N WITH TILDE+ $ Entry 'O' '!' '\xd2' -- LATIN CAPITAL LETTER O WITH GRAVE+ $ Entry 'O' '\'' '\xd3' -- LATIN CAPITAL LETTER O WITH ACUTE+ $ Entry 'O' '>' '\xd4' -- LATIN CAPITAL LETTER O WITH CIRCUMFLEX+ $ Entry 'O' '?' '\xd5' -- LATIN CAPITAL LETTER O WITH TILDE+ $ Entry 'O' ':' '\xd6' -- LATIN CAPITAL LETTER O WITH DIAERESIS+ $ Entry '*' 'X' '\xd7' -- MULTIPLICATION SIGN+ $ Entry 'O' '/' '\xd8' -- LATIN CAPITAL LETTER O WITH STROKE+ $ Entry 'U' '!' '\xd9' -- LATIN CAPITAL LETTER U WITH GRAVE+ $ Entry 'U' '\'' '\xda' -- LATIN CAPITAL LETTER U WITH ACUTE+ $ Entry 'U' '>' '\xdb' -- LATIN CAPITAL LETTER U WITH CIRCUMFLEX+ $ Entry 'U' ':' '\xdc' -- LATIN CAPITAL LETTER U WITH DIAERESIS+ $ Entry 'Y' '\'' '\xdd' -- LATIN CAPITAL LETTER Y WITH ACUTE+ $ Entry 'T' 'H' '\xde' -- LATIN CAPITAL LETTER THORN (Icelandic)+ $ Entry 's' 's' '\xdf' -- LATIN SMALL LETTER SHARP S (German)+ $ Entry 'a' '!' '\xe0' -- LATIN SMALL LETTER A WITH GRAVE+ $ Entry 'a' '\'' '\xe1' -- LATIN SMALL LETTER A WITH ACUTE+ $ Entry 'a' '>' '\xe2' -- LATIN SMALL LETTER A WITH CIRCUMFLEX+ $ Entry 'a' '?' '\xe3' -- LATIN SMALL LETTER A WITH TILDE+ $ Entry 'a' ':' '\xe4' -- LATIN SMALL LETTER A WITH DIAERESIS+ $ Entry 'a' 'a' '\xe5' -- LATIN SMALL LETTER A WITH RING ABOVE+ $ Entry 'a' 'e' '\xe6' -- LATIN SMALL LETTER AE+ $ Entry 'c' ',' '\xe7' -- LATIN SMALL LETTER C WITH CEDILLA+ $ Entry 'e' '!' '\xe8' -- LATIN SMALL LETTER E WITH GRAVE+ $ Entry 'e' '\'' '\xe9' -- LATIN SMALL LETTER E WITH ACUTE+ $ Entry 'e' '>' '\xea' -- LATIN SMALL LETTER E WITH CIRCUMFLEX+ $ Entry 'e' ':' '\xeb' -- LATIN SMALL LETTER E WITH DIAERESIS+ $ Entry 'i' '!' '\xec' -- LATIN SMALL LETTER I WITH GRAVE+ $ Entry 'i' '\'' '\xed' -- LATIN SMALL LETTER I WITH ACUTE+ $ Entry 'i' '>' '\xee' -- LATIN SMALL LETTER I WITH CIRCUMFLEX+ $ Entry 'i' ':' '\xef' -- LATIN SMALL LETTER I WITH DIAERESIS+ $ Entry 'd' '-' '\xf0' -- LATIN SMALL LETTER ETH (Icelandic)+ $ Entry 'n' '?' '\xf1' -- LATIN SMALL LETTER N WITH TILDE+ $ Entry 'o' '!' '\xf2' -- LATIN SMALL LETTER O WITH GRAVE+ $ Entry 'o' '\'' '\xf3' -- LATIN SMALL LETTER O WITH ACUTE+ $ Entry 'o' '>' '\xf4' -- LATIN SMALL LETTER O WITH CIRCUMFLEX+ $ Entry 'o' '?' '\xf5' -- LATIN SMALL LETTER O WITH TILDE+ $ Entry 'o' ':' '\xf6' -- LATIN SMALL LETTER O WITH DIAERESIS+ $ Entry '-' ':' '\xf7' -- DIVISION SIGN+ $ Entry 'o' '/' '\xf8' -- LATIN SMALL LETTER O WITH STROKE+ $ Entry 'u' '!' '\xf9' -- LATIN SMALL LETTER U WITH GRAVE+ $ Entry 'u' '\'' '\xfa' -- LATIN SMALL LETTER U WITH ACUTE+ $ Entry 'u' '>' '\xfb' -- LATIN SMALL LETTER U WITH CIRCUMFLEX+ $ Entry 'u' ':' '\xfc' -- LATIN SMALL LETTER U WITH DIAERESIS+ $ Entry 'y' '\'' '\xfd' -- LATIN SMALL LETTER Y WITH ACUTE+ $ Entry 't' 'h' '\xfe' -- LATIN SMALL LETTER THORN (Icelandic)+ $ Entry 'y' ':' '\xff' -- LATIN SMALL LETTER Y WITH DIAERESIS+ $ Entry 'A' '-' '\x0100' -- LATIN CAPITAL LETTER A WITH MACRON+ $ Entry 'a' '-' '\x0101' -- LATIN SMALL LETTER A WITH MACRON+ $ Entry 'A' '(' '\x0102' -- LATIN CAPITAL LETTER A WITH BREVE+ $ Entry 'a' '(' '\x0103' -- LATIN SMALL LETTER A WITH BREVE+ $ Entry 'A' ';' '\x0104' -- LATIN CAPITAL LETTER A WITH OGONEK+ $ Entry 'a' ';' '\x0105' -- LATIN SMALL LETTER A WITH OGONEK+ $ Entry 'C' '\'' '\x0106' -- LATIN CAPITAL LETTER C WITH ACUTE+ $ Entry 'c' '\'' '\x0107' -- LATIN SMALL LETTER C WITH ACUTE+ $ Entry 'C' '>' '\x0108' -- LATIN CAPITAL LETTER C WITH CIRCUMFLEX+ $ Entry 'c' '>' '\x0109' -- LATIN SMALL LETTER C WITH CIRCUMFLEX+ $ Entry 'C' '.' '\x010A' -- LATIN CAPITAL LETTER C WITH DOT ABOVE+ $ Entry 'c' '.' '\x010B' -- LATIN SMALL LETTER C WITH DOT ABOVE+ $ Entry 'C' '<' '\x010C' -- LATIN CAPITAL LETTER C WITH CARON+ $ Entry 'c' '<' '\x010D' -- LATIN SMALL LETTER C WITH CARON+ $ Entry 'D' '<' '\x010E' -- LATIN CAPITAL LETTER D WITH CARON+ $ Entry 'd' '<' '\x010F' -- LATIN SMALL LETTER D WITH CARON+ $ Entry 'D' '/' '\x0110' -- LATIN CAPITAL LETTER D WITH STROKE+ $ Entry 'd' '/' '\x0111' -- LATIN SMALL LETTER D WITH STROKE+ $ Entry 'E' '-' '\x0112' -- LATIN CAPITAL LETTER E WITH MACRON+ $ Entry 'e' '-' '\x0113' -- LATIN SMALL LETTER E WITH MACRON+ $ Entry 'E' '(' '\x0114' -- LATIN CAPITAL LETTER E WITH BREVE+ $ Entry 'e' '(' '\x0115' -- LATIN SMALL LETTER E WITH BREVE+ $ Entry 'E' '.' '\x0116' -- LATIN CAPITAL LETTER E WITH DOT ABOVE+ $ Entry 'e' '.' '\x0117' -- LATIN SMALL LETTER E WITH DOT ABOVE+ $ Entry 'E' ';' '\x0118' -- LATIN CAPITAL LETTER E WITH OGONEK+ $ Entry 'e' ';' '\x0119' -- LATIN SMALL LETTER E WITH OGONEK+ $ Entry 'E' '<' '\x011A' -- LATIN CAPITAL LETTER E WITH CARON+ $ Entry 'e' '<' '\x011B' -- LATIN SMALL LETTER E WITH CARON+ $ Entry 'G' '>' '\x011C' -- LATIN CAPITAL LETTER G WITH CIRCUMFLEX+ $ Entry 'g' '>' '\x011D' -- LATIN SMALL LETTER G WITH CIRCUMFLEX+ $ Entry 'G' '(' '\x011E' -- LATIN CAPITAL LETTER G WITH BREVE+ $ Entry 'g' '(' '\x011F' -- LATIN SMALL LETTER G WITH BREVE+ $ Entry 'G' '.' '\x0120' -- LATIN CAPITAL LETTER G WITH DOT ABOVE+ $ Entry 'g' '.' '\x0121' -- LATIN SMALL LETTER G WITH DOT ABOVE+ $ Entry 'G' ',' '\x0122' -- LATIN CAPITAL LETTER G WITH CEDILLA+ $ Entry 'g' ',' '\x0123' -- LATIN SMALL LETTER G WITH CEDILLA+ $ Entry 'H' '>' '\x0124' -- LATIN CAPITAL LETTER H WITH CIRCUMFLEX+ $ Entry 'h' '>' '\x0125' -- LATIN SMALL LETTER H WITH CIRCUMFLEX+ $ Entry 'H' '/' '\x0126' -- LATIN CAPITAL LETTER H WITH STROKE+ $ Entry 'h' '/' '\x0127' -- LATIN SMALL LETTER H WITH STROKE+ $ Entry 'I' '?' '\x0128' -- LATIN CAPITAL LETTER I WITH TILDE+ $ Entry 'i' '?' '\x0129' -- LATIN SMALL LETTER I WITH TILDE+ $ Entry 'I' '-' '\x012A' -- LATIN CAPITAL LETTER I WITH MACRON+ $ Entry 'i' '-' '\x012B' -- LATIN SMALL LETTER I WITH MACRON+ $ Entry 'I' '(' '\x012C' -- LATIN CAPITAL LETTER I WITH BREVE+ $ Entry 'i' '(' '\x012D' -- LATIN SMALL LETTER I WITH BREVE+ $ Entry 'I' ';' '\x012E' -- LATIN CAPITAL LETTER I WITH OGONEK+ $ Entry 'i' ';' '\x012F' -- LATIN SMALL LETTER I WITH OGONEK+ $ Entry 'I' '.' '\x0130' -- LATIN CAPITAL LETTER I WITH DOT ABOVE+ $ Entry 'i' '.' '\x0131' -- LATIN SMALL LETTER DOTLESS I+ $ Entry 'I' 'J' '\x0132' -- LATIN CAPITAL LIGATURE IJ+ $ Entry 'i' 'j' '\x0133' -- LATIN SMALL LIGATURE IJ+ $ Entry 'J' '>' '\x0134' -- LATIN CAPITAL LETTER J WITH CIRCUMFLEX+ $ Entry 'j' '>' '\x0135' -- LATIN SMALL LETTER J WITH CIRCUMFLEX+ $ Entry 'K' ',' '\x0136' -- LATIN CAPITAL LETTER K WITH CEDILLA+ $ Entry 'k' ',' '\x0137' -- LATIN SMALL LETTER K WITH CEDILLA+ $ Entry 'k' 'k' '\x0138' -- LATIN SMALL LETTER KRA+ $ Entry 'L' '\'' '\x0139' -- LATIN CAPITAL LETTER L WITH ACUTE+ $ Entry 'l' '\'' '\x013A' -- LATIN SMALL LETTER L WITH ACUTE+ $ Entry 'L' ',' '\x013B' -- LATIN CAPITAL LETTER L WITH CEDILLA+ $ Entry 'l' ',' '\x013C' -- LATIN SMALL LETTER L WITH CEDILLA+ $ Entry 'L' '<' '\x013D' -- LATIN CAPITAL LETTER L WITH CARON+ $ Entry 'l' '<' '\x013E' -- LATIN SMALL LETTER L WITH CARON+ $ Entry 'L' '.' '\x013F' -- LATIN CAPITAL LETTER L WITH MIDDLE DOT+ $ Entry 'l' '.' '\x0140' -- LATIN SMALL LETTER L WITH MIDDLE DOT+ $ Entry 'L' '/' '\x0141' -- LATIN CAPITAL LETTER L WITH STROKE+ $ Entry 'l' '/' '\x0142' -- LATIN SMALL LETTER L WITH STROKE+ $ Entry 'N' '\'' '\x0143' -- LATIN CAPITAL LETTER N WITH ACUTE `+ $ Entry 'n' '\'' '\x0144' -- LATIN SMALL LETTER N WITH ACUTE `+ $ Entry 'N' ',' '\x0145' -- LATIN CAPITAL LETTER N WITH CEDILLA `+ $ Entry 'n' ',' '\x0146' -- LATIN SMALL LETTER N WITH CEDILLA `+ $ Entry 'N' '<' '\x0147' -- LATIN CAPITAL LETTER N WITH CARON `+ $ Entry 'n' '<' '\x0148' -- LATIN SMALL LETTER N WITH CARON `+ $ Entry '\'' 'n' '\x0149' -- LATIN SMALL LETTER N PRECEDED BY APOSTROPHE `+ $ Entry 'N' 'G' '\x014A' -- LATIN CAPITAL LETTER ENG+ $ Entry 'n' 'g' '\x014B' -- LATIN SMALL LETTER ENG+ $ Entry 'O' '-' '\x014C' -- LATIN CAPITAL LETTER O WITH MACRON+ $ Entry 'o' '-' '\x014D' -- LATIN SMALL LETTER O WITH MACRON+ $ Entry 'O' '(' '\x014E' -- LATIN CAPITAL LETTER O WITH BREVE+ $ Entry 'o' '(' '\x014F' -- LATIN SMALL LETTER O WITH BREVE+ $ Entry 'O' '"' '\x0150' -- LATIN CAPITAL LETTER O WITH DOUBLE ACUTE+ $ Entry 'o' '"' '\x0151' -- LATIN SMALL LETTER O WITH DOUBLE ACUTE+ $ Entry 'O' 'E' '\x0152' -- LATIN CAPITAL LIGATURE OE+ $ Entry 'o' 'e' '\x0153' -- LATIN SMALL LIGATURE OE+ $ Entry 'R' '\'' '\x0154' -- LATIN CAPITAL LETTER R WITH ACUTE+ $ Entry 'r' '\'' '\x0155' -- LATIN SMALL LETTER R WITH ACUTE+ $ Entry 'R' ',' '\x0156' -- LATIN CAPITAL LETTER R WITH CEDILLA+ $ Entry 'r' ',' '\x0157' -- LATIN SMALL LETTER R WITH CEDILLA+ $ Entry 'R' '<' '\x0158' -- LATIN CAPITAL LETTER R WITH CARON+ $ Entry 'r' '<' '\x0159' -- LATIN SMALL LETTER R WITH CARON+ $ Entry 'S' '\'' '\x015A' -- LATIN CAPITAL LETTER S WITH ACUTE+ $ Entry 's' '\'' '\x015B' -- LATIN SMALL LETTER S WITH ACUTE+ $ Entry 'S' '>' '\x015C' -- LATIN CAPITAL LETTER S WITH CIRCUMFLEX+ $ Entry 's' '>' '\x015D' -- LATIN SMALL LETTER S WITH CIRCUMFLEX+ $ Entry 'S' ',' '\x015E' -- LATIN CAPITAL LETTER S WITH CEDILLA+ $ Entry 's' ',' '\x015F' -- LATIN SMALL LETTER S WITH CEDILLA+ $ Entry 'S' '<' '\x0160' -- LATIN CAPITAL LETTER S WITH CARON+ $ Entry 's' '<' '\x0161' -- LATIN SMALL LETTER S WITH CARON+ $ Entry 'T' ',' '\x0162' -- LATIN CAPITAL LETTER T WITH CEDILLA+ $ Entry 't' ',' '\x0163' -- LATIN SMALL LETTER T WITH CEDILLA+ $ Entry 'T' '<' '\x0164' -- LATIN CAPITAL LETTER T WITH CARON+ $ Entry 't' '<' '\x0165' -- LATIN SMALL LETTER T WITH CARON+ $ Entry 'T' '/' '\x0166' -- LATIN CAPITAL LETTER T WITH STROKE+ $ Entry 't' '/' '\x0167' -- LATIN SMALL LETTER T WITH STROKE+ $ Entry 'U' '?' '\x0168' -- LATIN CAPITAL LETTER U WITH TILDE+ $ Entry 'u' '?' '\x0169' -- LATIN SMALL LETTER U WITH TILDE+ $ Entry 'U' '-' '\x016A' -- LATIN CAPITAL LETTER U WITH MACRON+ $ Entry 'u' '-' '\x016B' -- LATIN SMALL LETTER U WITH MACRON+ $ Entry 'U' '(' '\x016C' -- LATIN CAPITAL LETTER U WITH BREVE+ $ Entry 'u' '(' '\x016D' -- LATIN SMALL LETTER U WITH BREVE+ $ Entry 'U' '0' '\x016E' -- LATIN CAPITAL LETTER U WITH RING ABOVE+ $ Entry 'u' '0' '\x016F' -- LATIN SMALL LETTER U WITH RING ABOVE+ $ Entry 'U' '"' '\x0170' -- LATIN CAPITAL LETTER U WITH DOUBLE ACUTE+ $ Entry 'u' '"' '\x0171' -- LATIN SMALL LETTER U WITH DOUBLE ACUTE+ $ Entry 'U' ';' '\x0172' -- LATIN CAPITAL LETTER U WITH OGONEK+ $ Entry 'u' ';' '\x0173' -- LATIN SMALL LETTER U WITH OGONEK+ $ Entry 'W' '>' '\x0174' -- LATIN CAPITAL LETTER W WITH CIRCUMFLEX+ $ Entry 'w' '>' '\x0175' -- LATIN SMALL LETTER W WITH CIRCUMFLEX+ $ Entry 'Y' '>' '\x0176' -- LATIN CAPITAL LETTER Y WITH CIRCUMFLEX+ $ Entry 'y' '>' '\x0177' -- LATIN SMALL LETTER Y WITH CIRCUMFLEX+ $ Entry 'Y' ':' '\x0178' -- LATIN CAPITAL LETTER Y WITH DIAERESIS+ $ Entry 'Z' '\'' '\x0179' -- LATIN CAPITAL LETTER Z WITH ACUTE+ $ Entry 'z' '\'' '\x017A' -- LATIN SMALL LETTER Z WITH ACUTE+ $ Entry 'Z' '.' '\x017B' -- LATIN CAPITAL LETTER Z WITH DOT ABOVE+ $ Entry 'z' '.' '\x017C' -- LATIN SMALL LETTER Z WITH DOT ABOVE+ $ Entry 'Z' '<' '\x017D' -- LATIN CAPITAL LETTER Z WITH CARON+ $ Entry 'z' '<' '\x017E' -- LATIN SMALL LETTER Z WITH CARON+ $ Entry 'O' '9' '\x01A0' -- LATIN CAPITAL LETTER O WITH HORN+ $ Entry 'o' '9' '\x01A1' -- LATIN SMALL LETTER O WITH HORN+ $ Entry 'O' 'I' '\x01A2' -- LATIN CAPITAL LETTER OI+ $ Entry 'o' 'i' '\x01A3' -- LATIN SMALL LETTER OI+ $ Entry 'y' 'r' '\x01A6' -- LATIN LETTER YR+ $ Entry 'U' '9' '\x01AF' -- LATIN CAPITAL LETTER U WITH HORN+ $ Entry 'u' '9' '\x01B0' -- LATIN SMALL LETTER U WITH HORN+ $ Entry 'Z' '/' '\x01B5' -- LATIN CAPITAL LETTER Z WITH STROKE+ $ Entry 'z' '/' '\x01B6' -- LATIN SMALL LETTER Z WITH STROKE+ $ Entry 'E' 'D' '\x01B7' -- LATIN CAPITAL LETTER EZH+ $ Entry 'A' '<' '\x01CD' -- LATIN CAPITAL LETTER A WITH CARON+ $ Entry 'a' '<' '\x01CE' -- LATIN SMALL LETTER A WITH CARON+ $ Entry 'I' '<' '\x01CF' -- LATIN CAPITAL LETTER I WITH CARON+ $ Entry 'i' '<' '\x01D0' -- LATIN SMALL LETTER I WITH CARON+ $ Entry 'O' '<' '\x01D1' -- LATIN CAPITAL LETTER O WITH CARON+ $ Entry 'o' '<' '\x01D2' -- LATIN SMALL LETTER O WITH CARON+ $ Entry 'U' '<' '\x01D3' -- LATIN CAPITAL LETTER U WITH CARON+ $ Entry 'u' '<' '\x01D4' -- LATIN SMALL LETTER U WITH CARON+ $ Entry 'A' '1' '\x01DE' -- LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON+ $ Entry 'a' '1' '\x01DF' -- LATIN SMALL LETTER A WITH DIAERESIS AND MACRON+ $ Entry 'A' '7' '\x01E0' -- LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON+ $ Entry 'a' '7' '\x01E1' -- LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON+ $ Entry 'A' '3' '\x01E2' -- LATIN CAPITAL LETTER AE WITH MACRON+ $ Entry 'a' '3' '\x01E3' -- LATIN SMALL LETTER AE WITH MACRON+ $ Entry 'G' '/' '\x01E4' -- LATIN CAPITAL LETTER G WITH STROKE+ $ Entry 'g' '/' '\x01E5' -- LATIN SMALL LETTER G WITH STROKE+ $ Entry 'G' '<' '\x01E6' -- LATIN CAPITAL LETTER G WITH CARON+ $ Entry 'g' '<' '\x01E7' -- LATIN SMALL LETTER G WITH CARON+ $ Entry 'K' '<' '\x01E8' -- LATIN CAPITAL LETTER K WITH CARON+ $ Entry 'k' '<' '\x01E9' -- LATIN SMALL LETTER K WITH CARON+ $ Entry 'O' ';' '\x01EA' -- LATIN CAPITAL LETTER O WITH OGONEK+ $ Entry 'o' ';' '\x01EB' -- LATIN SMALL LETTER O WITH OGONEK+ $ Entry 'O' '1' '\x01EC' -- LATIN CAPITAL LETTER O WITH OGONEK AND MACRON+ $ Entry 'o' '1' '\x01ED' -- LATIN SMALL LETTER O WITH OGONEK AND MACRON+ $ Entry 'E' 'Z' '\x01EE' -- LATIN CAPITAL LETTER EZH WITH CARON+ $ Entry 'e' 'z' '\x01EF' -- LATIN SMALL LETTER EZH WITH CARON+ $ Entry 'j' '<' '\x01F0' -- LATIN SMALL LETTER J WITH CARON+ $ Entry 'G' '\'' '\x01F4' -- LATIN CAPITAL LETTER G WITH ACUTE+ $ Entry 'g' '\'' '\x01F5' -- LATIN SMALL LETTER G WITH ACUTE+ $ Entry ';' 'S' '\x02BF' -- MODIFIER LETTER LEFT HALF RING+ $ Entry '\'' '<' '\x02C7' -- CARON+ $ Entry '\'' '(' '\x02D8' -- BREVE+ $ Entry '\'' '.' '\x02D9' -- DOT ABOVE+ $ Entry '\'' '0' '\x02DA' -- RING ABOVE+ $ Entry '\'' ';' '\x02DB' -- OGONEK+ $ Entry '\'' '"' '\x02DD' -- DOUBLE ACUTE ACCENT+ $ Entry 'A' '%' '\x0386' -- GREEK CAPITAL LETTER ALPHA WITH TONOS+ $ Entry 'E' '%' '\x0388' -- GREEK CAPITAL LETTER EPSILON WITH TONOS+ $ Entry 'Y' '%' '\x0389' -- GREEK CAPITAL LETTER ETA WITH TONOS+ $ Entry 'I' '%' '\x038A' -- GREEK CAPITAL LETTER IOTA WITH TONOS+ $ Entry 'O' '%' '\x038C' -- GREEK CAPITAL LETTER OMICRON WITH TONOS+ $ Entry 'U' '%' '\x038E' -- GREEK CAPITAL LETTER UPSILON WITH TONOS+ $ Entry 'W' '%' '\x038F' -- GREEK CAPITAL LETTER OMEGA WITH TONOS+ $ Entry 'i' '3' '\x0390' -- GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS+ $ Entry 'A' '*' '\x0391' -- GREEK CAPITAL LETTER ALPHA+ $ Entry 'B' '*' '\x0392' -- GREEK CAPITAL LETTER BETA+ $ Entry 'G' '*' '\x0393' -- GREEK CAPITAL LETTER GAMMA+ $ Entry 'D' '*' '\x0394' -- GREEK CAPITAL LETTER DELTA+ $ Entry 'E' '*' '\x0395' -- GREEK CAPITAL LETTER EPSILON+ $ Entry 'Z' '*' '\x0396' -- GREEK CAPITAL LETTER ZETA+ $ Entry 'Y' '*' '\x0397' -- GREEK CAPITAL LETTER ETA+ $ Entry 'H' '*' '\x0398' -- GREEK CAPITAL LETTER THETA+ $ Entry 'I' '*' '\x0399' -- GREEK CAPITAL LETTER IOTA+ $ Entry 'K' '*' '\x039A' -- GREEK CAPITAL LETTER KAPPA+ $ Entry 'L' '*' '\x039B' -- GREEK CAPITAL LETTER LAMDA+ $ Entry 'M' '*' '\x039C' -- GREEK CAPITAL LETTER MU+ $ Entry 'N' '*' '\x039D' -- GREEK CAPITAL LETTER NU+ $ Entry 'C' '*' '\x039E' -- GREEK CAPITAL LETTER XI+ $ Entry 'O' '*' '\x039F' -- GREEK CAPITAL LETTER OMICRON+ $ Entry 'P' '*' '\x03A0' -- GREEK CAPITAL LETTER PI+ $ Entry 'R' '*' '\x03A1' -- GREEK CAPITAL LETTER RHO+ $ Entry 'S' '*' '\x03A3' -- GREEK CAPITAL LETTER SIGMA+ $ Entry 'T' '*' '\x03A4' -- GREEK CAPITAL LETTER TAU+ $ Entry 'U' '*' '\x03A5' -- GREEK CAPITAL LETTER UPSILON+ $ Entry 'F' '*' '\x03A6' -- GREEK CAPITAL LETTER PHI+ $ Entry 'X' '*' '\x03A7' -- GREEK CAPITAL LETTER CHI+ $ Entry 'Q' '*' '\x03A8' -- GREEK CAPITAL LETTER PSI+ $ Entry 'W' '*' '\x03A9' -- GREEK CAPITAL LETTER OMEGA+ $ Entry 'J' '*' '\x03AA' -- GREEK CAPITAL LETTER IOTA WITH DIALYTIKA+ $ Entry 'V' '*' '\x03AB' -- GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA+ $ Entry 'a' '%' '\x03AC' -- GREEK SMALL LETTER ALPHA WITH TONOS+ $ Entry 'e' '%' '\x03AD' -- GREEK SMALL LETTER EPSILON WITH TONOS+ $ Entry 'y' '%' '\x03AE' -- GREEK SMALL LETTER ETA WITH TONOS+ $ Entry 'i' '%' '\x03AF' -- GREEK SMALL LETTER IOTA WITH TONOS+ $ Entry 'u' '3' '\x03B0' -- GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS+ $ Entry 'a' '*' '\x03B1' -- GREEK SMALL LETTER ALPHA+ $ Entry 'b' '*' '\x03B2' -- GREEK SMALL LETTER BETA+ $ Entry 'g' '*' '\x03B3' -- GREEK SMALL LETTER GAMMA+ $ Entry 'd' '*' '\x03B4' -- GREEK SMALL LETTER DELTA+ $ Entry 'e' '*' '\x03B5' -- GREEK SMALL LETTER EPSILON+ $ Entry 'z' '*' '\x03B6' -- GREEK SMALL LETTER ZETA+ $ Entry 'y' '*' '\x03B7' -- GREEK SMALL LETTER ETA+ $ Entry 'h' '*' '\x03B8' -- GREEK SMALL LETTER THETA+ $ Entry 'i' '*' '\x03B9' -- GREEK SMALL LETTER IOTA+ $ Entry 'k' '*' '\x03BA' -- GREEK SMALL LETTER KAPPA+ $ Entry 'l' '*' '\x03BB' -- GREEK SMALL LETTER LAMDA+ $ Entry 'm' '*' '\x03BC' -- GREEK SMALL LETTER MU+ $ Entry 'n' '*' '\x03BD' -- GREEK SMALL LETTER NU+ $ Entry 'c' '*' '\x03BE' -- GREEK SMALL LETTER XI+ $ Entry 'o' '*' '\x03BF' -- GREEK SMALL LETTER OMICRON+ $ Entry 'p' '*' '\x03C0' -- GREEK SMALL LETTER PI+ $ Entry 'r' '*' '\x03C1' -- GREEK SMALL LETTER RHO+ $ Entry '*' 's' '\x03C2' -- GREEK SMALL LETTER FINAL SIGMA+ $ Entry 's' '*' '\x03C3' -- GREEK SMALL LETTER SIGMA+ $ Entry 't' '*' '\x03C4' -- GREEK SMALL LETTER TAU+ $ Entry 'u' '*' '\x03C5' -- GREEK SMALL LETTER UPSILON+ $ Entry 'f' '*' '\x03C6' -- GREEK SMALL LETTER PHI+ $ Entry 'x' '*' '\x03C7' -- GREEK SMALL LETTER CHI+ $ Entry 'q' '*' '\x03C8' -- GREEK SMALL LETTER PSI+ $ Entry 'w' '*' '\x03C9' -- GREEK SMALL LETTER OMEGA+ $ Entry 'j' '*' '\x03CA' -- GREEK SMALL LETTER IOTA WITH DIALYTIKA+ $ Entry 'v' '*' '\x03CB' -- GREEK SMALL LETTER UPSILON WITH DIALYTIKA+ $ Entry 'o' '%' '\x03CC' -- GREEK SMALL LETTER OMICRON WITH TONOS+ $ Entry 'u' '%' '\x03CD' -- GREEK SMALL LETTER UPSILON WITH TONOS+ $ Entry 'w' '%' '\x03CE' -- GREEK SMALL LETTER OMEGA WITH TONOS+ $ Entry '\'' 'G' '\x03D8' -- GREEK LETTER ARCHAIC KOPPA+ $ Entry ',' 'G' '\x03D9' -- GREEK SMALL LETTER ARCHAIC KOPPA+ $ Entry 'T' '3' '\x03DA' -- GREEK LETTER STIGMA+ $ Entry 't' '3' '\x03DB' -- GREEK SMALL LETTER STIGMA+ $ Entry 'M' '3' '\x03DC' -- GREEK LETTER DIGAMMA+ $ Entry 'm' '3' '\x03DD' -- GREEK SMALL LETTER DIGAMMA+ $ Entry 'K' '3' '\x03DE' -- GREEK LETTER KOPPA+ $ Entry 'k' '3' '\x03DF' -- GREEK SMALL LETTER KOPPA+ $ Entry 'P' '3' '\x03E0' -- GREEK LETTER SAMPI+ $ Entry 'p' '3' '\x03E1' -- GREEK SMALL LETTER SAMPI+ $ Entry '\'' '%' '\x03F4' -- GREEK CAPITAL THETA SYMBOL+ $ Entry 'j' '3' '\x03F5' -- GREEK LUNATE EPSILON SYMBOL+ $ Entry 'I' 'O' '\x0401' -- CYRILLIC CAPITAL LETTER IO+ $ Entry 'D' '%' '\x0402' -- CYRILLIC CAPITAL LETTER DJE+ $ Entry 'G' '%' '\x0403' -- CYRILLIC CAPITAL LETTER GJE+ $ Entry 'I' 'E' '\x0404' -- CYRILLIC CAPITAL LETTER UKRAINIAN IE+ $ Entry 'D' 'S' '\x0405' -- CYRILLIC CAPITAL LETTER DZE+ $ Entry 'I' 'I' '\x0406' -- CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I+ $ Entry 'Y' 'I' '\x0407' -- CYRILLIC CAPITAL LETTER YI+ $ Entry 'J' '%' '\x0408' -- CYRILLIC CAPITAL LETTER JE+ $ Entry 'L' 'J' '\x0409' -- CYRILLIC CAPITAL LETTER LJE+ $ Entry 'N' 'J' '\x040A' -- CYRILLIC CAPITAL LETTER NJE+ $ Entry 'T' 's' '\x040B' -- CYRILLIC CAPITAL LETTER TSHE+ $ Entry 'K' 'J' '\x040C' -- CYRILLIC CAPITAL LETTER KJE+ $ Entry 'V' '%' '\x040E' -- CYRILLIC CAPITAL LETTER SHORT U+ $ Entry 'D' 'Z' '\x040F' -- CYRILLIC CAPITAL LETTER DZHE+ $ Entry 'A' '=' '\x0410' -- CYRILLIC CAPITAL LETTER A+ $ Entry 'B' '=' '\x0411' -- CYRILLIC CAPITAL LETTER BE+ $ Entry 'V' '=' '\x0412' -- CYRILLIC CAPITAL LETTER VE+ $ Entry 'G' '=' '\x0413' -- CYRILLIC CAPITAL LETTER GHE+ $ Entry 'D' '=' '\x0414' -- CYRILLIC CAPITAL LETTER DE+ $ Entry 'E' '=' '\x0415' -- CYRILLIC CAPITAL LETTER IE+ $ Entry 'Z' '%' '\x0416' -- CYRILLIC CAPITAL LETTER ZHE+ $ Entry 'Z' '=' '\x0417' -- CYRILLIC CAPITAL LETTER ZE+ $ Entry 'I' '=' '\x0418' -- CYRILLIC CAPITAL LETTER I+ $ Entry 'J' '=' '\x0419' -- CYRILLIC CAPITAL LETTER SHORT I+ $ Entry 'K' '=' '\x041A' -- CYRILLIC CAPITAL LETTER KA+ $ Entry 'L' '=' '\x041B' -- CYRILLIC CAPITAL LETTER EL+ $ Entry 'M' '=' '\x041C' -- CYRILLIC CAPITAL LETTER EM+ $ Entry 'N' '=' '\x041D' -- CYRILLIC CAPITAL LETTER EN+ $ Entry 'O' '=' '\x041E' -- CYRILLIC CAPITAL LETTER O+ $ Entry 'P' '=' '\x041F' -- CYRILLIC CAPITAL LETTER PE+ $ Entry 'R' '=' '\x0420' -- CYRILLIC CAPITAL LETTER ER+ $ Entry 'S' '=' '\x0421' -- CYRILLIC CAPITAL LETTER ES+ $ Entry 'T' '=' '\x0422' -- CYRILLIC CAPITAL LETTER TE+ $ Entry 'U' '=' '\x0423' -- CYRILLIC CAPITAL LETTER U+ $ Entry 'F' '=' '\x0424' -- CYRILLIC CAPITAL LETTER EF+ $ Entry 'H' '=' '\x0425' -- CYRILLIC CAPITAL LETTER HA+ $ Entry 'C' '=' '\x0426' -- CYRILLIC CAPITAL LETTER TSE+ $ Entry 'C' '%' '\x0427' -- CYRILLIC CAPITAL LETTER CHE+ $ Entry 'S' '%' '\x0428' -- CYRILLIC CAPITAL LETTER SHA+ $ Entry 'S' 'c' '\x0429' -- CYRILLIC CAPITAL LETTER SHCHA+ $ Entry '=' '\'' '\x042A' -- CYRILLIC CAPITAL LETTER HARD SIGN+ $ Entry 'Y' '=' '\x042B' -- CYRILLIC CAPITAL LETTER YERU+ $ Entry '%' '\'' '\x042C' -- CYRILLIC CAPITAL LETTER SOFT SIGN+ $ Entry 'J' 'E' '\x042D' -- CYRILLIC CAPITAL LETTER E+ $ Entry 'J' 'U' '\x042E' -- CYRILLIC CAPITAL LETTER YU+ $ Entry 'J' 'A' '\x042F' -- CYRILLIC CAPITAL LETTER YA+ $ Entry 'a' '=' '\x0430' -- CYRILLIC SMALL LETTER A+ $ Entry 'b' '=' '\x0431' -- CYRILLIC SMALL LETTER BE+ $ Entry 'v' '=' '\x0432' -- CYRILLIC SMALL LETTER VE+ $ Entry 'g' '=' '\x0433' -- CYRILLIC SMALL LETTER GHE+ $ Entry 'd' '=' '\x0434' -- CYRILLIC SMALL LETTER DE+ $ Entry 'e' '=' '\x0435' -- CYRILLIC SMALL LETTER IE+ $ Entry 'z' '%' '\x0436' -- CYRILLIC SMALL LETTER ZHE+ $ Entry 'z' '=' '\x0437' -- CYRILLIC SMALL LETTER ZE+ $ Entry 'i' '=' '\x0438' -- CYRILLIC SMALL LETTER I+ $ Entry 'j' '=' '\x0439' -- CYRILLIC SMALL LETTER SHORT I+ $ Entry 'k' '=' '\x043A' -- CYRILLIC SMALL LETTER KA+ $ Entry 'l' '=' '\x043B' -- CYRILLIC SMALL LETTER EL+ $ Entry 'm' '=' '\x043C' -- CYRILLIC SMALL LETTER EM+ $ Entry 'n' '=' '\x043D' -- CYRILLIC SMALL LETTER EN+ $ Entry 'o' '=' '\x043E' -- CYRILLIC SMALL LETTER O+ $ Entry 'p' '=' '\x043F' -- CYRILLIC SMALL LETTER PE+ $ Entry 'r' '=' '\x0440' -- CYRILLIC SMALL LETTER ER+ $ Entry 's' '=' '\x0441' -- CYRILLIC SMALL LETTER ES+ $ Entry 't' '=' '\x0442' -- CYRILLIC SMALL LETTER TE+ $ Entry 'u' '=' '\x0443' -- CYRILLIC SMALL LETTER U+ $ Entry 'f' '=' '\x0444' -- CYRILLIC SMALL LETTER EF+ $ Entry 'h' '=' '\x0445' -- CYRILLIC SMALL LETTER HA+ $ Entry 'c' '=' '\x0446' -- CYRILLIC SMALL LETTER TSE+ $ Entry 'c' '%' '\x0447' -- CYRILLIC SMALL LETTER CHE+ $ Entry 's' '%' '\x0448' -- CYRILLIC SMALL LETTER SHA+ $ Entry 's' 'c' '\x0449' -- CYRILLIC SMALL LETTER SHCHA+ $ Entry '=' '\'' '\x044A' -- CYRILLIC SMALL LETTER HARD SIGN+ $ Entry 'y' '=' '\x044B' -- CYRILLIC SMALL LETTER YERU+ $ Entry '%' '\'' '\x044C' -- CYRILLIC SMALL LETTER SOFT SIGN+ $ Entry 'j' 'e' '\x044D' -- CYRILLIC SMALL LETTER E+ $ Entry 'j' 'u' '\x044E' -- CYRILLIC SMALL LETTER YU+ $ Entry 'j' 'a' '\x044F' -- CYRILLIC SMALL LETTER YA+ $ Entry 'i' 'o' '\x0451' -- CYRILLIC SMALL LETTER IO+ $ Entry 'd' '%' '\x0452' -- CYRILLIC SMALL LETTER DJE+ $ Entry 'g' '%' '\x0453' -- CYRILLIC SMALL LETTER GJE+ $ Entry 'i' 'e' '\x0454' -- CYRILLIC SMALL LETTER UKRAINIAN IE+ $ Entry 'd' 's' '\x0455' -- CYRILLIC SMALL LETTER DZE+ $ Entry 'i' 'i' '\x0456' -- CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I+ $ Entry 'y' 'i' '\x0457' -- CYRILLIC SMALL LETTER YI+ $ Entry 'j' '%' '\x0458' -- CYRILLIC SMALL LETTER JE+ $ Entry 'l' 'j' '\x0459' -- CYRILLIC SMALL LETTER LJE+ $ Entry 'n' 'j' '\x045A' -- CYRILLIC SMALL LETTER NJE+ $ Entry 't' 's' '\x045B' -- CYRILLIC SMALL LETTER TSHE+ $ Entry 'k' 'j' '\x045C' -- CYRILLIC SMALL LETTER KJE+ $ Entry 'v' '%' '\x045E' -- CYRILLIC SMALL LETTER SHORT U+ $ Entry 'd' 'z' '\x045F' -- CYRILLIC SMALL LETTER DZHE+ $ Entry 'Y' '3' '\x0462' -- CYRILLIC CAPITAL LETTER YAT+ $ Entry 'y' '3' '\x0463' -- CYRILLIC SMALL LETTER YAT+ $ Entry 'O' '3' '\x046A' -- CYRILLIC CAPITAL LETTER BIG YUS+ $ Entry 'o' '3' '\x046B' -- CYRILLIC SMALL LETTER BIG YUS+ $ Entry 'F' '3' '\x0472' -- CYRILLIC CAPITAL LETTER FITA+ $ Entry 'f' '3' '\x0473' -- CYRILLIC SMALL LETTER FITA+ $ Entry 'V' '3' '\x0474' -- CYRILLIC CAPITAL LETTER IZHITSA+ $ Entry 'v' '3' '\x0475' -- CYRILLIC SMALL LETTER IZHITSA+ $ Entry 'C' '3' '\x0480' -- CYRILLIC CAPITAL LETTER KOPPA+ $ Entry 'c' '3' '\x0481' -- CYRILLIC SMALL LETTER KOPPA+ $ Entry 'G' '3' '\x0490' -- CYRILLIC CAPITAL LETTER GHE WITH UPTURN+ $ Entry 'g' '3' '\x0491' -- CYRILLIC SMALL LETTER GHE WITH UPTURN+ $ Entry 'A' '+' '\x05D0' -- HEBREW LETTER ALEF+ $ Entry 'B' '+' '\x05D1' -- HEBREW LETTER BET+ $ Entry 'G' '+' '\x05D2' -- HEBREW LETTER GIMEL+ $ Entry 'D' '+' '\x05D3' -- HEBREW LETTER DALET+ $ Entry 'H' '+' '\x05D4' -- HEBREW LETTER HE+ $ Entry 'W' '+' '\x05D5' -- HEBREW LETTER VAV+ $ Entry 'Z' '+' '\x05D6' -- HEBREW LETTER ZAYIN+ $ Entry 'X' '+' '\x05D7' -- HEBREW LETTER HET+ $ Entry 'T' 'j' '\x05D8' -- HEBREW LETTER TET+ $ Entry 'J' '+' '\x05D9' -- HEBREW LETTER YOD+ $ Entry 'K' '%' '\x05DA' -- HEBREW LETTER FINAL KAF+ $ Entry 'K' '+' '\x05DB' -- HEBREW LETTER KAF+ $ Entry 'L' '+' '\x05DC' -- HEBREW LETTER LAMED+ $ Entry 'M' '%' '\x05DD' -- HEBREW LETTER FINAL MEM+ $ Entry 'M' '+' '\x05DE' -- HEBREW LETTER MEM+ $ Entry 'N' '%' '\x05DF' -- HEBREW LETTER FINAL NUN `+ $ Entry 'N' '+' '\x05E0' -- HEBREW LETTER NUN `+ $ Entry 'S' '+' '\x05E1' -- HEBREW LETTER SAMEKH+ $ Entry 'E' '+' '\x05E2' -- HEBREW LETTER AYIN+ $ Entry 'P' '%' '\x05E3' -- HEBREW LETTER FINAL PE+ $ Entry 'P' '+' '\x05E4' -- HEBREW LETTER PE+ $ Entry 'Z' 'j' '\x05E5' -- HEBREW LETTER FINAL TSADI+ $ Entry 'Z' 'J' '\x05E6' -- HEBREW LETTER TSADI+ $ Entry 'Q' '+' '\x05E7' -- HEBREW LETTER QOF+ $ Entry 'R' '+' '\x05E8' -- HEBREW LETTER RESH+ $ Entry 'S' 'h' '\x05E9' -- HEBREW LETTER SHIN+ $ Entry 'T' '+' '\x05EA' -- HEBREW LETTER TAV+ $ Entry ',' '+' '\x060C' -- ARABIC COMMA+ $ Entry ';' '+' '\x061B' -- ARABIC SEMICOLON+ $ Entry '?' '+' '\x061F' -- ARABIC QUESTION MARK+ $ Entry 'H' '\'' '\x0621' -- ARABIC LETTER HAMZA+ $ Entry 'a' 'M' '\x0622' -- ARABIC LETTER ALEF WITH MADDA ABOVE+ $ Entry 'a' 'H' '\x0623' -- ARABIC LETTER ALEF WITH HAMZA ABOVE+ $ Entry 'w' 'H' '\x0624' -- ARABIC LETTER WAW WITH HAMZA ABOVE+ $ Entry 'a' 'h' '\x0625' -- ARABIC LETTER ALEF WITH HAMZA BELOW+ $ Entry 'y' 'H' '\x0626' -- ARABIC LETTER YEH WITH HAMZA ABOVE+ $ Entry 'a' '+' '\x0627' -- ARABIC LETTER ALEF+ $ Entry 'b' '+' '\x0628' -- ARABIC LETTER BEH+ $ Entry 't' 'm' '\x0629' -- ARABIC LETTER TEH MARBUTA+ $ Entry 't' '+' '\x062A' -- ARABIC LETTER TEH+ $ Entry 't' 'k' '\x062B' -- ARABIC LETTER THEH+ $ Entry 'g' '+' '\x062C' -- ARABIC LETTER JEEM+ $ Entry 'h' 'k' '\x062D' -- ARABIC LETTER HAH+ $ Entry 'x' '+' '\x062E' -- ARABIC LETTER KHAH+ $ Entry 'd' '+' '\x062F' -- ARABIC LETTER DAL+ $ Entry 'd' 'k' '\x0630' -- ARABIC LETTER THAL+ $ Entry 'r' '+' '\x0631' -- ARABIC LETTER REH+ $ Entry 'z' '+' '\x0632' -- ARABIC LETTER ZAIN+ $ Entry 's' '+' '\x0633' -- ARABIC LETTER SEEN+ $ Entry 's' 'n' '\x0634' -- ARABIC LETTER SHEEN+ $ Entry 'c' '+' '\x0635' -- ARABIC LETTER SAD+ $ Entry 'd' 'd' '\x0636' -- ARABIC LETTER DAD+ $ Entry 't' 'j' '\x0637' -- ARABIC LETTER TAH+ $ Entry 'z' 'H' '\x0638' -- ARABIC LETTER ZAH+ $ Entry 'e' '+' '\x0639' -- ARABIC LETTER AIN+ $ Entry 'i' '+' '\x063A' -- ARABIC LETTER GHAIN+ $ Entry '+' '+' '\x0640' -- ARABIC TATWEEL+ $ Entry 'f' '+' '\x0641' -- ARABIC LETTER FEH+ $ Entry 'q' '+' '\x0642' -- ARABIC LETTER QAF+ $ Entry 'k' '+' '\x0643' -- ARABIC LETTER KAF+ $ Entry 'l' '+' '\x0644' -- ARABIC LETTER LAM+ $ Entry 'm' '+' '\x0645' -- ARABIC LETTER MEEM+ $ Entry 'n' '+' '\x0646' -- ARABIC LETTER NOON+ $ Entry 'h' '+' '\x0647' -- ARABIC LETTER HEH+ $ Entry 'w' '+' '\x0648' -- ARABIC LETTER WAW+ $ Entry 'j' '+' '\x0649' -- ARABIC LETTER ALEF MAKSURA+ $ Entry 'y' '+' '\x064A' -- ARABIC LETTER YEH+ $ Entry ':' '+' '\x064B' -- ARABIC FATHATAN+ $ Entry '"' '+' '\x064C' -- ARABIC DAMMATAN+ $ Entry '=' '+' '\x064D' -- ARABIC KASRATAN+ $ Entry '/' '+' '\x064E' -- ARABIC FATHA+ $ Entry '\'' '+' '\x064F' -- ARABIC DAMMA+ $ Entry '1' '+' '\x0650' -- ARABIC KASRA+ $ Entry '3' '+' '\x0651' -- ARABIC SHADDA+ $ Entry '0' '+' '\x0652' -- ARABIC SUKUN+ $ Entry 'a' 'S' '\x0670' -- ARABIC LETTER SUPERSCRIPT ALEF+ $ Entry 'p' '+' '\x067E' -- ARABIC LETTER PEH+ $ Entry 'v' '+' '\x06A4' -- ARABIC LETTER VEH+ $ Entry 'g' 'f' '\x06AF' -- ARABIC LETTER GAF+ $ Entry '0' 'a' '\x06F0' -- EXTENDED ARABIC-INDIC DIGIT ZERO+ $ Entry '1' 'a' '\x06F1' -- EXTENDED ARABIC-INDIC DIGIT ONE+ $ Entry '2' 'a' '\x06F2' -- EXTENDED ARABIC-INDIC DIGIT TWO+ $ Entry '3' 'a' '\x06F3' -- EXTENDED ARABIC-INDIC DIGIT THREE+ $ Entry '4' 'a' '\x06F4' -- EXTENDED ARABIC-INDIC DIGIT FOUR+ $ Entry '5' 'a' '\x06F5' -- EXTENDED ARABIC-INDIC DIGIT FIVE+ $ Entry '6' 'a' '\x06F6' -- EXTENDED ARABIC-INDIC DIGIT SIX+ $ Entry '7' 'a' '\x06F7' -- EXTENDED ARABIC-INDIC DIGIT SEVEN+ $ Entry '8' 'a' '\x06F8' -- EXTENDED ARABIC-INDIC DIGIT EIGHT+ $ Entry '9' 'a' '\x06F9' -- EXTENDED ARABIC-INDIC DIGIT NINE+ $ Entry 'B' '.' '\x1E02' -- LATIN CAPITAL LETTER B WITH DOT ABOVE+ $ Entry 'b' '.' '\x1E03' -- LATIN SMALL LETTER B WITH DOT ABOVE+ $ Entry 'B' '_' '\x1E06' -- LATIN CAPITAL LETTER B WITH LINE BELOW+ $ Entry 'b' '_' '\x1E07' -- LATIN SMALL LETTER B WITH LINE BELOW+ $ Entry 'D' '.' '\x1E0A' -- LATIN CAPITAL LETTER D WITH DOT ABOVE+ $ Entry 'd' '.' '\x1E0B' -- LATIN SMALL LETTER D WITH DOT ABOVE+ $ Entry 'D' '_' '\x1E0E' -- LATIN CAPITAL LETTER D WITH LINE BELOW+ $ Entry 'd' '_' '\x1E0F' -- LATIN SMALL LETTER D WITH LINE BELOW+ $ Entry 'D' ',' '\x1E10' -- LATIN CAPITAL LETTER D WITH CEDILLA+ $ Entry 'd' ',' '\x1E11' -- LATIN SMALL LETTER D WITH CEDILLA+ $ Entry 'F' '.' '\x1E1E' -- LATIN CAPITAL LETTER F WITH DOT ABOVE+ $ Entry 'f' '.' '\x1E1F' -- LATIN SMALL LETTER F WITH DOT ABOVE+ $ Entry 'G' '-' '\x1E20' -- LATIN CAPITAL LETTER G WITH MACRON+ $ Entry 'g' '-' '\x1E21' -- LATIN SMALL LETTER G WITH MACRON+ $ Entry 'H' '.' '\x1E22' -- LATIN CAPITAL LETTER H WITH DOT ABOVE+ $ Entry 'h' '.' '\x1E23' -- LATIN SMALL LETTER H WITH DOT ABOVE+ $ Entry 'H' ':' '\x1E26' -- LATIN CAPITAL LETTER H WITH DIAERESIS+ $ Entry 'h' ':' '\x1E27' -- LATIN SMALL LETTER H WITH DIAERESIS+ $ Entry 'H' ',' '\x1E28' -- LATIN CAPITAL LETTER H WITH CEDILLA+ $ Entry 'h' ',' '\x1E29' -- LATIN SMALL LETTER H WITH CEDILLA+ $ Entry 'K' '\'' '\x1E30' -- LATIN CAPITAL LETTER K WITH ACUTE+ $ Entry 'k' '\'' '\x1E31' -- LATIN SMALL LETTER K WITH ACUTE+ $ Entry 'K' '_' '\x1E34' -- LATIN CAPITAL LETTER K WITH LINE BELOW+ $ Entry 'k' '_' '\x1E35' -- LATIN SMALL LETTER K WITH LINE BELOW+ $ Entry 'L' '_' '\x1E3A' -- LATIN CAPITAL LETTER L WITH LINE BELOW+ $ Entry 'l' '_' '\x1E3B' -- LATIN SMALL LETTER L WITH LINE BELOW+ $ Entry 'M' '\'' '\x1E3E' -- LATIN CAPITAL LETTER M WITH ACUTE+ $ Entry 'm' '\'' '\x1E3F' -- LATIN SMALL LETTER M WITH ACUTE+ $ Entry 'M' '.' '\x1E40' -- LATIN CAPITAL LETTER M WITH DOT ABOVE+ $ Entry 'm' '.' '\x1E41' -- LATIN SMALL LETTER M WITH DOT ABOVE+ $ Entry 'N' '.' '\x1E44' -- LATIN CAPITAL LETTER N WITH DOT ABOVE `+ $ Entry 'n' '.' '\x1E45' -- LATIN SMALL LETTER N WITH DOT ABOVE `+ $ Entry 'N' '_' '\x1E48' -- LATIN CAPITAL LETTER N WITH LINE BELOW `+ $ Entry 'n' '_' '\x1E49' -- LATIN SMALL LETTER N WITH LINE BELOW `+ $ Entry 'P' '\'' '\x1E54' -- LATIN CAPITAL LETTER P WITH ACUTE+ $ Entry 'p' '\'' '\x1E55' -- LATIN SMALL LETTER P WITH ACUTE+ $ Entry 'P' '.' '\x1E56' -- LATIN CAPITAL LETTER P WITH DOT ABOVE+ $ Entry 'p' '.' '\x1E57' -- LATIN SMALL LETTER P WITH DOT ABOVE+ $ Entry 'R' '.' '\x1E58' -- LATIN CAPITAL LETTER R WITH DOT ABOVE+ $ Entry 'r' '.' '\x1E59' -- LATIN SMALL LETTER R WITH DOT ABOVE+ $ Entry 'R' '_' '\x1E5E' -- LATIN CAPITAL LETTER R WITH LINE BELOW+ $ Entry 'r' '_' '\x1E5F' -- LATIN SMALL LETTER R WITH LINE BELOW+ $ Entry 'S' '.' '\x1E60' -- LATIN CAPITAL LETTER S WITH DOT ABOVE+ $ Entry 's' '.' '\x1E61' -- LATIN SMALL LETTER S WITH DOT ABOVE+ $ Entry 'T' '.' '\x1E6A' -- LATIN CAPITAL LETTER T WITH DOT ABOVE+ $ Entry 't' '.' '\x1E6B' -- LATIN SMALL LETTER T WITH DOT ABOVE+ $ Entry 'T' '_' '\x1E6E' -- LATIN CAPITAL LETTER T WITH LINE BELOW+ $ Entry 't' '_' '\x1E6F' -- LATIN SMALL LETTER T WITH LINE BELOW+ $ Entry 'V' '?' '\x1E7C' -- LATIN CAPITAL LETTER V WITH TILDE+ $ Entry 'v' '?' '\x1E7D' -- LATIN SMALL LETTER V WITH TILDE+ $ Entry 'W' '!' '\x1E80' -- LATIN CAPITAL LETTER W WITH GRAVE+ $ Entry 'w' '!' '\x1E81' -- LATIN SMALL LETTER W WITH GRAVE+ $ Entry 'W' '\'' '\x1E82' -- LATIN CAPITAL LETTER W WITH ACUTE+ $ Entry 'w' '\'' '\x1E83' -- LATIN SMALL LETTER W WITH ACUTE+ $ Entry 'W' ':' '\x1E84' -- LATIN CAPITAL LETTER W WITH DIAERESIS+ $ Entry 'w' ':' '\x1E85' -- LATIN SMALL LETTER W WITH DIAERESIS+ $ Entry 'W' '.' '\x1E86' -- LATIN CAPITAL LETTER W WITH DOT ABOVE+ $ Entry 'w' '.' '\x1E87' -- LATIN SMALL LETTER W WITH DOT ABOVE+ $ Entry 'X' '.' '\x1E8A' -- LATIN CAPITAL LETTER X WITH DOT ABOVE+ $ Entry 'x' '.' '\x1E8B' -- LATIN SMALL LETTER X WITH DOT ABOVE+ $ Entry 'X' ':' '\x1E8C' -- LATIN CAPITAL LETTER X WITH DIAERESIS+ $ Entry 'x' ':' '\x1E8D' -- LATIN SMALL LETTER X WITH DIAERESIS+ $ Entry 'Y' '.' '\x1E8E' -- LATIN CAPITAL LETTER Y WITH DOT ABOVE+ $ Entry 'y' '.' '\x1E8F' -- LATIN SMALL LETTER Y WITH DOT ABOVE+ $ Entry 'Z' '>' '\x1E90' -- LATIN CAPITAL LETTER Z WITH CIRCUMFLEX+ $ Entry 'z' '>' '\x1E91' -- LATIN SMALL LETTER Z WITH CIRCUMFLEX+ $ Entry 'Z' '_' '\x1E94' -- LATIN CAPITAL LETTER Z WITH LINE BELOW+ $ Entry 'z' '_' '\x1E95' -- LATIN SMALL LETTER Z WITH LINE BELOW+ $ Entry 'h' '_' '\x1E96' -- LATIN SMALL LETTER H WITH LINE BELOW+ $ Entry 't' ':' '\x1E97' -- LATIN SMALL LETTER T WITH DIAERESIS+ $ Entry 'w' '0' '\x1E98' -- LATIN SMALL LETTER W WITH RING ABOVE+ $ Entry 'y' '0' '\x1E99' -- LATIN SMALL LETTER Y WITH RING ABOVE+ $ Entry 'A' '2' '\x1EA2' -- LATIN CAPITAL LETTER A WITH HOOK ABOVE+ $ Entry 'a' '2' '\x1EA3' -- LATIN SMALL LETTER A WITH HOOK ABOVE+ $ Entry 'E' '2' '\x1EBA' -- LATIN CAPITAL LETTER E WITH HOOK ABOVE+ $ Entry 'e' '2' '\x1EBB' -- LATIN SMALL LETTER E WITH HOOK ABOVE+ $ Entry 'E' '?' '\x1EBC' -- LATIN CAPITAL LETTER E WITH TILDE+ $ Entry 'e' '?' '\x1EBD' -- LATIN SMALL LETTER E WITH TILDE+ $ Entry 'I' '2' '\x1EC8' -- LATIN CAPITAL LETTER I WITH HOOK ABOVE+ $ Entry 'i' '2' '\x1EC9' -- LATIN SMALL LETTER I WITH HOOK ABOVE+ $ Entry 'O' '2' '\x1ECE' -- LATIN CAPITAL LETTER O WITH HOOK ABOVE+ $ Entry 'o' '2' '\x1ECF' -- LATIN SMALL LETTER O WITH HOOK ABOVE+ $ Entry 'U' '2' '\x1EE6' -- LATIN CAPITAL LETTER U WITH HOOK ABOVE+ $ Entry 'u' '2' '\x1EE7' -- LATIN SMALL LETTER U WITH HOOK ABOVE+ $ Entry 'Y' '!' '\x1EF2' -- LATIN CAPITAL LETTER Y WITH GRAVE+ $ Entry 'y' '!' '\x1EF3' -- LATIN SMALL LETTER Y WITH GRAVE+ $ Entry 'Y' '2' '\x1EF6' -- LATIN CAPITAL LETTER Y WITH HOOK ABOVE+ $ Entry 'y' '2' '\x1EF7' -- LATIN SMALL LETTER Y WITH HOOK ABOVE+ $ Entry 'Y' '?' '\x1EF8' -- LATIN CAPITAL LETTER Y WITH TILDE+ $ Entry 'y' '?' '\x1EF9' -- LATIN SMALL LETTER Y WITH TILDE+ $ Entry ';' '\'' '\x1F00' -- GREEK SMALL LETTER ALPHA WITH PSILI+ $ Entry ',' '\'' '\x1F01' -- GREEK SMALL LETTER ALPHA WITH DASIA+ $ Entry ';' '!' '\x1F02' -- GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA+ $ Entry ',' '!' '\x1F03' -- GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA+ $ Entry '?' ';' '\x1F04' -- GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA+ $ Entry '?' ',' '\x1F05' -- GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA+ $ Entry '!' ':' '\x1F06' -- GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI+ $ Entry '?' ':' '\x1F07' -- GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI+ $ Entry '1' 'N' '\x2002' -- EN SPACE+ $ Entry '1' 'M' '\x2003' -- EM SPACE+ $ Entry '3' 'M' '\x2004' -- THREE-PER-EM SPACE+ $ Entry '4' 'M' '\x2005' -- FOUR-PER-EM SPACE+ $ Entry '6' 'M' '\x2006' -- SIX-PER-EM SPACE+ $ Entry '1' 'T' '\x2009' -- THIN SPACE+ $ Entry '1' 'H' '\x200A' -- HAIR SPACE+ $ Entry '-' '1' '\x2010' -- HYPHEN+ $ Entry '-' 'N' '\x2013' -- EN DASH `+ $ Entry '-' 'M' '\x2014' -- EM DASH+ $ Entry '-' '3' '\x2015' -- HORIZONTAL BAR+ $ Entry '!' '2' '\x2016' -- DOUBLE VERTICAL LINE+ $ Entry '=' '2' '\x2017' -- DOUBLE LOW LINE+ $ Entry '\'' '6' '\x2018' -- LEFT SINGLE QUOTATION MARK+ $ Entry '\'' '9' '\x2019' -- RIGHT SINGLE QUOTATION MARK+ $ Entry '.' '9' '\x201A' -- SINGLE LOW-9 QUOTATION MARK+ $ Entry '9' '\'' '\x201B' -- SINGLE HIGH-REVERSED-9 QUOTATION MARK+ $ Entry '"' '6' '\x201C' -- LEFT DOUBLE QUOTATION MARK+ $ Entry '"' '9' '\x201D' -- RIGHT DOUBLE QUOTATION MARK+ $ Entry ':' '9' '\x201E' -- DOUBLE LOW-9 QUOTATION MARK+ $ Entry '9' '"' '\x201F' -- DOUBLE HIGH-REVERSED-9 QUOTATION MARK+ $ Entry '/' '-' '\x2020' -- DAGGER+ $ Entry '/' '=' '\x2021' -- DOUBLE DAGGER+ $ Entry '.' '.' '\x2025' -- TWO DOT LEADER+ $ Entry '%' '0' '\x2030' -- PER MILLE SIGN+ $ Entry '1' '\'' '\x2032' -- PRIME+ $ Entry '2' '\'' '\x2033' -- DOUBLE PRIME+ $ Entry '3' '\'' '\x2034' -- TRIPLE PRIME+ $ Entry '1' '"' '\x2035' -- REVERSED PRIME+ $ Entry '2' '"' '\x2036' -- REVERSED DOUBLE PRIME+ $ Entry '3' '"' '\x2037' -- REVERSED TRIPLE PRIME+ $ Entry 'C' 'a' '\x2038' -- CARET+ $ Entry '<' '1' '\x2039' -- SINGLE LEFT-POINTING ANGLE QUOTATION MARK+ $ Entry '>' '1' '\x203A' -- SINGLE RIGHT-POINTING ANGLE QUOTATION MARK+ $ Entry ':' 'X' '\x203B' -- REFERENCE MARK+ $ Entry '\'' '-' '\x203E' -- OVERLINE+ $ Entry '/' 'f' '\x2044' -- FRACTION SLASH+ $ Entry '0' 'S' '\x2070' -- SUPERSCRIPT ZERO+ $ Entry '4' 'S' '\x2074' -- SUPERSCRIPT FOUR+ $ Entry '5' 'S' '\x2075' -- SUPERSCRIPT FIVE+ $ Entry '6' 'S' '\x2076' -- SUPERSCRIPT SIX+ $ Entry '7' 'S' '\x2077' -- SUPERSCRIPT SEVEN+ $ Entry '8' 'S' '\x2078' -- SUPERSCRIPT EIGHT+ $ Entry '9' 'S' '\x2079' -- SUPERSCRIPT NINE+ $ Entry '+' 'S' '\x207A' -- SUPERSCRIPT PLUS SIGN+ $ Entry '-' 'S' '\x207B' -- SUPERSCRIPT MINUS+ $ Entry '=' 'S' '\x207C' -- SUPERSCRIPT EQUALS SIGN+ $ Entry '(' 'S' '\x207D' -- SUPERSCRIPT LEFT PARENTHESIS+ $ Entry ')' 'S' '\x207E' -- SUPERSCRIPT RIGHT PARENTHESIS+ $ Entry 'n' 'S' '\x207F' -- SUPERSCRIPT LATIN SMALL LETTER N `+ $ Entry '0' 's' '\x2080' -- SUBSCRIPT ZERO+ $ Entry '1' 's' '\x2081' -- SUBSCRIPT ONE+ $ Entry '2' 's' '\x2082' -- SUBSCRIPT TWO+ $ Entry '3' 's' '\x2083' -- SUBSCRIPT THREE+ $ Entry '4' 's' '\x2084' -- SUBSCRIPT FOUR+ $ Entry '5' 's' '\x2085' -- SUBSCRIPT FIVE+ $ Entry '6' 's' '\x2086' -- SUBSCRIPT SIX+ $ Entry '7' 's' '\x2087' -- SUBSCRIPT SEVEN+ $ Entry '8' 's' '\x2088' -- SUBSCRIPT EIGHT+ $ Entry '9' 's' '\x2089' -- SUBSCRIPT NINE+ $ Entry '+' 's' '\x208A' -- SUBSCRIPT PLUS SIGN+ $ Entry '-' 's' '\x208B' -- SUBSCRIPT MINUS+ $ Entry '=' 's' '\x208C' -- SUBSCRIPT EQUALS SIGN+ $ Entry '(' 's' '\x208D' -- SUBSCRIPT LEFT PARENTHESIS+ $ Entry ')' 's' '\x208E' -- SUBSCRIPT RIGHT PARENTHESIS+ $ Entry 'L' 'i' '\x20A4' -- LIRA SIGN+ $ Entry 'P' 't' '\x20A7' -- PESETA SIGN+ $ Entry 'W' '=' '\x20A9' -- WON SIGN+ $ Entry 'E' 'u' '\x20AC' -- EURO SIGN+ $ Entry 'o' 'C' '\x2103' -- DEGREE CELSIUS+ $ Entry 'c' 'o' '\x2105' -- CARE OF+ $ Entry 'o' 'F' '\x2109' -- DEGREE FAHRENHEIT+ $ Entry 'N' '0' '\x2116' -- NUMERO SIGN+ $ Entry 'P' 'O' '\x2117' -- SOUND RECORDING COPYRIGHT+ $ Entry 'R' 'x' '\x211E' -- PRESCRIPTION TAKE+ $ Entry 'S' 'M' '\x2120' -- SERVICE MARK+ $ Entry 'T' 'M' '\x2122' -- TRADE MARK SIGN+ $ Entry 'O' 'm' '\x2126' -- OHM SIGN+ $ Entry 'A' 'O' '\x212B' -- ANGSTROM SIGN+ $ Entry '1' '3' '\x2153' -- VULGAR FRACTION ONE THIRD+ $ Entry '2' '3' '\x2154' -- VULGAR FRACTION TWO THIRDS+ $ Entry '1' '5' '\x2155' -- VULGAR FRACTION ONE FIFTH+ $ Entry '2' '5' '\x2156' -- VULGAR FRACTION TWO FIFTHS+ $ Entry '3' '5' '\x2157' -- VULGAR FRACTION THREE FIFTHS+ $ Entry '4' '5' '\x2158' -- VULGAR FRACTION FOUR FIFTHS+ $ Entry '1' '6' '\x2159' -- VULGAR FRACTION ONE SIXTH+ $ Entry '5' '6' '\x215A' -- VULGAR FRACTION FIVE SIXTHS+ $ Entry '1' '8' '\x215B' -- VULGAR FRACTION ONE EIGHTH+ $ Entry '3' '8' '\x215C' -- VULGAR FRACTION THREE EIGHTHS+ $ Entry '5' '8' '\x215D' -- VULGAR FRACTION FIVE EIGHTHS+ $ Entry '7' '8' '\x215E' -- VULGAR FRACTION SEVEN EIGHTHS+ $ Entry '1' 'R' '\x2160' -- ROMAN NUMERAL ONE+ $ Entry '2' 'R' '\x2161' -- ROMAN NUMERAL TWO+ $ Entry '3' 'R' '\x2162' -- ROMAN NUMERAL THREE+ $ Entry '4' 'R' '\x2163' -- ROMAN NUMERAL FOUR+ $ Entry '5' 'R' '\x2164' -- ROMAN NUMERAL FIVE+ $ Entry '6' 'R' '\x2165' -- ROMAN NUMERAL SIX+ $ Entry '7' 'R' '\x2166' -- ROMAN NUMERAL SEVEN+ $ Entry '8' 'R' '\x2167' -- ROMAN NUMERAL EIGHT+ $ Entry '9' 'R' '\x2168' -- ROMAN NUMERAL NINE+ $ Entry 'a' 'R' '\x2169' -- ROMAN NUMERAL TEN+ $ Entry 'b' 'R' '\x216A' -- ROMAN NUMERAL ELEVEN+ $ Entry 'c' 'R' '\x216B' -- ROMAN NUMERAL TWELVE+ $ Entry '1' 'r' '\x2170' -- SMALL ROMAN NUMERAL ONE+ $ Entry '2' 'r' '\x2171' -- SMALL ROMAN NUMERAL TWO+ $ Entry '3' 'r' '\x2172' -- SMALL ROMAN NUMERAL THREE+ $ Entry '4' 'r' '\x2173' -- SMALL ROMAN NUMERAL FOUR+ $ Entry '5' 'r' '\x2174' -- SMALL ROMAN NUMERAL FIVE+ $ Entry '6' 'r' '\x2175' -- SMALL ROMAN NUMERAL SIX+ $ Entry '7' 'r' '\x2176' -- SMALL ROMAN NUMERAL SEVEN+ $ Entry '8' 'r' '\x2177' -- SMALL ROMAN NUMERAL EIGHT+ $ Entry '9' 'r' '\x2178' -- SMALL ROMAN NUMERAL NINE+ $ Entry 'a' 'r' '\x2179' -- SMALL ROMAN NUMERAL TEN+ $ Entry 'b' 'r' '\x217A' -- SMALL ROMAN NUMERAL ELEVEN+ $ Entry 'c' 'r' '\x217B' -- SMALL ROMAN NUMERAL TWELVE+ $ Entry '<' '-' '\x2190' -- LEFTWARDS ARROW+ $ Entry '-' '!' '\x2191' -- UPWARDS ARROW+ $ Entry '-' '>' '\x2192' -- RIGHTWARDS ARROW+ $ Entry '-' 'v' '\x2193' -- DOWNWARDS ARROW+ $ Entry '<' '>' '\x2194' -- LEFT RIGHT ARROW+ $ Entry 'U' 'D' '\x2195' -- UP DOWN ARROW+ $ Entry '<' '=' '\x21D0' -- LEFTWARDS DOUBLE ARROW+ $ Entry '=' '>' '\x21D2' -- RIGHTWARDS DOUBLE ARROW+ $ Entry '=' '=' '\x21D4' -- LEFT RIGHT DOUBLE ARROW+ $ Entry 'F' 'A' '\x2200' -- FOR ALL+ $ Entry 'd' 'P' '\x2202' -- PARTIAL DIFFERENTIAL+ $ Entry 'T' 'E' '\x2203' -- THERE EXISTS+ $ Entry '/' '0' '\x2205' -- EMPTY SET+ $ Entry 'D' 'E' '\x2206' -- INCREMENT+ $ Entry 'N' 'B' '\x2207' -- NABLA+ $ Entry '(' '-' '\x2208' -- ELEMENT OF+ $ Entry '-' ')' '\x220B' -- CONTAINS AS MEMBER+ $ Entry '*' 'P' '\x220F' -- N-ARY PRODUCT `+ $ Entry '+' 'Z' '\x2211' -- N-ARY SUMMATION `+ $ Entry '-' '2' '\x2212' -- MINUS SIGN+ $ Entry '-' '+' '\x2213' -- MINUS-OR-PLUS SIGN+ $ Entry '*' '-' '\x2217' -- ASTERISK OPERATOR+ $ Entry 'O' 'b' '\x2218' -- RING OPERATOR+ $ Entry 'S' 'b' '\x2219' -- BULLET OPERATOR+ $ Entry 'R' 'T' '\x221A' -- SQUARE ROOT+ $ Entry '0' '(' '\x221D' -- PROPORTIONAL TO+ $ Entry '0' '0' '\x221E' -- INFINITY+ $ Entry '-' 'L' '\x221F' -- RIGHT ANGLE+ $ Entry '-' 'V' '\x2220' -- ANGLE+ $ Entry 'P' 'P' '\x2225' -- PARALLEL TO+ $ Entry 'A' 'N' '\x2227' -- LOGICAL AND+ $ Entry 'O' 'R' '\x2228' -- LOGICAL OR+ $ Entry '(' 'U' '\x2229' -- INTERSECTION+ $ Entry ')' 'U' '\x222A' -- UNION+ $ Entry 'I' 'n' '\x222B' -- INTEGRAL+ $ Entry 'D' 'I' '\x222C' -- DOUBLE INTEGRAL+ $ Entry 'I' 'o' '\x222E' -- CONTOUR INTEGRAL+ $ Entry '.' ':' '\x2234' -- THEREFORE+ $ Entry ':' '.' '\x2235' -- BECAUSE+ $ Entry ':' 'R' '\x2236' -- RATIO+ $ Entry ':' ':' '\x2237' -- PROPORTION+ $ Entry '?' '1' '\x223C' -- TILDE OPERATOR+ $ Entry 'C' 'G' '\x223E' -- INVERTED LAZY S+ $ Entry '?' '-' '\x2243' -- ASYMPTOTICALLY EQUAL TO+ $ Entry '?' '=' '\x2245' -- APPROXIMATELY EQUAL TO+ $ Entry '?' '2' '\x2248' -- ALMOST EQUAL TO+ $ Entry '=' '?' '\x224C' -- ALL EQUAL TO+ $ Entry 'H' 'I' '\x2253' -- IMAGE OF OR APPROXIMATELY EQUAL TO+ $ Entry '!' '=' '\x2260' -- NOT EQUAL TO+ $ Entry '=' '3' '\x2261' -- IDENTICAL TO+ $ Entry '=' '<' '\x2264' -- LESS-THAN OR EQUAL TO+ $ Entry '>' '=' '\x2265' -- GREATER-THAN OR EQUAL TO+ $ Entry '<' '*' '\x226A' -- MUCH LESS-THAN+ $ Entry '*' '>' '\x226B' -- MUCH GREATER-THAN+ $ Entry '!' '<' '\x226E' -- NOT LESS-THAN+ $ Entry '!' '>' '\x226F' -- NOT GREATER-THAN+ $ Entry '(' 'C' '\x2282' -- SUBSET OF+ $ Entry ')' 'C' '\x2283' -- SUPERSET OF+ $ Entry '(' '_' '\x2286' -- SUBSET OF OR EQUAL TO+ $ Entry ')' '_' '\x2287' -- SUPERSET OF OR EQUAL TO+ $ Entry '0' '.' '\x2299' -- CIRCLED DOT OPERATOR+ $ Entry '0' '2' '\x229A' -- CIRCLED RING OPERATOR+ $ Entry '-' 'T' '\x22A5' -- UP TACK+ $ Entry '.' 'P' '\x22C5' -- DOT OPERATOR+ $ Entry ':' '3' '\x22EE' -- VERTICAL ELLIPSIS+ $ Entry '.' '3' '\x22EF' -- MIDLINE HORIZONTAL ELLIPSIS+ $ Entry 'E' 'h' '\x2302' -- HOUSE+ $ Entry '<' '7' '\x2308' -- LEFT CEILING+ $ Entry '>' '7' '\x2309' -- RIGHT CEILING+ $ Entry '7' '<' '\x230A' -- LEFT FLOOR+ $ Entry '7' '>' '\x230B' -- RIGHT FLOOR+ $ Entry 'N' 'I' '\x2310' -- REVERSED NOT SIGN+ $ Entry '(' 'A' '\x2312' -- ARC+ $ Entry 'T' 'R' '\x2315' -- TELEPHONE RECORDER+ $ Entry 'I' 'u' '\x2320' -- TOP HALF INTEGRAL+ $ Entry 'I' 'l' '\x2321' -- BOTTOM HALF INTEGRAL+ $ Entry '<' '/' '\x2329' -- LEFT-POINTING ANGLE BRACKET+ $ Entry '/' '>' '\x232A' -- RIGHT-POINTING ANGLE BRACKET+ $ Entry 'V' 's' '\x2423' -- OPEN BOX+ $ Entry '1' 'h' '\x2440' -- OCR HOOK+ $ Entry '3' 'h' '\x2441' -- OCR CHAIR+ $ Entry '2' 'h' '\x2442' -- OCR FORK+ $ Entry '4' 'h' '\x2443' -- OCR INVERTED FORK+ $ Entry '1' 'j' '\x2446' -- OCR BRANCH BANK IDENTIFICATION+ $ Entry '2' 'j' '\x2447' -- OCR AMOUNT OF CHECK+ $ Entry '3' 'j' '\x2448' -- OCR DASH+ $ Entry '4' 'j' '\x2449' -- OCR CUSTOMER ACCOUNT NUMBER+ $ Entry '1' '.' '\x2488' -- DIGIT ONE FULL STOP+ $ Entry '2' '.' '\x2489' -- DIGIT TWO FULL STOP+ $ Entry '3' '.' '\x248A' -- DIGIT THREE FULL STOP+ $ Entry '4' '.' '\x248B' -- DIGIT FOUR FULL STOP+ $ Entry '5' '.' '\x248C' -- DIGIT FIVE FULL STOP+ $ Entry '6' '.' '\x248D' -- DIGIT SIX FULL STOP+ $ Entry '7' '.' '\x248E' -- DIGIT SEVEN FULL STOP+ $ Entry '8' '.' '\x248F' -- DIGIT EIGHT FULL STOP+ $ Entry '9' '.' '\x2490' -- DIGIT NINE FULL STOP+ $ Entry 'h' 'h' '\x2500' -- BOX DRAWINGS LIGHT HORIZONTAL+ $ Entry 'H' 'H' '\x2501' -- BOX DRAWINGS HEAVY HORIZONTAL+ $ Entry 'v' 'v' '\x2502' -- BOX DRAWINGS LIGHT VERTICAL+ $ Entry 'V' 'V' '\x2503' -- BOX DRAWINGS HEAVY VERTICAL+ $ Entry '3' '-' '\x2504' -- BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL+ $ Entry '3' '_' '\x2505' -- BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL+ $ Entry '3' '!' '\x2506' -- BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL+ $ Entry '3' '/' '\x2507' -- BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL+ $ Entry '4' '-' '\x2508' -- BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL+ $ Entry '4' '_' '\x2509' -- BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL+ $ Entry '4' '!' '\x250A' -- BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL+ $ Entry '4' '/' '\x250B' -- BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL+ $ Entry 'd' 'r' '\x250C' -- BOX DRAWINGS LIGHT DOWN AND RIGHT+ $ Entry 'd' 'R' '\x250D' -- BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY+ $ Entry 'D' 'r' '\x250E' -- BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT+ $ Entry 'D' 'R' '\x250F' -- BOX DRAWINGS HEAVY DOWN AND RIGHT+ $ Entry 'd' 'l' '\x2510' -- BOX DRAWINGS LIGHT DOWN AND LEFT+ $ Entry 'd' 'L' '\x2511' -- BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY+ $ Entry 'D' 'l' '\x2512' -- BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT+ $ Entry 'L' 'D' '\x2513' -- BOX DRAWINGS HEAVY DOWN AND LEFT+ $ Entry 'u' 'r' '\x2514' -- BOX DRAWINGS LIGHT UP AND RIGHT+ $ Entry 'u' 'R' '\x2515' -- BOX DRAWINGS UP LIGHT AND RIGHT HEAVY+ $ Entry 'U' 'r' '\x2516' -- BOX DRAWINGS UP HEAVY AND RIGHT LIGHT+ $ Entry 'U' 'R' '\x2517' -- BOX DRAWINGS HEAVY UP AND RIGHT+ $ Entry 'u' 'l' '\x2518' -- BOX VOICED SOUND MARKDRAWINGS LIGHT UP AND LEFT+ $ Entry 'u' 'L' '\x2519' -- BOX DRAWINGS UP LIGHT AND LEFT HEAVY+ $ Entry 'U' 'l' '\x251A' -- BOX DRAWINGS UP HEAVY AND LEFT LIGHT+ $ Entry 'U' 'L' '\x251B' -- BOX DRAWINGS HEAVY UP AND LEFT+ $ Entry 'v' 'r' '\x251C' -- BOX DRAWINGS LIGHT VERTICAL AND RIGHT+ $ Entry 'v' 'R' '\x251D' -- BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY+ $ Entry 'V' 'r' '\x2520' -- BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT+ $ Entry 'V' 'R' '\x2523' -- BOX DRAWINGS HEAVY VERTICAL AND RIGHT+ $ Entry 'v' 'l' '\x2524' -- BOX DRAWINGS LIGHT VERTICAL AND LEFT+ $ Entry 'v' 'L' '\x2525' -- BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY+ $ Entry 'V' 'l' '\x2528' -- BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT+ $ Entry 'V' 'L' '\x252B' -- BOX DRAWINGS HEAVY VERTICAL AND LEFT+ $ Entry 'd' 'h' '\x252C' -- BOX DRAWINGS LIGHT DOWN AND HORIZONTAL+ $ Entry 'd' 'H' '\x252F' -- BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY+ $ Entry 'D' 'h' '\x2530' -- BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT+ $ Entry 'D' 'H' '\x2533' -- BOX DRAWINGS HEAVY DOWN AND HORIZONTAL+ $ Entry 'u' 'h' '\x2534' -- BOX DRAWINGS LIGHT UP AND HORIZONTAL+ $ Entry 'u' 'H' '\x2537' -- BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY+ $ Entry 'U' 'h' '\x2538' -- BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT+ $ Entry 'U' 'H' '\x253B' -- BOX DRAWINGS HEAVY UP AND HORIZONTAL+ $ Entry 'v' 'h' '\x253C' -- BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL+ $ Entry 'v' 'H' '\x253F' -- BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY+ $ Entry 'V' 'h' '\x2542' -- BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT+ $ Entry 'V' 'H' '\x254B' -- BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL+ $ Entry 'F' 'D' '\x2571' -- BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT+ $ Entry 'B' 'D' '\x2572' -- BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT+ $ Entry 'T' 'B' '\x2580' -- UPPER HALF BLOCK+ $ Entry 'L' 'B' '\x2584' -- LOWER HALF BLOCK+ $ Entry 'F' 'B' '\x2588' -- FULL BLOCK+ $ Entry 'l' 'B' '\x258C' -- LEFT HALF BLOCK+ $ Entry 'R' 'B' '\x2590' -- RIGHT HALF BLOCK+ $ Entry '.' 'S' '\x2591' -- LIGHT SHADE+ $ Entry ':' 'S' '\x2592' -- MEDIUM SHADE+ $ Entry '?' 'S' '\x2593' -- DARK SHADE+ $ Entry 'f' 'S' '\x25A0' -- BLACK SQUARE+ $ Entry 'O' 'S' '\x25A1' -- WHITE SQUARE+ $ Entry 'R' 'O' '\x25A2' -- WHITE SQUARE WITH ROUNDED CORNERS+ $ Entry 'R' 'r' '\x25A3' -- WHITE SQUARE CONTAINING BLACK SMALL SQUARE+ $ Entry 'R' 'F' '\x25A4' -- SQUARE WITH HORIZONTAL FILL+ $ Entry 'R' 'Y' '\x25A5' -- SQUARE WITH VERTICAL FILL+ $ Entry 'R' 'H' '\x25A6' -- SQUARE WITH ORTHOGONAL CROSSHATCH FILL+ $ Entry 'R' 'Z' '\x25A7' -- SQUARE WITH UPPER LEFT TO LOWER RIGHT FILL+ $ Entry 'R' 'K' '\x25A8' -- SQUARE WITH UPPER RIGHT TO LOWER LEFT FILL+ $ Entry 'R' 'X' '\x25A9' -- SQUARE WITH DIAGONAL CROSSHATCH FILL+ $ Entry 's' 'B' '\x25AA' -- BLACK SMALL SQUARE+ $ Entry 'S' 'R' '\x25AC' -- BLACK RECTANGLE+ $ Entry 'O' 'r' '\x25AD' -- WHITE RECTANGLE+ $ Entry 'U' 'T' '\x25B2' -- BLACK UP-POINTING TRIANGLE+ $ Entry 'u' 'T' '\x25B3' -- WHITE UP-POINTING TRIANGLE+ $ Entry 'P' 'R' '\x25B6' -- BLACK RIGHT-POINTING TRIANGLE+ $ Entry 'T' 'r' '\x25B7' -- WHITE RIGHT-POINTING TRIANGLE+ $ Entry 'D' 't' '\x25BC' -- BLACK DOWN-POINTING TRIANGLE+ $ Entry 'd' 'T' '\x25BD' -- WHITE DOWN-POINTING TRIANGLE+ $ Entry 'P' 'L' '\x25C0' -- BLACK LEFT-POINTING TRIANGLE+ $ Entry 'T' 'l' '\x25C1' -- WHITE LEFT-POINTING TRIANGLE+ $ Entry 'D' 'b' '\x25C6' -- BLACK DIAMOND+ $ Entry 'D' 'w' '\x25C7' -- WHITE DIAMOND+ $ Entry 'L' 'Z' '\x25CA' -- LOZENGE+ $ Entry '0' 'm' '\x25CB' -- WHITE CIRCLE+ $ Entry '0' 'o' '\x25CE' -- BULLSEYE+ $ Entry '0' 'M' '\x25CF' -- BLACK CIRCLE+ $ Entry '0' 'L' '\x25D0' -- CIRCLE WITH LEFT HALF BLACK+ $ Entry '0' 'R' '\x25D1' -- CIRCLE WITH RIGHT HALF BLACK+ $ Entry 'S' 'n' '\x25D8' -- INVERSE BULLET+ $ Entry 'I' 'c' '\x25D9' -- INVERSE WHITE CIRCLE+ $ Entry 'F' 'd' '\x25E2' -- BLACK LOWER RIGHT TRIANGLE+ $ Entry 'B' 'd' '\x25E3' -- BLACK LOWER LEFT TRIANGLE+ $ Entry '*' '2' '\x2605' -- BLACK STAR+ $ Entry '*' '1' '\x2606' -- WHITE STAR+ $ Entry '<' 'H' '\x261C' -- WHITE LEFT POINTING INDEX+ $ Entry '>' 'H' '\x261E' -- WHITE RIGHT POINTING INDEX+ $ Entry '0' 'u' '\x263A' -- WHITE SMILING FACE+ $ Entry '0' 'U' '\x263B' -- BLACK SMILING FACE+ $ Entry 'S' 'U' '\x263C' -- WHITE SUN WITH RAYS+ $ Entry 'F' 'm' '\x2640' -- FEMALE SIGN+ $ Entry 'M' 'l' '\x2642' -- MALE SIGN+ $ Entry 'c' 'S' '\x2660' -- BLACK SPADE SUIT+ $ Entry 'c' 'H' '\x2661' -- WHITE HEART SUIT+ $ Entry 'c' 'D' '\x2662' -- WHITE DIAMOND SUIT+ $ Entry 'c' 'C' '\x2663' -- BLACK CLUB SUIT+ $ Entry 'M' 'd' '\x2669' -- QUARTER NOTE `+ $ Entry 'M' '8' '\x266A' -- EIGHTH NOTE `+ $ Entry 'M' '2' '\x266B' -- BEAMED EIGHTH NOTES+ $ Entry 'M' 'b' '\x266D' -- MUSIC FLAT SIGN+ $ Entry 'M' 'x' '\x266E' -- MUSIC NATURAL SIGN+ $ Entry 'M' 'X' '\x266F' -- MUSIC SHARP SIGN+ $ Entry 'O' 'K' '\x2713' -- CHECK MARK+ $ Entry 'X' 'X' '\x2717' -- BALLOT X+ $ Entry '-' 'X' '\x2720' -- MALTESE CROSS+ $ Entry 'I' 'S' '\x3000' -- IDEOGRAPHIC SPACE+ $ Entry ',' '_' '\x3001' -- IDEOGRAPHIC COMMA+ $ Entry '.' '_' '\x3002' -- IDEOGRAPHIC FULL STOP+ $ Entry '+' '"' '\x3003' -- DITTO MARK+ $ Entry '+' '_' '\x3004' -- JAPANESE INDUSTRIAL STANDARD SYMBOL+ $ Entry '*' '_' '\x3005' -- IDEOGRAPHIC ITERATION MARK+ $ Entry ';' '_' '\x3006' -- IDEOGRAPHIC CLOSING MARK+ $ Entry '0' '_' '\x3007' -- IDEOGRAPHIC NUMBER ZERO+ $ Entry '<' '+' '\x300A' -- LEFT DOUBLE ANGLE BRACKET+ $ Entry '>' '+' '\x300B' -- RIGHT DOUBLE ANGLE BRACKET+ $ Entry '<' '\'' '\x300C' -- LEFT CORNER BRACKET+ $ Entry '>' '\'' '\x300D' -- RIGHT CORNER BRACKET+ $ Entry '<' '"' '\x300E' -- LEFT WHITE CORNER BRACKET+ $ Entry '>' '"' '\x300F' -- RIGHT WHITE CORNER BRACKET+ $ Entry '(' '"' '\x3010' -- LEFT BLACK LENTICULAR BRACKET+ $ Entry ')' '"' '\x3011' -- RIGHT BLACK LENTICULAR BRACKET+ $ Entry '=' 'T' '\x3012' -- POSTAL MARK+ $ Entry '=' '_' '\x3013' -- GETA MARK+ $ Entry '(' '\'' '\x3014' -- LEFT TORTOISE SHELL BRACKET+ $ Entry ')' '\'' '\x3015' -- RIGHT TORTOISE SHELL BRACKET+ $ Entry '(' 'I' '\x3016' -- LEFT WHITE LENTICULAR BRACKET+ $ Entry ')' 'I' '\x3017' -- RIGHT WHITE LENTICULAR BRACKET+ $ Entry '-' '?' '\x301C' -- WAVE DASH+ $ Entry 'A' '5' '\x3041' -- HIRAGANA LETTER SMALL A+ $ Entry 'a' '5' '\x3042' -- HIRAGANA LETTER A+ $ Entry 'I' '5' '\x3043' -- HIRAGANA LETTER SMALL I+ $ Entry 'i' '5' '\x3044' -- HIRAGANA LETTER I+ $ Entry 'U' '5' '\x3045' -- HIRAGANA LETTER SMALL U+ $ Entry 'u' '5' '\x3046' -- HIRAGANA LETTER U+ $ Entry 'E' '5' '\x3047' -- HIRAGANA LETTER SMALL E+ $ Entry 'e' '5' '\x3048' -- HIRAGANA LETTER E+ $ Entry 'O' '5' '\x3049' -- HIRAGANA LETTER SMALL O+ $ Entry 'o' '5' '\x304A' -- HIRAGANA LETTER O+ $ Entry 'k' 'a' '\x304B' -- HIRAGANA LETTER KA+ $ Entry 'g' 'a' '\x304C' -- HIRAGANA LETTER GA+ $ Entry 'k' 'i' '\x304D' -- HIRAGANA LETTER KI+ $ Entry 'g' 'i' '\x304E' -- HIRAGANA LETTER GI+ $ Entry 'k' 'u' '\x304F' -- HIRAGANA LETTER KU+ $ Entry 'g' 'u' '\x3050' -- HIRAGANA LETTER GU+ $ Entry 'k' 'e' '\x3051' -- HIRAGANA LETTER KE+ $ Entry 'g' 'e' '\x3052' -- HIRAGANA LETTER GE+ $ Entry 'k' 'o' '\x3053' -- HIRAGANA LETTER KO+ $ Entry 'g' 'o' '\x3054' -- HIRAGANA LETTER GO+ $ Entry 's' 'a' '\x3055' -- HIRAGANA LETTER SA+ $ Entry 'z' 'a' '\x3056' -- HIRAGANA LETTER ZA+ $ Entry 's' 'i' '\x3057' -- HIRAGANA LETTER SI+ $ Entry 'z' 'i' '\x3058' -- HIRAGANA LETTER ZI+ $ Entry 's' 'u' '\x3059' -- HIRAGANA LETTER SU+ $ Entry 'z' 'u' '\x305A' -- HIRAGANA LETTER ZU+ $ Entry 's' 'e' '\x305B' -- HIRAGANA LETTER SE+ $ Entry 'z' 'e' '\x305C' -- HIRAGANA LETTER ZE+ $ Entry 's' 'o' '\x305D' -- HIRAGANA LETTER SO+ $ Entry 'z' 'o' '\x305E' -- HIRAGANA LETTER ZO+ $ Entry 't' 'a' '\x305F' -- HIRAGANA LETTER TA+ $ Entry 'd' 'a' '\x3060' -- HIRAGANA LETTER DA+ $ Entry 't' 'i' '\x3061' -- HIRAGANA LETTER TI+ $ Entry 'd' 'i' '\x3062' -- HIRAGANA LETTER DI+ $ Entry 't' 'U' '\x3063' -- HIRAGANA LETTER SMALL TU+ $ Entry 't' 'u' '\x3064' -- HIRAGANA LETTER TU+ $ Entry 'd' 'u' '\x3065' -- HIRAGANA LETTER DU+ $ Entry 't' 'e' '\x3066' -- HIRAGANA LETTER TE+ $ Entry 'd' 'e' '\x3067' -- HIRAGANA LETTER DE+ $ Entry 't' 'o' '\x3068' -- HIRAGANA LETTER TO+ $ Entry 'd' 'o' '\x3069' -- HIRAGANA LETTER DO+ $ Entry 'n' 'a' '\x306A' -- HIRAGANA LETTER NA+ $ Entry 'n' 'i' '\x306B' -- HIRAGANA LETTER NI+ $ Entry 'n' 'u' '\x306C' -- HIRAGANA LETTER NU+ $ Entry 'n' 'e' '\x306D' -- HIRAGANA LETTER NE+ $ Entry 'n' 'o' '\x306E' -- HIRAGANA LETTER NO+ $ Entry 'h' 'a' '\x306F' -- HIRAGANA LETTER HA+ $ Entry 'b' 'a' '\x3070' -- HIRAGANA LETTER BA+ $ Entry 'p' 'a' '\x3071' -- HIRAGANA LETTER PA+ $ Entry 'h' 'i' '\x3072' -- HIRAGANA LETTER HI+ $ Entry 'b' 'i' '\x3073' -- HIRAGANA LETTER BI+ $ Entry 'p' 'i' '\x3074' -- HIRAGANA LETTER PI+ $ Entry 'h' 'u' '\x3075' -- HIRAGANA LETTER HU+ $ Entry 'b' 'u' '\x3076' -- HIRAGANA LETTER BU+ $ Entry 'p' 'u' '\x3077' -- HIRAGANA LETTER PU+ $ Entry 'h' 'e' '\x3078' -- HIRAGANA LETTER HE+ $ Entry 'b' 'e' '\x3079' -- HIRAGANA LETTER BE+ $ Entry 'p' 'e' '\x307A' -- HIRAGANA LETTER PE+ $ Entry 'h' 'o' '\x307B' -- HIRAGANA LETTER HO+ $ Entry 'b' 'o' '\x307C' -- HIRAGANA LETTER BO+ $ Entry 'p' 'o' '\x307D' -- HIRAGANA LETTER PO+ $ Entry 'm' 'a' '\x307E' -- HIRAGANA LETTER MA+ $ Entry 'm' 'i' '\x307F' -- HIRAGANA LETTER MI+ $ Entry 'm' 'u' '\x3080' -- HIRAGANA LETTER MU+ $ Entry 'm' 'e' '\x3081' -- HIRAGANA LETTER ME+ $ Entry 'm' 'o' '\x3082' -- HIRAGANA LETTER MO+ $ Entry 'y' 'A' '\x3083' -- HIRAGANA LETTER SMALL YA+ $ Entry 'y' 'a' '\x3084' -- HIRAGANA LETTER YA+ $ Entry 'y' 'U' '\x3085' -- HIRAGANA LETTER SMALL YU+ $ Entry 'y' 'u' '\x3086' -- HIRAGANA LETTER YU+ $ Entry 'y' 'O' '\x3087' -- HIRAGANA LETTER SMALL YO+ $ Entry 'y' 'o' '\x3088' -- HIRAGANA LETTER YO+ $ Entry 'r' 'a' '\x3089' -- HIRAGANA LETTER RA+ $ Entry 'r' 'i' '\x308A' -- HIRAGANA LETTER RI+ $ Entry 'r' 'u' '\x308B' -- HIRAGANA LETTER RU+ $ Entry 'r' 'e' '\x308C' -- HIRAGANA LETTER RE+ $ Entry 'r' 'o' '\x308D' -- HIRAGANA LETTER RO+ $ Entry 'w' 'A' '\x308E' -- HIRAGANA LETTER SMALL WA+ $ Entry 'w' 'a' '\x308F' -- HIRAGANA LETTER WA+ $ Entry 'w' 'i' '\x3090' -- HIRAGANA LETTER WI+ $ Entry 'w' 'e' '\x3091' -- HIRAGANA LETTER WE+ $ Entry 'w' 'o' '\x3092' -- HIRAGANA LETTER WO+ $ Entry 'n' '5' '\x3093' -- HIRAGANA LETTER N `+ $ Entry 'v' 'u' '\x3094' -- HIRAGANA LETTER VU+ $ Entry '"' '5' '\x309B' -- KATAKANA-HIRAGANA VOICED SOUND MARK+ $ Entry '0' '5' '\x309C' -- KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK+ $ Entry '*' '5' '\x309D' -- HIRAGANA ITERATION MARK+ $ Entry '+' '5' '\x309E' -- HIRAGANA VOICED ITERATION MARK+ $ Entry 'a' '6' '\x30A1' -- KATAKANA LETTER SMALL A+ $ Entry 'A' '6' '\x30A2' -- KATAKANA LETTER A+ $ Entry 'i' '6' '\x30A3' -- KATAKANA LETTER SMALL I+ $ Entry 'I' '6' '\x30A4' -- KATAKANA LETTER I+ $ Entry 'u' '6' '\x30A5' -- KATAKANA LETTER SMALL U+ $ Entry 'U' '6' '\x30A6' -- KATAKANA LETTER U+ $ Entry 'e' '6' '\x30A7' -- KATAKANA LETTER SMALL E+ $ Entry 'E' '6' '\x30A8' -- KATAKANA LETTER E+ $ Entry 'o' '6' '\x30A9' -- KATAKANA LETTER SMALL O+ $ Entry 'O' '6' '\x30AA' -- KATAKANA LETTER O+ $ Entry 'K' 'a' '\x30AB' -- KATAKANA LETTER KA+ $ Entry 'G' 'a' '\x30AC' -- KATAKANA LETTER GA+ $ Entry 'K' 'i' '\x30AD' -- KATAKANA LETTER KI+ $ Entry 'G' 'i' '\x30AE' -- KATAKANA LETTER GI+ $ Entry 'K' 'u' '\x30AF' -- KATAKANA LETTER KU+ $ Entry 'G' 'u' '\x30B0' -- KATAKANA LETTER GU+ $ Entry 'K' 'e' '\x30B1' -- KATAKANA LETTER KE+ $ Entry 'G' 'e' '\x30B2' -- KATAKANA LETTER GE+ $ Entry 'K' 'o' '\x30B3' -- KATAKANA LETTER KO+ $ Entry 'G' 'o' '\x30B4' -- KATAKANA LETTER GO+ $ Entry 'S' 'a' '\x30B5' -- KATAKANA LETTER SA+ $ Entry 'Z' 'a' '\x30B6' -- KATAKANA LETTER ZA+ $ Entry 'S' 'i' '\x30B7' -- KATAKANA LETTER SI+ $ Entry 'Z' 'i' '\x30B8' -- KATAKANA LETTER ZI+ $ Entry 'S' 'u' '\x30B9' -- KATAKANA LETTER SU+ $ Entry 'Z' 'u' '\x30BA' -- KATAKANA LETTER ZU+ $ Entry 'S' 'e' '\x30BB' -- KATAKANA LETTER SE+ $ Entry 'Z' 'e' '\x30BC' -- KATAKANA LETTER ZE+ $ Entry 'S' 'o' '\x30BD' -- KATAKANA LETTER SO+ $ Entry 'Z' 'o' '\x30BE' -- KATAKANA LETTER ZO+ $ Entry 'T' 'a' '\x30BF' -- KATAKANA LETTER TA+ $ Entry 'D' 'a' '\x30C0' -- KATAKANA LETTER DA+ $ Entry 'T' 'i' '\x30C1' -- KATAKANA LETTER TI+ $ Entry 'D' 'i' '\x30C2' -- KATAKANA LETTER DI+ $ Entry 'T' 'U' '\x30C3' -- KATAKANA LETTER SMALL TU+ $ Entry 'T' 'u' '\x30C4' -- KATAKANA LETTER TU+ $ Entry 'D' 'u' '\x30C5' -- KATAKANA LETTER DU+ $ Entry 'T' 'e' '\x30C6' -- KATAKANA LETTER TE+ $ Entry 'D' 'e' '\x30C7' -- KATAKANA LETTER DE+ $ Entry 'T' 'o' '\x30C8' -- KATAKANA LETTER TO+ $ Entry 'D' 'o' '\x30C9' -- KATAKANA LETTER DO+ $ Entry 'N' 'a' '\x30CA' -- KATAKANA LETTER NA+ $ Entry 'N' 'i' '\x30CB' -- KATAKANA LETTER NI+ $ Entry 'N' 'u' '\x30CC' -- KATAKANA LETTER NU+ $ Entry 'N' 'e' '\x30CD' -- KATAKANA LETTER NE+ $ Entry 'N' 'o' '\x30CE' -- KATAKANA LETTER NO+ $ Entry 'H' 'a' '\x30CF' -- KATAKANA LETTER HA+ $ Entry 'B' 'a' '\x30D0' -- KATAKANA LETTER BA+ $ Entry 'P' 'a' '\x30D1' -- KATAKANA LETTER PA+ $ Entry 'H' 'i' '\x30D2' -- KATAKANA LETTER HI+ $ Entry 'B' 'i' '\x30D3' -- KATAKANA LETTER BI+ $ Entry 'P' 'i' '\x30D4' -- KATAKANA LETTER PI+ $ Entry 'H' 'u' '\x30D5' -- KATAKANA LETTER HU+ $ Entry 'B' 'u' '\x30D6' -- KATAKANA LETTER BU+ $ Entry 'P' 'u' '\x30D7' -- KATAKANA LETTER PU+ $ Entry 'H' 'e' '\x30D8' -- KATAKANA LETTER HE+ $ Entry 'B' 'e' '\x30D9' -- KATAKANA LETTER BE+ $ Entry 'P' 'e' '\x30DA' -- KATAKANA LETTER PE+ $ Entry 'H' 'o' '\x30DB' -- KATAKANA LETTER HO+ $ Entry 'B' 'o' '\x30DC' -- KATAKANA LETTER BO+ $ Entry 'P' 'o' '\x30DD' -- KATAKANA LETTER PO+ $ Entry 'M' 'a' '\x30DE' -- KATAKANA LETTER MA+ $ Entry 'M' 'i' '\x30DF' -- KATAKANA LETTER MI+ $ Entry 'M' 'u' '\x30E0' -- KATAKANA LETTER MU+ $ Entry 'M' 'e' '\x30E1' -- KATAKANA LETTER ME+ $ Entry 'M' 'o' '\x30E2' -- KATAKANA LETTER MO+ $ Entry 'Y' 'A' '\x30E3' -- KATAKANA LETTER SMALL YA+ $ Entry 'Y' 'a' '\x30E4' -- KATAKANA LETTER YA+ $ Entry 'Y' 'U' '\x30E5' -- KATAKANA LETTER SMALL YU+ $ Entry 'Y' 'u' '\x30E6' -- KATAKANA LETTER YU+ $ Entry 'Y' 'O' '\x30E7' -- KATAKANA LETTER SMALL YO+ $ Entry 'Y' 'o' '\x30E8' -- KATAKANA LETTER YO+ $ Entry 'R' 'a' '\x30E9' -- KATAKANA LETTER RA+ $ Entry 'R' 'i' '\x30EA' -- KATAKANA LETTER RI+ $ Entry 'R' 'u' '\x30EB' -- KATAKANA LETTER RU+ $ Entry 'R' 'e' '\x30EC' -- KATAKANA LETTER RE+ $ Entry 'R' 'o' '\x30ED' -- KATAKANA LETTER RO+ $ Entry 'W' 'A' '\x30EE' -- KATAKANA LETTER SMALL WA+ $ Entry 'W' 'a' '\x30EF' -- KATAKANA LETTER WA+ $ Entry 'W' 'i' '\x30F0' -- KATAKANA LETTER WI+ $ Entry 'W' 'e' '\x30F1' -- KATAKANA LETTER WE+ $ Entry 'W' 'o' '\x30F2' -- KATAKANA LETTER WO+ $ Entry 'N' '6' '\x30F3' -- KATAKANA LETTER N `+ $ Entry 'V' 'u' '\x30F4' -- KATAKANA LETTER VU+ $ Entry 'K' 'A' '\x30F5' -- KATAKANA LETTER SMALL KA+ $ Entry 'K' 'E' '\x30F6' -- KATAKANA LETTER SMALL KE+ $ Entry 'V' 'a' '\x30F7' -- KATAKANA LETTER VA+ $ Entry 'V' 'i' '\x30F8' -- KATAKANA LETTER VI+ $ Entry 'V' 'e' '\x30F9' -- KATAKANA LETTER VE+ $ Entry 'V' 'o' '\x30FA' -- KATAKANA LETTER VO+ $ Entry '.' '6' '\x30FB' -- KATAKANA MIDDLE DOT+ $ Entry '-' '6' '\x30FC' -- KATAKANA-HIRAGANA PROLONGED SOUND MARK+ $ Entry '*' '6' '\x30FD' -- KATAKANA ITERATION MARK+ $ Entry '+' '6' '\x30FE' -- KATAKANA VOICED ITERATION MARK+ $ Entry 'b' '4' '\x3105' -- BOPOMOFO LETTER B+ $ Entry 'p' '4' '\x3106' -- BOPOMOFO LETTER P+ $ Entry 'm' '4' '\x3107' -- BOPOMOFO LETTER M+ $ Entry 'f' '4' '\x3108' -- BOPOMOFO LETTER F+ $ Entry 'd' '4' '\x3109' -- BOPOMOFO LETTER D+ $ Entry 't' '4' '\x310A' -- BOPOMOFO LETTER T+ $ Entry 'n' '4' '\x310B' -- BOPOMOFO LETTER N `+ $ Entry 'l' '4' '\x310C' -- BOPOMOFO LETTER L+ $ Entry 'g' '4' '\x310D' -- BOPOMOFO LETTER G+ $ Entry 'k' '4' '\x310E' -- BOPOMOFO LETTER K+ $ Entry 'h' '4' '\x310F' -- BOPOMOFO LETTER H+ $ Entry 'j' '4' '\x3110' -- BOPOMOFO LETTER J+ $ Entry 'q' '4' '\x3111' -- BOPOMOFO LETTER Q+ $ Entry 'x' '4' '\x3112' -- BOPOMOFO LETTER X+ $ Entry 'z' 'h' '\x3113' -- BOPOMOFO LETTER ZH+ $ Entry 'c' 'h' '\x3114' -- BOPOMOFO LETTER CH+ $ Entry 's' 'h' '\x3115' -- BOPOMOFO LETTER SH+ $ Entry 'r' '4' '\x3116' -- BOPOMOFO LETTER R+ $ Entry 'z' '4' '\x3117' -- BOPOMOFO LETTER Z+ $ Entry 'c' '4' '\x3118' -- BOPOMOFO LETTER C+ $ Entry 's' '4' '\x3119' -- BOPOMOFO LETTER S+ $ Entry 'a' '4' '\x311A' -- BOPOMOFO LETTER A+ $ Entry 'o' '4' '\x311B' -- BOPOMOFO LETTER O+ $ Entry 'e' '4' '\x311C' -- BOPOMOFO LETTER E+ $ Entry 'a' 'i' '\x311E' -- BOPOMOFO LETTER AI+ $ Entry 'e' 'i' '\x311F' -- BOPOMOFO LETTER EI+ $ Entry 'a' 'u' '\x3120' -- BOPOMOFO LETTER AU+ $ Entry 'o' 'u' '\x3121' -- BOPOMOFO LETTER OU+ $ Entry 'a' 'n' '\x3122' -- BOPOMOFO LETTER AN+ $ Entry 'e' 'n' '\x3123' -- BOPOMOFO LETTER EN+ $ Entry 'a' 'N' '\x3124' -- BOPOMOFO LETTER ANG+ $ Entry 'e' 'N' '\x3125' -- BOPOMOFO LETTER ENG+ $ Entry 'e' 'r' '\x3126' -- BOPOMOFO LETTER ER+ $ Entry 'i' '4' '\x3127' -- BOPOMOFO LETTER I+ $ Entry 'u' '4' '\x3128' -- BOPOMOFO LETTER U+ $ Entry 'i' 'u' '\x3129' -- BOPOMOFO LETTER IU+ $ Entry 'v' '4' '\x312A' -- BOPOMOFO LETTER V+ $ Entry 'n' 'G' '\x312B' -- BOPOMOFO LETTER NG+ $ Entry 'g' 'n' '\x312C' -- BOPOMOFO LETTER GN+ $ Entry '1' 'c' '\x3220' -- PARENTHESIZED IDEOGRAPH ONE+ $ Entry '2' 'c' '\x3221' -- PARENTHESIZED IDEOGRAPH TWO+ $ Entry '3' 'c' '\x3222' -- PARENTHESIZED IDEOGRAPH THREE+ $ Entry '4' 'c' '\x3223' -- PARENTHESIZED IDEOGRAPH FOUR+ $ Entry '5' 'c' '\x3224' -- PARENTHESIZED IDEOGRAPH FIVE+ $ Entry '6' 'c' '\x3225' -- PARENTHESIZED IDEOGRAPH SIX+ $ Entry '7' 'c' '\x3226' -- PARENTHESIZED IDEOGRAPH SEVEN+ $ Entry '8' 'c' '\x3227' -- PARENTHESIZED IDEOGRAPH EIGHT+ $ Entry '9' 'c' '\x3228' -- PARENTHESIZED IDEOGRAPH NINE+ $ Entry 'f' 'f' '\xFB00' -- LATIN SMALL LIGATURE FF+ $ Entry 'f' 'i' '\xFB01' -- LATIN SMALL LIGATURE FI+ $ Entry 'f' 'l' '\xFB02' -- LATIN SMALL LIGATURE FL+ $ Entry 'f' 't' '\xFB05' -- LATIN SMALL LIGATURE LONG S T+ $ Entry 's' 't' '\xFB06' -- LATIN SMALL LIGATURE ST+ $ Nil