Skip to content

Commit 07df926

Browse files
committed
add timeout for getBlockOfChars
- fixes haskell#77
1 parent 7d8d1ab commit 07df926

File tree

2 files changed

+30
-13
lines changed

2 files changed

+30
-13
lines changed

System/Console/Haskeline/Backend/Posix.hsc

+29-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import Control.Monad
2222
import Control.Monad.Catch (MonadMask, handle, finally)
2323
import Control.Concurrent.STM
2424
import Control.Concurrent hiding (throwTo)
25+
import Control.Concurrent.Async (runConcurrently, Concurrently(..))
26+
import Control.Applicative ((<|>))
27+
import Data.Functor (($>))
2528
import Data.Maybe (catMaybes)
2629
import System.Posix.Signals.Exts
2730
import System.Posix.Types(Fd(..))
@@ -233,21 +236,35 @@ getEvent h baseMap = keyEventLoop $ do
233236
return [KeyInput $ lexKeys baseMap cs]
234237

235238
-- Read at least one character of input, and more if immediately
236-
-- available. In particular the characters making up a control sequence
237-
-- will all be available at once, so they can be processed together
238-
-- (with Posix.lexKeys).
239+
-- available. If an ESC character is seen, timeout mode is
240+
-- activated to prevent control sequences from being broken across
241+
-- getBlockofChars calls. A timeout of 10ms is chosen to work well
242+
-- down to 1200baud while still providing decent response times.
243+
239244
getBlockOfChars :: Handle -> IO String
240245
getBlockOfChars h = do
241246
c <- hGetChar h
242-
loop [c]
243-
where
244-
loop cs = do
245-
isReady <- hReady h
246-
if not isReady
247-
then return $ reverse cs
248-
else do
249-
c <- hGetChar h
250-
loop (c:cs)
247+
loop c [] False
248+
where
249+
loop :: Char -> String -> Bool -> IO String
250+
loop c' cs' timeout' = do
251+
let
252+
timeout = timeout' || (c' == '\ESC')
253+
cs = (c':cs')
254+
maybeC <- do
255+
isReady <- hReady h
256+
case (timeout, isReady) of
257+
-- fast new character case
258+
(_, True) -> Just <$> hGetChar h
259+
-- wait up to 10ms for next char
260+
(True, False) ->
261+
runConcurrently $ Concurrently (Just <$> hGetChar h)
262+
<|> Concurrently (threadDelay 10000 $> Nothing)
263+
-- no new char and timeout mode has not been triggered
264+
(False, False) -> pure Nothing
265+
case maybeC of
266+
Just c -> loop c cs timeout
267+
Nothing -> pure $ reverse cs
251268

252269
stdinTTYHandles, ttyHandles :: MaybeT IO Handles
253270
stdinTTYHandles = do

haskeline.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Library
5353
directory>=1.1 && < 1.4, bytestring>=0.9 && < 0.12,
5454
filepath >= 1.2 && < 1.5, transformers >= 0.2 && < 0.6,
5555
process >= 1.0 && < 1.7, stm >= 2.4 && < 2.6,
56-
exceptions == 0.10.*
56+
exceptions == 0.10.*, async == 2.2.*
5757
Default-Language: Haskell98
5858
Default-Extensions:
5959
ForeignFunctionInterface, Rank2Types, FlexibleInstances,

0 commit comments

Comments
 (0)