summaryrefslogtreecommitdiffstats
path: root/src/Scanner.hs
blob: 361a1c63171e8137fdf368e2b3d9aa76940e037b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
{-# LANGUAGE LambdaCase #-}

module Scanner
    ( scan
    ) where

import Data.Char (ord)
import Event
import System.IO (Handle, hGetChar, hLookAhead, hWaitForInput)


timeout :: Int
timeout = 1


scan :: Handle -> IO Event
scan h =
    hGetChar h >>= \case
        '\ESC' -> scanESC h
        c -> return $ EKey [c]



scanESC :: Handle -> IO Event
scanESC h =
    hWaitGetChar timeout h >>= \case
        Nothing -> return $ EKey "\ESC"
        Just c
            | c == '[' -> -- 05/11
                scanCS h
            | c == '\ESC' -> -- 01/11
                -- XXX M-F1 and other crazy chords may cause
                -- \ESC\ESC... on wu, so we just recurse here...
                scanESC h
            | c == 'O' -> -- 04/15
                -- XXX Non-CSI SS3
                -- XXX finalByte is maybe calles somehow else here, but it's
                -- the same range
                one h finalByte ['O','\ESC'] >>=
                return . EKey . reverse
            | otherwise ->
                return $ EKey ['\ESC',c]


scanCS :: Handle -> IO Event
scanCS h =
    hWaitLookAhead timeout h >>= \case
        Nothing -> return $ EKey "\ESC" -- TODO move this to scanESC
        Just c
            | c == 'M' -> do
                -- VT200 mouse
                _ <- hGetChar h -- drop 'M'
                b <- hGetChar h
                x <- hGetChar h
                y <- hGetChar h
                return $ EMouse b (ord x - 32) (ord y - 32)
            | otherwise ->
                zeroOrMore h parameterByte ['[', '\ESC'] >>=
                zeroOrMore h intermediateByte >>=
                one h finalByte >>=
                return . EKey . reverse



zeroOrMore :: Handle -> (Char -> Bool) -> [Char] -> IO [Char]
zeroOrMore h p buf =
    hWaitLookAhead timeout h >>= \case
        Nothing -> return buf
        Just c
            | p c ->
                hGetChar h {-drop c-} >> zeroOrMore h p (c:buf)
            | otherwise ->
                return buf


one :: Handle -> (Char -> Bool) -> [Char] -> IO [Char]
one h p buf =
    hWaitLookAhead timeout h >>= \case
        Nothing -> return buf -- TODO error?
        Just c
            | p c -> do
                _ <- hGetChar h -- drop c
                return (c:buf)
            | otherwise ->
                error "expected one TODO"


parameterByte :: Char -> Bool
parameterByte = between '0' '?'     -- 03/00 03/15

intermediateByte :: Char -> Bool
intermediateByte = between ' ' '/'  -- 02/00 02/15

finalByte :: Char -> Bool
finalByte = between '@' '~'         -- 04/00 07/14


between :: Ord a => a -> a -> (a -> Bool)
between lo hi = \ x -> lo <= x && x <= hi


hWaitGetChar :: Int -> Handle -> IO (Maybe Char)
hWaitGetChar t h = do
    ready <- hWaitForInput h t
    if ready
        then hGetChar h >>= return . Just
        else return Nothing


hWaitLookAhead :: Int -> Handle -> IO (Maybe Char)
hWaitLookAhead t h = do
    ready <- hWaitForInput h t
    if ready
        then hLookAhead h >>= return . Just
        else return Nothing