Skip to content

Commit b564d7a

Browse files
committed
Fix #88 Don't skip "\ESC...\STX" sequences
For ANSI-capable terminals, this reverses commit f827f10 of 11 September 2010 (about 5 years before the November 2015 Update to Windows 10 made the native consoles on Windows 10 ANSI-capable). It uses the same logic as the ansi-terminal package to determine if the terminal is ANSI-capable.
1 parent 30aafaf commit b564d7a

File tree

2 files changed

+61
-12
lines changed

2 files changed

+61
-12
lines changed

System/Console/Haskeline/Backend/Win32.hsc

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,25 @@ module System.Console.Haskeline.Backend.Win32(
88

99

1010
import System.IO
11+
import System.IO.Unsafe (unsafePerformIO)
1112
import Foreign
1213
import Foreign.C
1314
#if MIN_VERSION_Win32(2,9,0)
14-
import System.Win32 hiding (multiByteToWideChar, setConsoleMode, getConsoleMode)
15+
import System.Win32 hiding (getConsoleMode, isMinTTY, multiByteToWideChar,
16+
setConsoleMode, try)
17+
#elif MIN_VERSION_Win32(2,5,0)
18+
import System.Win32 hiding (isMintty, multiByteToWideChar, try)
1519
#else
16-
import System.Win32 hiding (multiByteToWideChar)
20+
import System.Win32 hiding (multiByteToWideChar, try)
21+
import System.Console.Mintty (isMinTTYHandle)
1722
#endif
1823
import Graphics.Win32.Misc(getStdHandle, sTD_OUTPUT_HANDLE)
1924
import Data.List(intercalate)
2025
import Control.Concurrent.STM
2126
import Control.Concurrent hiding (throwTo)
2227
import Data.Char(isPrint, chr, ord)
2328
import Data.Maybe(mapMaybe)
24-
import Control.Exception (IOException, throwTo)
29+
import Control.Exception (IOException, SomeException, throwTo, try)
2530
import Control.Monad
2631
import Control.Monad.Catch
2732
( MonadThrow
@@ -261,16 +266,26 @@ foreign import WINDOWS_CCONV "windows.h GetConsoleMode" c_GetConsoleMode
261266
foreign import WINDOWS_CCONV "windows.h SetConsoleMode" c_SetConsoleMode
262267
:: HANDLE -> DWORD -> IO Bool
263268

269+
#if !MIN_VERSION_Win32(2,8,5)
270+
eNABLE_VIRTUAL_TERMINAL_PROCESSING :: DWORD
271+
eNABLE_VIRTUAL_TERMINAL_PROCESSING = 4
272+
#endif
273+
264274
withWindowMode :: (MonadIO m, MonadMask m) => Handles -> m a -> m a
265275
withWindowMode hs f = do
266276
let h = hIn hs
267277
bracket (getConsoleMode h) (setConsoleMode h)
268278
$ \m -> setConsoleMode h (m .|. (#const ENABLE_WINDOW_INPUT)) >> f
269-
where
270-
getConsoleMode h = liftIO $ alloca $ \p -> do
271-
failIfFalse_ "GetConsoleMode" $ c_GetConsoleMode h p
272-
peek p
273-
setConsoleMode h m = liftIO $ failIfFalse_ "SetConsoleMode" $ c_SetConsoleMode h m
279+
280+
getConsoleMode :: (MonadIO m) => HANDLE -> m DWORD
281+
getConsoleMode h = liftIO $ alloca $ \p -> do
282+
failIfFalse_ "GetConsoleMode" $ c_GetConsoleMode h p
283+
peek p
284+
285+
setConsoleMode :: (MonadIO m) => HANDLE -> DWORD -> m ()
286+
setConsoleMode h m =
287+
liftIO $ failIfFalse_ "SetConsoleMode" $ c_SetConsoleMode h m
288+
274289

275290
----------------------------
276291
-- Drawing
@@ -369,9 +384,11 @@ crlf :: String
369384
crlf = "\r\n"
370385

371386
instance (MonadMask m, MonadIO m, MonadReader Layout m) => Term (Draw m) where
372-
drawLineDiff (xs1,ys1) (xs2,ys2) = let
373-
fixEsc = filter ((/= '\ESC') . baseChar)
374-
in drawLineDiffWin (fixEsc xs1, fixEsc ys1) (fixEsc xs2, fixEsc ys2)
387+
drawLineDiff (xs1,ys1) (xs2,ys2) = if aNSISupport
388+
then drawLineDiffWin (xs1,ys1) (xs2,ys2)
389+
else
390+
let fixEsc = filter ((/= '\ESC') . baseChar)
391+
in drawLineDiffWin (fixEsc xs1, fixEsc ys1) (fixEsc xs2, fixEsc ys2)
375392
-- TODO now that we capture resize events.
376393
-- first, looks like the cursor stays on the same line but jumps
377394
-- to the beginning if cut off.
@@ -567,3 +584,35 @@ clearScreen = do
567584
liftIO $ fillConsoleChar h ' ' windowSize origin
568585
liftIO $ fillConsoleAttribute h attr windowSize origin
569586
setPos origin
587+
588+
-- | This function assumes that once it is first established whether or not the
589+
-- Windows console is ANSI-capable, that will not change.
590+
{-# NOINLINE aNSISupport #-}
591+
aNSISupport :: Bool
592+
aNSISupport = unsafePerformIO $ withHandleToHANDLE stdout $ withHANDLE
593+
(return False) -- Invalid handle or no handle
594+
$ \h -> do
595+
tryMode <- try (getConsoleMode h) :: IO (Either SomeException DWORD)
596+
case tryMode of
597+
Left _ -> do -- No ConHost mode
598+
isMinTTY <- isMinTTYHandle h
599+
if isMinTTY
600+
then return True -- 'mintty' terminal emulator
601+
else return False -- Not sure!
602+
Right mode -> if mode .&. eNABLE_VIRTUAL_TERMINAL_PROCESSING /= 0
603+
then return True -- VT processing already enabled
604+
else do
605+
let mode' = mode .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING
606+
trySetMode <- try (setConsoleMode h mode')
607+
:: IO (Either SomeException ())
608+
case trySetMode of
609+
Left _ -> return False -- Can't enable VT processing
610+
Right () -> return True -- VT processing enabled
611+
where
612+
-- | This function applies another to the Windows handle, if the handle is
613+
-- valid. If it is invalid, the specified default action is returned.
614+
withHANDLE :: IO a -> (HANDLE -> IO a) -> HANDLE -> IO a
615+
withHANDLE invalid action h =
616+
if h == iNVALID_HANDLE_VALUE || h == nullHANDLE
617+
then invalid -- Invalid handle or no handle
618+
else action h

haskeline.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Library
9494
c-sources: cbits/h_wcwidth.c
9595

9696
if os(windows) {
97-
Build-depends: Win32>=2.0
97+
Build-depends: Win32>=2.0, mintty
9898
Other-modules: System.Console.Haskeline.Backend.Win32
9999
System.Console.Haskeline.Backend.Win32.Echo
100100
c-sources: cbits/win_console.c

0 commit comments

Comments
 (0)