on master help sign in
add config parser and test
689e8f5Corey O'Connor
src/Data/Terminfo/Parse.hs+2−2
@@ -3,7 +3,7 @@
3{-# LANGUAGE NamedFieldPuns #-}
3{-# LANGUAGE NamedFieldPuns #-}
4{-# OPTIONS_GHC -funbox-strict-fields -O #-}
4{-# OPTIONS_GHC -funbox-strict-fields -O #-}
5module Data.Terminfo.Parse ( module Data.Terminfo.Parse
5module Data.Terminfo.Parse ( module Data.Terminfo.Parse
6− , Text.ParserCombinators.Parsec.ParseError
6+ , Text.Parsec.ParseError
7 )
7 )
8 where
8 where
9
9
@@ -106,7 +106,7 @@ constructCapExpression capString buildResults =
106 }
106 }
107 in rnf expr `seq` expr
107 in rnf expr `seq` expr
108
108
109−type CapParser a = GenParser Char BuildState a
109+type CapParser a = Parsec String BuildState a
110
110
111capExpressionParser :: CapParser BuildResults
111capExpressionParser :: CapParser BuildResults
112capExpressionParser = do
112capExpressionParser = do
src/Graphics/Vty.hs+9−1
@@ -33,6 +33,7 @@ module Graphics.Vty ( Vty(..)
33
33
34import Graphics.Vty.Prelude
34import Graphics.Vty.Prelude
35
35
36+import Graphics.Vty.Config
36import Graphics.Vty.Input
37import Graphics.Vty.Input
37import Graphics.Vty.Output
38import Graphics.Vty.Output
38import Graphics.Vty.Picture
39import Graphics.Vty.Picture
@@ -40,6 +41,7 @@ import Graphics.Vty.Picture
40import Control.Concurrent
41import Control.Concurrent
41
42
42import Data.IORef
43import Data.IORef
44+import Data.Monoid
43
45
44-- | The main object. At most one should be created.
46-- | The main object. At most one should be created.
45--
47--
@@ -81,8 +83,14 @@ data Vty = Vty
81
83
82-- | Set up the state object for using vty. At most one state object should be
84-- | Set up the state object for using vty. At most one state object should be
83-- created at a time.
85-- 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.
84mkVty :: Config -> IO Vty
91mkVty :: Config -> IO Vty
85−mkVty config = do
92+mkVty appConfig = do
93+ config <- mappend <$> pure appConfig <*> userConfig
86 input <- inputForCurrentTerminal config
94 input <- inputForCurrentTerminal config
87 out <- outputForCurrentTerminal
95 out <- outputForCurrentTerminal
88 intMkVty input out
96 intMkVty input out
src/Graphics/Vty/Config.hs+145−28
@@ -1,53 +1,170 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE NoMonomorphismRestriction #-}
1-- | A 'Config' can be provided to mkVty to customize the applications use of vty.
3-- | A 'Config' can be provided to mkVty to customize the applications use of vty.
2--
4--
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.
4--
11--
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.
6--
33--
7module Graphics.Vty.Config where
34module Graphics.Vty.Config where
8
35
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
9import Data.Default
43import Data.Default
10−import Data.Either (either)
11import Data.Monoid
44import Data.Monoid
12
45
13import Graphics.Vty.Input.Events
46import Graphics.Vty.Input.Events
14
47
15−import System.FilePath
48+import Text.Parsec hiding ((<|>))
16−
17−import Text.Parsec
49+import Text.Parsec.Token ( GenLanguageDef(..) )
18import qualified Text.Parsec.Token as P
50import qualified Text.Parsec.Token as P
19−import Text.Parsec.Language (haskellDef)
20
51
21data Config = Config
52data Config = Config
22− { singleEscPeriod :: Int -- ^ AKA VTIME. The default is 100000 microseconds or 0.1 seconds.
53+ { specifiedEscPeriod :: Maybe Int
23 -- | Debug information about the input process is appended to the file.
54 -- | Debug information about the input process is appended to the file.
24− , debugInputLog :: Maybe FilePath
55+ , debugInputLog :: Maybe FilePath
25− , inputOverrides :: ClassifyTable
56+ , inputOverrides :: ClassifyTable
26 } deriving (Show, Eq)
57 } deriving (Show, Eq)
27
58
59+-- | AKA VTIME. The default is 100000 microseconds or 0.1 seconds.
60+singleEscPeriod :: Config -> Int
61+singleEscPeriod = maybe 100000 id . specifiedEscPeriod
62+
28instance Default Config where
63instance Default Config where
29 def = Config
64 def = Config
30− { singleEscPeriod = 100000
65+ { specifiedEscPeriod = Nothing
31− , debugInputLog = Nothing
66+ , debugInputLog = Nothing
32− , inputOverrides = []
67+ , inputOverrides = []
33 }
68 }
34
69
35−-- not a proper monoid but useful as one.
36instance Monoid Config where
70instance Monoid Config where
37 mempty = def
71 mempty = def
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
38− mappend c0 c1 = c1 { inputOverrides = inputOverrides c0 <> inputOverrides c1 }
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
39
87
40parseConfigFile :: FilePath -> IO Config
88parseConfigFile :: FilePath -> IO Config
41−parseConfigFile path = either (const def) id <$> parseFromFile parseConfig path
89+parseConfigFile path = runParseConfig path <$> BS.readFile path
42−
90+
43−parseConfig = do
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
44− let lexer = P.makeTokenParser haskellDef
112+configLexer = P.makeTokenParser configLanguage
113+
45− parseOverride = do
114+parseOverride = do
46− P.whiteSpace lexer
47− string "map"
115+ void $ string "map"
48− P.whiteSpace lexer
116+ P.whiteSpace configLexer
49− bytes <- P.stringLiteral
117+ bytes <- P.stringLiteral configLexer
50− key <- parseKey
118+ key <- parseKey
51− modifier <- parseModifier
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+
52− _ <- manyTill space newline
163+ignoreLine = void $ manyTill anyChar newline
53− overrides <- many $ try parseOverride <|> ignoreOverride
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(..)
34 , Button(..)
34 , Button(..)
35 , Event(..)
35 , Event(..)
36 , Input(..)
36 , Input(..)
37− , Config(..)
38 , inputForCurrentTerminal
37 , inputForCurrentTerminal
39 , inputForNameAndIO
38 , inputForNameAndIO
40 )
39 )
41 where
40 where
42
41
43import Graphics.Vty.Config
42import Graphics.Vty.Config
44−import Graphics.Vty.Input.Classify
45import Graphics.Vty.Input.Events
43import Graphics.Vty.Input.Events
46import Graphics.Vty.Input.Loop
44import Graphics.Vty.Input.Loop
47import Graphics.Vty.Input.Terminfo
45import Graphics.Vty.Input.Terminfo
48
46
49−import Control.Applicative
50import Control.Concurrent
47import Control.Concurrent
51import Control.Lens
48import Control.Lens
52
49
@@ -96,8 +93,7 @@ inputForCurrentTerminal config = do
96inputForNameAndIO :: Config -> String -> Fd -> IO Input
93inputForNameAndIO :: Config -> String -> Fd -> IO Input
97inputForNameAndIO config termName termFd = do
94inputForNameAndIO config termName termFd = do
98 terminal <- Terminfo.setupTerm termName
95 terminal <- Terminfo.setupTerm termName
99− classifyTable <- mappend <$> pure (classifyTableForTerm termName terminal)
96+ let classifyTable = classifyTableForTerm termName terminal `mappend` inputOverrides config
100− <*> classifyTableUserOverrides
101 (setAttrs,unsetAttrs) <- attributeControl termFd
97 (setAttrs,unsetAttrs) <- attributeControl termFd
102 setAttrs
98 setAttrs
103 input <- initInputForFd config classifyTable termFd
99 input <- initInputForFd config classifyTable termFd
src/Graphics/Vty/Input/Terminfo.hs+3−33
@@ -40,36 +40,6 @@ classifyTableForTerm termName term =
40 : universalTable
40 : universalTable
41 : termSpecificTables termName
41 : termSpecificTables termName
42
42
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−
73-- | key table assumed to be applicable to all terminals.
43-- | key table assumed to be applicable to all terminals.
74universalTable :: ClassifyTable
44universalTable :: ClassifyTable
75universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]
45universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]
@@ -103,9 +73,9 @@ visibleChars = [ ([x], EvKey (KChar x) [])
103ctrlChars :: ClassifyTable
73ctrlChars :: ClassifyTable
104ctrlChars =
74ctrlChars =
105 [ ([toEnum x],EvKey (KChar y) [MCtrl])
75 [ ([toEnum x],EvKey (KChar y) [MCtrl])
106− | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),
76+ | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_'])
107− y /= 'i', -- Resolve issue #3 where CTRL-i hides TAB.
77+ , y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.
108− y /= 'h' -- CTRL-h should not hide BS
78+ , y /= 'h' -- CTRL-h should not hide BS
109 ]
79 ]
110
80
111-- | Ctrl+Meta+Char
81-- | Ctrl+Meta+Char
test/VerifyConfig.hs+13−1
@@ -2,6 +2,7 @@
2module Main where
2module Main where
3
3
4import Graphics.Vty.Config
4import Graphics.Vty.Config
5+import Graphics.Vty.Input.Events
5
6
6import Control.Applicative
7import Control.Applicative
7import Control.Exception
8import Control.Exception
@@ -19,12 +20,23 @@ import Text.Printf
19
20
20exampleConfig :: String
21exampleConfig :: String
21exampleConfig = [s|
22exampleConfig = [s|
23+-- comments should be ignored.
22map "\ESC[B" KUp []
24map "\ESC[B" KUp []
25+askfjla dfasjdflk jasdlkfj asdfj -- lines failing parse should be ignored
23map "\ESC[1;3B" KDown [MAlt]
26map "\ESC[1;3B" KDown [MAlt]
24|]
27|]
25
28
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+
26exampleConfigParses :: IO ()
36exampleConfigParses :: IO ()
27−exampleConfigParses = return ()
37+exampleConfigParses = assertEqual "example config parses as expected"
38+ exampleConfigConfig
39+ (runParseConfig "exampleConfig" exampleConfig)
28
40
29main :: IO ()
41main :: IO ()
30main = defaultMain
42main = defaultMain
vty.cabal+1−1
@@ -113,7 +113,7 @@ library
113
113
114 ghc-prof-options: -O2 -funbox-strict-fields -threaded -caf-all -Wall -fspec-constr -fspec-constr-count=10
114 ghc-prof-options: -O2 -funbox-strict-fields -threaded -caf-all -Wall -fspec-constr -fspec-constr-count=10
115
115
116− cc-options: -O2
116+ cc-options: -O2 -fpic
117
117
118test-suite verify-attribute-ops
118test-suite verify-attribute-ops
119 default-language: Haskell2010
119 default-language: Haskell2010