-
Notifications
You must be signed in to change notification settings - Fork 206
Representation of arbitrary types for a plugin #14
Comments
Wouldn't it be easier if the inner protocol is plain Haskell and the outer protocol is something that can easily be called from any language? Since I'm familiar with the msgpack-rpc protocol, I'll elaborate what I mean with that. Assuming the IDE process contains plugins that run a linter on a file and another one that gives completion hints for a partial identifier. type Error = Text
data LintSuggestion = LintSuggestion
{ file :: FilePath
, line :: Int
, column :: Int
, suggestion :: Text
} deriving (MSGPACK) -- The instance will just be a map with the record fields as keys.
lintFiles :: [FilePath] -> Either Error [LintSuggestions]
-- | Apply the given suggestion. Make sure to reload the file in the editor when this function returns without an error.
applyLintSuggestion :: LintSuggestion -> Either Error ()
data CompletionSuggestion = CompletionSuggestion
{ completion :: Text
, doc :: HTML -- ^ i.e. 'Text' in html format (This is just an example.)
} deriving (MSGPACK)
-- | Return a list of possible completions for the given partial identifier
completions :: FilePath -- ^ absolute filepath to determine the context
-> Text -- ^ partial identifier
-> Either Error [CompletionSuggestion] (The result type probably has to be wrapped in an The cabal/stack/.. context should be deferrable from the filepath given to the arguments of the functions, but if more specific metadata is required, I would simply put it as required arguments. You can write a few lines of general documentation that explains how record types and result types are mapped to MSGPACK, and let haddock take care of the rest of the documentation. Say, you want to call try
let lintSuggestions = callRemoteFunction(haskellIDE, "lintFiles", [ expand('%:p') ])
for s in lintSuggestions
setmarker(s['file'], s['line'], s['suggestion'])
endfor
endtry What do we get for this specific case over the transport? According to the msgpack-rpc specification, we get a 4 element array which contains an integer value that identifies the message as a function call, an integer which serves as an identifier to match the result to the function call, the name of the function that has to be called and finally an array/list of function arguments. So, technically it shouldn't be a problem to use Haskell functions rather directly instead of defining a lot of boilerplate data types that have to be handled within Haskell and by the frontends. The arguments to the externally exposed Haskell functions are merely limited to primitive types (e.g. Although I have specifically used MSGPACK as a example here, JSON or other formats can easily be used here as well. |
@saep tend to agree. Rather plain-old-Haskell interally with an HTTP+JSON API exposed for clients. |
The intention is that the inner protocol is just plain haskell. But the plugin does not communicate with the IDE directly, this goes through a dispatcher/router first. So the idea is to come up with a common interface that any plugin can honour, to be able to work within the router/dispatcher. As such there needs to be a common type for the request to the plugin and for the response from the plugin. In my POC these are @gracjan has suggested that we use Data.Aeson.Value for this, because it is is general and represents types that can be converted. Each transport could then provide a means to serialize/deserialize the |
The current POC uses Data.Aeson.Value as the internal data structure |
Could we have a more explicit structure for Fail and Error, so that we avoid the "let's parse some cryptic error message to try to have tools deal with it" problem we have with ghc? I think a data type that contains the plugin name, command name, an error code (an arbitrary text), explicit parameters and finally a human readable error would be good. |
The intention is that the type RequestId = Int
data ChannelRequest = CReq
{ cinPlugin :: PluginId
, cinReqId :: RequestId -- ^An identifier for the request, can tie back to
-- e.g. a promise id. It is returned with the
-- ChannelResponse.
, cinReq :: IdeRequest
, cinReplyChan :: Chan ChannelResponse
} deriving Show
data ChannelResponse = CResp
{ couPlugin :: PluginId
, coutReqId :: RequestId
, coutResp :: IdeResponse
} deriving Show This sorts out
The distinction between
needs to be decided/managed. Of course this assumes that the transport used makes the |
Ah, ok. Because I was trying the console, to run ghc-mod:check in it, and it wasn't pleasant, because of
And the console I think parses all parameters as ParamText, so we always fail. So here we use error instead of returning IDEResponseFail and we repeat the plugin and command name. What I means by parameters is that we would need to outline here that the |
The console is basically useless at the moment. Not sure if the head version still has The new version treats them just as params, so should work better. See my PR from a short while ago |
Closing this as it was basically a discussion, and we now have a working way of doing it, via |
My thinking is that we have an "inner" and an "outer" protocol. The inner/logical one is processed by all the plugins, the outer one comes about when a specific inner message is wrapped in a transport for delivery on the wire.
A possible version of the inner protocol is defined in https://github.com/haskell/haskell-ide-engine/blob/plugins-definitions-play/haskell-ide-plugin-api/Haskell/Ide/PluginDescriptor.hs.
A
IdeRequest
is defined asThis initially expects any additional parameters required for a command are
String
values.The response is defined as
The
String
type is a problem.What is a good way to represent the value for the
IdeResponse
inner protocol so that it can be serialised/deserialied via an arbitrary transport mechanism, such as JSON, MSGPACK, etc.The text was updated successfully, but these errors were encountered: