neovimhaskell / nvim-hs

Neovim API for Haskell plugins as well as the plugin provider
Other
267 stars 18 forks source link

Improve feedback when plugin code throws exception #96

Open saep opened 2 years ago

saep commented 2 years ago

@isovector wrote in #95 :

One side effect of this patch is that unhandled exceptions no longer end up in the log --- plugins just mysteriously stop working.

Is this actually true? I've written and incredibly useful plugin below and I receive log messages and I see error messages in neovim.

When calling the command :ThrowExceptionCommand:

[Socket Reader : DEBUG] Received: ObjectArray [ObjectInt 0,ObjectInt 2,ObjectString "ThrowExceptionCommand:command",ObjectArray [ObjectArray [],ObjectInt 0]]
[Socket Reader : DEBUG] Executing stateful function with ID: Just 2
[EventHandler : DEBUG] Sending: Response 2 (Left (ObjectBinary "ErrorResult throwExceptionFunction (ObjectArray [ObjectInt 1,ObjectBinary \"Simulate neovim exception\"])"))

image

When calling the function with :call ThrowExceptionFunction():

[Socket Reader : DEBUG] Received: ObjectArray [ObjectInt 2,ObjectString "ThrowExceptionFunction:function",ObjectArray [ObjectArray []]]
[Socket Reader : DEBUG] Executing stateful function with ID: Nothing
[EventHandler : DEBUG] Sending: Request (Request {reqMethod = F "nvim_err_writeln", reqId = 9, reqArgs = [ObjectBinary "ErrorResult throwExceptionFunction (ObjectArray [ObjectInt 1,ObjectBinary \"Simulate neovim exception\"])"]})
[Socket Reader : DEBUG] Received: ObjectArray [ObjectInt 1,ObjectInt 9,ObjectNil,ObjectNil]

image

When an autocmd is executed:

[Socket Reader : DEBUG] Received: ObjectArray [ObjectInt 2,ObjectString "ThrowExceptionAutoCommand",ObjectArray []]
[Socket Reader : DEBUG] Received: ObjectArray [ObjectInt 2,ObjectString "ThrowExceptionAutoCommand",ObjectArray []]
[Socket Reader : DEBUG] Executing stateful function with ID: Nothing
[Socket Reader : DEBUG] Executing stateful function with ID: Nothing
[EventHandler : DEBUG] Sending: Request (Request {reqMethod = F "nvim_err_writeln", reqId = 8, reqArgs = [ObjectBinary "ErrorResult throwExceptionFunction (ObjectArray [ObjectInt 1,ObjectBinary \"Simulate neovim exception\"])"]})
[EventHandler : DEBUG] Sending: Request (Request {reqMethod = F "nvim_err_writeln", reqId = 8, reqArgs = [ObjectBinary "ErrorResult throwExceptionFunction (ObjectArray [ObjectInt 1,ObjectBinary \"Simulate neovim exception\"])"]})

image

{-# LANGUAGE OverloadedStrings #-}
module Neovim.Example.Plugin.Throws where

import Neovim
import UnliftIO (throwIO)

throwExceptionFunction :: Neovim env ()
throwExceptionFunction = throwIO $ ErrorResult
        "throwExceptionFunction"
        (toObject (1 :: Int32, "Simulate neovim exception" :: String))

throwExceptionCommand :: CommandArguments -> Neovim env ()
throwExceptionCommand _ = throwExceptionFunction

throwExceptionAutoCommand :: Neovim env ()
throwExceptionAutoCommand = throwExceptionFunction
{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
module Neovim.Example.Plugin ( plugin ) where

import Neovim.Example.Plugin.Throws (throwExceptionAutoCommand, throwExceptionCommand, throwExceptionFunction)

plugin :: Neovim () NeovimPlugin
plugin = do
    wrapPlugin Plugin
        { environment = ()
        , exports =
            [ $(command' 'throwExceptionCommand) [CmdBang]
            , $(function' 'throwExceptionFunction) Async
            , $(autocmd 'throwExceptionAutoCommand) "FileType" Async def{acmdPattern = "vim"}
            ]
        }
isovector commented 2 years ago

I experienced this issue when calling vim from an async thread! Maybe I am confused that it never used to report exceptions in async threads?

I can put together an mvp later today

saep commented 2 years ago

The following code swallows exceptions and if the second to last line is not commented, and exception is visible.

neovimAsync :: (MonadUnliftIO m) => m a -> m (Async a)
neovimAsync m =
  withRunInIO $ \lower ->
    liftIO $ async $ lower m

throwExceptionFunctionAsync :: Neovim env Bool
throwExceptionFunctionAsync = do
  spawned <- neovimAsync $ throwExceptionFunction
  -- liftIO $ waitAnyCancel [spawned]
  pure True

I barely ever program in Haskell, but to me it seems that this is kind of expected behavior from the async library?

isovector commented 2 years ago

Yeah, might be. Perhaps it's worth adding an async helper to the library that deals with this --- rather than doing the whole withRunInIO dance. Would you be interested in a PR?

saep commented 2 years ago

Sure!

saep commented 2 years ago

Or maybe just use: https://hackage.haskell.org/package/unliftio-0.2.20.1/docs/UnliftIO-Async.html It's already a dependency and used internally.