on master help sign in
add config parser and test
commit689e8f57322e048a2e9e11f960b7239ab12e4c22 authorCorey O'Connor <coreyoconnor@gmail.com> dateWed 23 Apr 2014, 19:07 (13 years ago) parentce9515a
7 files changed+174−71
src/Data/Terminfo/Parse.hs+2−2 src/Graphics/Vty.hs+9−1 src/Graphics/Vty/Config.hs+145−28 src/Graphics/Vty/Input.hs+1−5 src/Graphics/Vty/Input/Terminfo.hs+3−33 test/VerifyConfig.hs+13−1 vty.cabal+1−1
src/Data/Terminfo/Parse.hs+2−2
@@ -3,7 +3,7 @@
33{-# LANGUAGE NamedFieldPuns #-}
44{-# OPTIONS_GHC -funbox-strict-fields -O #-}
55module Data.Terminfo.Parse ( module Data.Terminfo.Parse
6− , Text.ParserCombinators.Parsec.ParseError
6+ , Text.Parsec.ParseError
77 )
88 where
99
@@ -106,7 +106,7 @@ constructCapExpression capString buildResults =
106106 }
107107 in rnf expr `seq` expr
108108
109−type CapParser a = GenParser Char BuildState a
109+type CapParser a = Parsec String BuildState a
110110
111111capExpressionParser :: CapParser BuildResults
112112capExpressionParser = do
src/Graphics/Vty.hs+9−1
@@ -33,6 +33,7 @@ module Graphics.Vty ( Vty(..)
3333
3434import Graphics.Vty.Prelude
3535
36+import Graphics.Vty.Config
3637import Graphics.Vty.Input
3738import Graphics.Vty.Output
3839import Graphics.Vty.Picture
@@ -40,6 +41,7 @@ import Graphics.Vty.Picture
4041import Control.Concurrent
4142
4243import Data.IORef
44+import Data.Monoid
4345
4446-- | The main object. At most one should be created.
4547--
@@ -81,8 +83,14 @@ data Vty = Vty
8183
8284-- | Set up the state object for using vty. At most one state object should be
8385-- created at a time.
86+--
87+-- The specified config is added to the 'userConfig'. With the 'userConfig' taking precedence.
88+-- See "Graphics.Vty.Config"
89+--
90+-- For most applications `mkVty def` is sufficient.
8491mkVty :: Config -> IO Vty
85−mkVty config = do
92+mkVty appConfig = do
93+ config <- mappend <$> pure appConfig <*> userConfig
8694 input <- inputForCurrentTerminal config
8795 out <- outputForCurrentTerminal
8896 intMkVty input out
src/Graphics/Vty/Config.hs+145−28
@@ -1,53 +1,170 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE NoMonomorphismRestriction #-}
13-- | A 'Config' can be provided to mkVty to customize the applications use of vty.
24--
3−-- Parts of the structure can be overriden by $HOME/.config/vty.config and then $VTY_CONFIG_FILE.
5+-- The 'Config' provided is mappend'd to 'Config's loaded from $HOME/.config/vty.config and
6+-- $VTY_CONFIG_FILE. The $VTY_CONFIG_FILE takes precedence over the input.conf file or the
7+-- application provided 'Config'.
8+--
9+-- Each line of the input config is processed individually. Lines that fail to parse are ignored.
10+-- Later entries take precedence over earlier.
411--
5−-- See also 'classifyTableUserOverrides'.
12+-- * Classify Table Overrides - "map"
13+--
14+-- Directive format:
15+--
16+-- @
17+-- entry := "map" string key modifier_list
18+-- key := KEsc | KChar Char | KBS ... (same as 'Key')
19+-- modifier_list := "[" modifier+ "]"
20+-- modifier := MShift | MCtrl | MMeta | MAlt
21+-- string := "\"" chars+ "\""
22+-- @
23+--
24+-- EG: If the contents of input.conf are
25+--
26+-- @
27+-- map "\ESC[B" KUp []
28+-- map "\ESC[1;3B" KDown [MAlt]
29+-- @
30+--
31+-- Then the bytes "\ESC[B" will result in the KUp event. The bytes "\ESC[1;3B" will result in the
32+-- event KDown with the MAlt modifier.
633--
734module Graphics.Vty.Config where
835
36+import Control.Applicative hiding (many)
37+
38+import Control.Monad (void)
39+import Control.Monad.Trans.Class
40+import Control.Monad.Trans.Writer
41+
42+import qualified Data.ByteString as BS
943import Data.Default
10−import Data.Either (either)
1144import Data.Monoid
1245
1346import Graphics.Vty.Input.Events
1447
15−import System.FilePath
16−
17−import Text.Parsec
48+import Text.Parsec hiding ((<|>))
49+import Text.Parsec.Token ( GenLanguageDef(..) )
1850import qualified Text.Parsec.Token as P
19−import Text.Parsec.Language (haskellDef)
2051
2152data Config = Config
22− { singleEscPeriod :: Int -- ^ AKA VTIME. The default is 100000 microseconds or 0.1 seconds.
53+ { specifiedEscPeriod :: Maybe Int
2354 -- | Debug information about the input process is appended to the file.
24− , debugInputLog :: Maybe FilePath
25− , inputOverrides :: ClassifyTable
55+ , debugInputLog :: Maybe FilePath
56+ , inputOverrides :: ClassifyTable
2657 } deriving (Show, Eq)
2758
59+-- | AKA VTIME. The default is 100000 microseconds or 0.1 seconds.
60+singleEscPeriod :: Config -> Int
61+singleEscPeriod = maybe 100000 id . specifiedEscPeriod
62+
2863instance Default Config where
2964 def = Config
30− { singleEscPeriod = 100000
31− , debugInputLog = Nothing
32− , inputOverrides = []
65+ { specifiedEscPeriod = Nothing
66+ , debugInputLog = Nothing
67+ , inputOverrides = []
3368 }
3469
35−-- not a proper monoid but useful as one.
3670instance Monoid Config where
3771 mempty = def
38− mappend c0 c1 = c1 { inputOverrides = inputOverrides c0 <> inputOverrides c1 }
72+ mappend c0 c1 = Config
73+ -- latter config takes priority in specifiedEscPeriod
74+ { specifiedEscPeriod = specifiedEscPeriod c1 <|> specifiedEscPeriod c0
75+ -- latter config takes priority in debugInputLog
76+ , debugInputLog = debugInputLog c1 <|> debugInputLog c0
77+ , inputOverrides = inputOverrides c0 <> inputOverrides c1
78+ }
79+
80+type ConfigParser s a = ParsecT s () (Writer Config) a
81+
82+userConfig :: IO Config
83+userConfig = do
84+ userGlobalConfig <- parseConfigFile "$HOME/.config/vty.config"
85+ overrideConfig <- parseConfigFile "$VTY_CONFIG_FILE"
86+ return $ userGlobalConfig `mappend` overrideConfig
3987
4088parseConfigFile :: FilePath -> IO Config
41−parseConfigFile path = either (const def) id <$> parseFromFile parseConfig path
42−
43−parseConfig = do
44− let lexer = P.makeTokenParser haskellDef
45− parseOverride = do
46− P.whiteSpace lexer
47− string "map"
48− P.whiteSpace lexer
49− bytes <- P.stringLiteral
50− key <- parseKey
51− modifier <- parseModifier
52− _ <- manyTill space newline
53− overrides <- many $ try parseOverride <|> ignoreOverride
89+parseConfigFile path = runParseConfig path <$> BS.readFile path
90+
91+runParseConfig :: Stream s (Writer Config) Char => String -> s -> Config
92+runParseConfig name = execWriter . runParserT parseConfig () name
93+
94+-- I tried to use the haskellStyle here but that was specialized (without requirement?) to the
95+-- String stream type.
96+configLanguage :: Stream s m Char => P.GenLanguageDef s u m
97+configLanguage = LanguageDef
98+ { commentStart = "{-"
99+ , commentEnd = "-}"
100+ , commentLine = "--"
101+ , nestedComments = True
102+ , identStart = letter <|> char '_'
103+ , identLetter = alphaNum <|> oneOf "_'"
104+ , opStart = opLetter configLanguage
105+ , opLetter = oneOf ":!#$%&*+./<=>?@\\^|-~"
106+ , reservedOpNames = []
107+ , reservedNames = []
108+ , caseSensitive = True
109+ }
110+
111+configLexer :: Stream s m Char => P.GenTokenParser s u m
112+configLexer = P.makeTokenParser configLanguage
113+
114+parseOverride = do
115+ void $ string "map"
116+ P.whiteSpace configLexer
117+ bytes <- P.stringLiteral configLexer
118+ key <- parseKey
119+ modifiers <- parseModifiers
120+ lift $ tell $ def { inputOverrides = [(bytes, EvKey key modifiers)] }
121+
122+parseKey = do
123+ key <- P.identifier configLexer
124+ case key of
125+ "KChar" -> KChar <$> P.charLiteral configLexer
126+ "KFun" -> KFun . fromInteger <$> P.natural configLexer
127+ "KEsc" -> return KEsc
128+ "KBS" -> return KBS
129+ "KEnter" -> return KEnter
130+ "KLeft" -> return KLeft
131+ "KRight" -> return KRight
132+ "KUp" -> return KUp
133+ "KDown" -> return KDown
134+ "KUpLeft" -> return KUpLeft
135+ "KUpRight" -> return KUpRight
136+ "KDownLeft" -> return KDownLeft
137+ "KDownRight" -> return KDownRight
138+ "KCenter" -> return KCenter
139+ "KBackTab" -> return KBackTab
140+ "KPrtScr" -> return KPrtScr
141+ "KPause" -> return KPause
142+ "KIns" -> return KIns
143+ "KHome" -> return KHome
144+ "KPageUp" -> return KPageUp
145+ "KDel" -> return KDel
146+ "KEnd" -> return KEnd
147+ "KPageDown" -> return KPageDown
148+ "KBegin" -> return KBegin
149+ "KMenu" -> return KMenu
150+ _ -> fail $ key ++ " is not a valid key identifier"
151+
152+parseModifiers = P.brackets configLexer (parseModifier `sepBy` P.symbol configLexer ",")
153+
154+parseModifier = do
155+ m <- P.identifier configLexer
156+ case m of
157+ "KMenu" -> return MShift
158+ "MCtrl" -> return MCtrl
159+ "MMeta" -> return MMeta
160+ "MAlt" -> return MAlt
161+ _ -> fail $ m ++ " is not a valid modifier identifier"
162+
163+ignoreLine = void $ manyTill anyChar newline
164+
165+-- TODO: Generated by a vim macro. There is a better way here. Derive parser? Use Read
166+-- instance?
167+parseConfig = void $ many $ do
168+ P.whiteSpace configLexer
169+ let directives = [parseOverride]
170+ try (choice directives) <|> ignoreLine
src/Graphics/Vty/Input.hs+1−5
@@ -34,19 +34,16 @@ module Graphics.Vty.Input ( Key(..)
3434 , Button(..)
3535 , Event(..)
3636 , Input(..)
37− , Config(..)
3837 , inputForCurrentTerminal
3938 , inputForNameAndIO
4039 )
4140 where
4241
4342import Graphics.Vty.Config
44−import Graphics.Vty.Input.Classify
4543import Graphics.Vty.Input.Events
4644import Graphics.Vty.Input.Loop
4745import Graphics.Vty.Input.Terminfo
4846
49−import Control.Applicative
5047import Control.Concurrent
5148import Control.Lens
5249
@@ -96,8 +93,7 @@ inputForCurrentTerminal config = do
9693inputForNameAndIO :: Config -> String -> Fd -> IO Input
9794inputForNameAndIO config termName termFd = do
9895 terminal <- Terminfo.setupTerm termName
99− classifyTable <- mappend <$> pure (classifyTableForTerm termName terminal)
100− <*> classifyTableUserOverrides
96+ let classifyTable = classifyTableForTerm termName terminal `mappend` inputOverrides config
10197 (setAttrs,unsetAttrs) <- attributeControl termFd
10298 setAttrs
10399 input <- initInputForFd config classifyTable termFd
src/Graphics/Vty/Input/Terminfo.hs+3−33
@@ -40,36 +40,6 @@ classifyTableForTerm termName term =
4040 : universalTable
4141 : termSpecificTables termName
4242
43−-- | The user can specify a list of classify table entries in $HOME/.config/vty.conf and
44−-- $VTY_CONFIG_FILE. The file at $VTY_CONFIG_FILE takes precedence over the input.conf file. Both
45−-- take precedence over the classify tables determined by 'classifyTableForTerm'
46−--
47−-- Each line of the input config is processed individually. Lines that fail to parse are ignored.
48−-- Later entries take precedence over earlier.
49−--
50−-- Entry format:
51−--
52−-- @
53−-- entry := "map" string key modifier_list
54−-- key := KEsc | KChar Char | KBS ... (same as 'Key')
55−-- modifier_list := "[" modifier+ "]"
56−-- modifier := MShift | MCtrl | MMeta | MAlt
57−-- string := "\"" chars+ "\""
58−-- @
59−--
60−-- EG: If the contents of input.conf are
61−--
62−-- @
63−-- map "\ESC[B" KUp []
64−-- map "\ESC[1;3B" KDown [MAlt]
65−-- @
66−--
67−-- Then the bytes "\ESC[B" will result in the KUp event. The bytes "\ESC[1;3B" will result in the
68−-- event KDown with the MAlt modifier.
69−--
70−classifyTableUserOverrides :: IO ClassifyTable
71−classifyTableUserOverrides = return []
72−
7343-- | key table assumed to be applicable to all terminals.
7444universalTable :: ClassifyTable
7545universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]
@@ -103,9 +73,9 @@ visibleChars = [ ([x], EvKey (KChar x) [])
10373ctrlChars :: ClassifyTable
10474ctrlChars =
10575 [ ([toEnum x],EvKey (KChar y) [MCtrl])
106− | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),
107− y /= 'i', -- Resolve issue #3 where CTRL-i hides TAB.
108− y /= 'h' -- CTRL-h should not hide BS
76+ | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_'])
77+ , y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
78+ , y /= 'h' -- CTRL-h should not hide BS
10979 ]
11080
11181-- | Ctrl+Meta+Char
test/VerifyConfig.hs+13−1
@@ -2,6 +2,7 @@
22module Main where
33
44import Graphics.Vty.Config
5+import Graphics.Vty.Input.Events
56
67import Control.Applicative
78import Control.Exception
@@ -19,12 +20,23 @@ import Text.Printf
1920
2021exampleConfig :: String
2122exampleConfig = [s|
23+-- comments should be ignored.
2224map "\ESC[B" KUp []
25+askfjla dfasjdflk jasdlkfj asdfj -- lines failing parse should be ignored
2326map "\ESC[1;3B" KDown [MAlt]
2427|]
2528
29+exampleConfigConfig :: Config
30+exampleConfigConfig = Config
31+ { specifiedEscPeriod = def
32+ , debugInputLog = def
33+ , inputOverrides = [("\ESC[B", EvKey KUp []), ("\ESC[1;3B", EvKey KDown [MAlt])]
34+ }
35+
2636exampleConfigParses :: IO ()
27−exampleConfigParses = return ()
37+exampleConfigParses = assertEqual "example config parses as expected"
38+ exampleConfigConfig
39+ (runParseConfig "exampleConfig" exampleConfig)
2840
2941main :: IO ()
3042main = defaultMain
vty.cabal+1−1
@@ -113,7 +113,7 @@ library
113113
114114 ghc-prof-options: -O2 -funbox-strict-fields -threaded -caf-all -Wall -fspec-constr -fspec-constr-count=10
115115
116− cc-options: -O2
116+ cc-options: -O2 -fpic
117117
118118test-suite verify-attribute-ops
119119 default-language: Haskell2010