Skip to content

Commit 28903b3

Browse files
committed
add timeout for getBlockOfChars
- fixes issue haskell#160 and maybe issue haskell#77. - 20ms delay worked well for 9600bps serial port, 10ms did not.
1 parent 9b0a955 commit 28903b3

File tree

2 files changed

+28
-24
lines changed

2 files changed

+28
-24
lines changed

System/Console/Haskeline/Backend/Posix.hsc

+27-12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import Control.Monad
2525
import Control.Monad.Catch (MonadMask, handle, finally)
2626
import Control.Concurrent.STM
2727
import Control.Concurrent hiding (throwTo)
28+
import Control.Concurrent.Async (runConcurrently, Concurrently(..))
29+
import Control.Applicative ((<|>))
30+
import Data.Functor (($>))
2831
import Data.Maybe (catMaybes)
2932
import System.Posix.Signals.Exts
3033
import System.Posix.Types(Fd(..))
@@ -244,21 +247,33 @@ getEvent h baseMap = keyEventLoop $ do
244247
return [KeyInput $ lexKeys baseMap cs]
245248

246249
-- Read at least one character of input, and more if immediately
247-
-- available. In particular the characters making up a control sequence
248-
-- will all be available at once, so they can be processed together
249-
-- (with Posix.lexKeys).
250+
-- available. If an ESC character is seen, timeout mode is
251+
-- activated to prevent control sequences from being broken across
252+
-- getBlockofChars calls. A timeout of 20ms was shown to work
253+
-- well with a 9600bps serial port.
250254
getBlockOfChars :: Handle -> IO String
251255
getBlockOfChars h = do
252256
c <- hGetChar h
253-
loop [c]
254-
where
255-
loop cs = do
256-
isReady <- hReady h
257-
if not isReady
258-
then return $ reverse cs
259-
else do
260-
c <- hGetChar h
261-
loop (c:cs)
257+
loop c [] False
258+
where
259+
loop :: Char -> String -> Bool -> IO String
260+
loop c' cs' timeout' = do
261+
let
262+
timeout = timeout' || (c' == '\ESC')
263+
cs = (c':cs')
264+
maybeC <- do
265+
isReady <- hReady h
266+
case (timeout, isReady) of
267+
(_, True) -> do -- fast new character case
268+
Just <$> hGetChar h
269+
(True, False) -> -- wait up to 20ms for next char
270+
runConcurrently $ Concurrently (Just <$> hGetChar h)
271+
<|> Concurrently (threadDelay 20000 $> Nothing)
272+
(False, False) -> -- no new char and timeout mode has not been triggered
273+
pure Nothing
274+
case maybeC of
275+
Just c -> loop c cs timeout
276+
Nothing -> pure $ reverse cs
262277

263278
stdinTTYHandles, ttyHandles :: MaybeT IO Handles
264279
stdinTTYHandles = do

haskeline.cabal

+1-12
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ flag examples
6161
Manual: True
6262

6363
Library
64-
<<<<<<< HEAD
6564
Build-depends:
6665
base >= 4.9 && < 4.21
6766
, containers >= 0.4 && < 0.8
@@ -72,16 +71,6 @@ Library
7271
, process >= 1.0 && < 1.7
7372
, stm >= 2.4 && < 2.6
7473
, exceptions == 0.10.*
75-
=======
76-
-- We require ghc>=7.4.1 (base>=4.5) to use the base library encodings, even
77-
-- though it was implemented in earlier releases, due to GHC bug #5436 which
78-
-- wasn't fixed until 7.4.1
79-
Build-depends: base >=4.9 && < 4.19, containers>=0.4 && < 0.7,
80-
directory>=1.1 && < 1.4, bytestring>=0.9 && < 0.12,
81-
filepath >= 1.2 && < 1.5, transformers >= 0.2 && < 0.7,
82-
process >= 1.0 && < 1.7, stm >= 2.4 && < 2.6,
83-
exceptions == 0.10.*
84-
>>>>>>> c689b89 (Allow transformers-0.6)
8574
Default-Language: Haskell98
8675
Default-Extensions:
8776
ForeignFunctionInterface, Rank2Types, FlexibleInstances,
@@ -180,4 +169,4 @@ Executable haskeline-examples-Test
180169
Build-depends: base, containers, haskeline
181170
Default-Language: Haskell2010
182171
hs-source-dirs: examples
183-
Main-Is: Test.hs
172+
Main-Is: Test.hs

0 commit comments

Comments
 (0)