haskell-language-server icon indicating copy to clipboard operation
haskell-language-server copied to clipboard

Capture stdout and stderr in eval plugin

Open anka-213 opened this issue 4 years ago • 7 comments

Steps to reproduce

Run the "Evaluate..." code lens on the following code

-- >>> putStrLn "Hello"

or

-- >>> hPutStrLn stderr "Hello"

Expected behaviour

It should put

-- Hello

in a comment below, just like it would do with normal return values.

Actual behaviour

In the stderr case, the output is sent to the debug output together with the logs. In the stdout case I believe the output is sent in the LSP channel directly to the editor as is. If you're lucky, the editor's LSP parser will handle the error gracefully instead of just crashing or something.

anka-213 avatar Jun 25 '21 06:06 anka-213

I would like to try implementing the stdout/stderr capture :slightly_smiling_face:

In #2775 it was suggested by @pepeiborra to use -fexternal-interpreter from GHC(i).

So far I could not figure out from GHC docs and code what happens to stdout when GHCi uses the external interpreter (which seems similar to our use case) and if it could be captured :confused:

xsebek avatar May 20 '22 12:05 xsebek

I wonder if a simple redirect of stdout using hDuplicateTo like silently does wouldn't actually work? As long as we make sure that all the output that should be sent to the actual stdout/stderr gets sent to the backup copy of the handles and not the ones currently named stdout/stderr. It should be easy to do for stdout, since (afaik) all the lsp communication is done at the same place, but might be more difficult to do for stderr, since code that writes to stderr is more spread out over the codebase.

There might also be an issue with this approach if we want to run multiple eval commands in parallel, since afaik, stdout is global per process.

hDuplicateTo is already done to prevent crashes from accidental prints: https://github.com/haskell/haskell-language-server/blob/65241229275e6a0950d6ca982456ed9819361b8a/ghcide/src/Development/IDE/Main.hs#L260-L273

Maybe all that's needed is to redirect stderr to a temporary buffer during eval and hope that there are no stray prints to stderr/stdout during that time. But of course, if we can get a more robust implementation using -fexternal-interpreter, that's better.

anka-213 avatar May 21 '22 14:05 anka-213

Hey @anka-213, thanks for pointing the redirection out, I was not aware of it!

The "global per process" part is exactly why we would like to use the external interpreter process as GHC and GHCi do. Their use-case is geared more toward Template Haskell evaluation, but presumably, the output of evaluated code is captured/redirected somewhere too.

But if we can hack in some stdout support without messing it up for others, then I am all for it. :slightly_smiling_face:

xsebek avatar May 21 '22 22:05 xsebek

Using the external interpreter is the right fix, but I think the last time I tried it didn't support graceful restarts.

pepeiborra avatar May 22 '22 11:05 pepeiborra

One of the reasons why this feature is good to have is to work around the allow one to print non-ASCII characters, which show always escapes.

Here is a workaround for this that I found that may be helpful until this gets fixed, or may be helpful in finding a solution.

It seems that going via the pretty function in the package prettyprinter has the desired behaviour:

import Prettyprinter

-- >>> pretty "λ \n λ"
-- λ 
--  λ

fmehta avatar Mar 14 '23 17:03 fmehta

Another reason why capturing stdout is indeed needed: The workaround mentioned in the docs (i.e. use error) is not compatible with doctest-parallel (and, I imagine, with other similar doctest implementations):

>>> prettyPrint [1..3]
[ 1
, 2
, 3
]
[ERROR  ] [ThreadId 58] failure in expression `prettyPrint [1..3]'
[ERROR  ] [ThreadId 58] expected: [ 1
[ERROR  ] [ThreadId 58]           , 2
[ERROR  ] [ThreadId 58]           , 3
[ERROR  ] [ThreadId 58]           ]
[ERROR  ] [ThreadId 58]  but got: *** Exception: [ 1
[ERROR  ] [ThreadId 58]           ^
[ERROR  ] [ThreadId 58]           , 2
[ERROR  ] [ThreadId 58]           , 3
[ERROR  ] [ThreadId 58]           ]
[ERROR  ] [ThreadId 58]           CallStack (from HasCallStack):
[ERROR  ] [ThreadId 58]             error, called at <interactive>:39:17 in interactive:Ghci5

(Now, I could add wildcards ... to make it work, but they would be gone every time I press "Refresh", so that's no good).

dcastro avatar Jul 14 '23 08:07 dcastro

In the stdout case I believe the output is sent in the LSP channel directly to the editor as is. If you're lucky, the editor's LSP parser will handle the error gracefully instead of just crashing or something.

Is this still the case? I do not observe that the stdout output is also printed into the LSP channel in my editor. But maybe it is not showing it because it is not able to parse it properly.

Also, I do not observe any differences between printing to stdout or stderr. I do observe the output of both handles in the log buffer of HLS.

dschrempf avatar Sep 13 '25 12:09 dschrempf