@@ -14,7 +14,8 @@ import Data.Maybe (catMaybes, fromMaybe,
1414 isJust )
1515import qualified Data.Text as T
1616import qualified Data.Text.IO as T
17- import Development.IDE (Action , Rules )
17+ import Development.IDE (Action , Rules ,
18+ hDuplicateTo' )
1819import Development.IDE.Core.Debouncer (Debouncer ,
1920 newAsyncDebouncer )
2021import Development.IDE.Core.FileStore (makeVFSHandle )
@@ -54,6 +55,7 @@ import Development.IDE.Types.Options (IdeGhcSession,
5455import Development.IDE.Types.Shake (Key (Key ))
5556import Development.Shake (action )
5657import GHC.IO.Encoding (setLocaleEncoding )
58+ import GHC.IO.Handle (hDuplicate )
5759import HIE.Bios.Cradle (findCradle )
5860import Ide.Plugin.Config (CheckParents (NeverCheck ),
5961 Config ,
@@ -68,11 +70,12 @@ import System.Exit (ExitCode (ExitFailure),
6870 exitWith )
6971import System.FilePath (takeExtension ,
7072 takeFileName )
71- import System.IO (BufferMode (LineBuffering ),
73+ import System.IO (BufferMode (LineBuffering , NoBuffering ),
74+ Handle , hFlush ,
7275 hPutStrLn ,
7376 hSetBuffering ,
7477 hSetEncoding , stderr ,
75- stdout , utf8 )
78+ stdin , stdout , utf8 )
7679import System.Time.Extra (offsetTime ,
7780 showDuration )
7881import Text.Printf (printf )
@@ -90,6 +93,8 @@ data Arguments = Arguments
9093 , argsDefaultHlsConfig :: Config
9194 , argsGetHieDbLoc :: FilePath -> IO FilePath -- ^ Map project roots to the location of the hiedb for the project
9295 , argsDebouncer :: IO (Debouncer NormalizedUri ) -- ^ Debouncer used for diagnostics
96+ , argsHandleIn :: IO Handle
97+ , argsHandleOut :: IO Handle
9398 }
9499
95100instance Default Arguments where
@@ -106,6 +111,21 @@ instance Default Arguments where
106111 , argsDefaultHlsConfig = def
107112 , argsGetHieDbLoc = getHieDbLoc
108113 , argsDebouncer = newAsyncDebouncer
114+ , argsHandleIn = pure stdin
115+ , argsHandleOut = do
116+ -- Move stdout to another file descriptor and duplicate stderr
117+ -- to stdout. This guards against stray prints from corrupting the JSON-RPC
118+ -- message stream.
119+ newStdout <- hDuplicate stdout
120+ stderr `hDuplicateTo'` stdout
121+ hSetBuffering stdout NoBuffering
122+
123+ -- Print out a single space to assert that the above redirection works.
124+ -- This is interleaved with the logger, hence we just print a space here in
125+ -- order not to mess up the output too much. Verified that this breaks
126+ -- the language server tests without the redirection.
127+ putStr " " >> hFlush stdout
128+ return newStdout
109129 }
110130
111131-- | Cheap stderr logger that relies on LineBuffering
@@ -130,13 +150,15 @@ defaultMain Arguments{..} = do
130150 rules = argsRules >> pluginRules plugins
131151
132152 debouncer <- argsDebouncer
153+ inH <- argsHandleIn
154+ outH <- argsHandleOut
133155
134156 case argFiles of
135157 Nothing -> do
136158 t <- offsetTime
137159 hPutStrLn stderr " Starting LSP server..."
138160 hPutStrLn stderr " If you are seeing this in a terminal, you probably should have run ghcide WITHOUT the --lsp option!"
139- runLanguageServer options argsGetHieDbLoc argsOnConfigChange (pluginHandlers plugins) $ \ env vfs rootPath hiedb hieChan -> do
161+ runLanguageServer options inH outH argsGetHieDbLoc argsOnConfigChange (pluginHandlers plugins) $ \ env vfs rootPath hiedb hieChan -> do
140162 t <- t
141163 hPutStrLn stderr $ " Started LSP server in " ++ showDuration t
142164
0 commit comments