This repository was archived by the owner on Oct 7, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 206
/
Copy pathIdeBackend.hs
186 lines (173 loc) · 7.15 KB
/
IdeBackend.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
module Haskell.Ide.IdeBackend
(idebackendDescriptor
) where
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Data.Aeson
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Haskell.Ide.Engine.ExtensibleState
import Haskell.Ide.Engine.MonadFunctions
import Haskell.Ide.Engine.PluginDescriptor
import Haskell.Ide.Engine.PluginUtils
import Haskell.Ide.Engine.SemanticTypes
import IdeSession
import IdeSession.Util.Logger
import Language.Haskell.GhcMod.Cradle
import Language.Haskell.GhcMod.GhcPkg
import Language.Haskell.GhcMod.Monad.Types hiding (liftIO)
import Language.Haskell.GhcMod.Types hiding (liftIO,ModuleName)
import System.FilePath
import System.Log.FastLogger
idebackendDescriptor :: TaggedPluginDescriptor _
idebackendDescriptor = PluginDescriptor
{
pdUIShortName = "ide-backend"
, pdUIOverview = "HIE plugin for ide-backend"
, pdCommands =
buildCommand typeCmd (Proxy :: Proxy "type") "type" [".hs"] (SCtxRegion :& RNil) RNil
:& RNil
, pdExposedServices = []
, pdUsedServices = []
}
-- | Get the type for a region in a file
typeCmd :: CommandFunc TypeInfo
typeCmd =
CmdSync $
\_ctxs req ->
case getParams (IdFile "file" :& IdPos "start_pos" :& IdPos "end_pos" :&
RNil)
req of
Left err -> return err
Right (ParamFile filename :& ParamPos startPos :& ParamPos endPos :& RNil) ->
do SubProcess cin cout _tid <- ensureProcessRunning filename
liftIO $
atomically $
writeTChan cin
(Type filename startPos endPos)
response <- liftIO $ atomically $ readTChan cout
case response of
TypeResp typeinfo -> return (IdeResponseOk typeinfo)
ErrorResp error' ->
return (IdeResponseError (IdeError PluginError error' Null))
Right _ ->
return (IdeResponseError
(IdeError InternalError
"IdeBackendPlugin.typesCmd: ghc’s exhaustiveness checker is broken"
Null))
instance ExtensionClass AsyncPluginState where
initialValue = APS Nothing
-- | Holds the worker process needed to cache the `IdeSession`
data AsyncPluginState = APS (Maybe SubProcess)
-- | Commands send to the worker process
data WorkerCmd = Type T.Text Pos Pos deriving (Show)
-- | Responses from the worker process
data WorkerResponse = TypeResp TypeInfo | ErrorResp T.Text
-- | The state for a worker process, consisting of two communicating
-- channels and the `ThreadId`
data SubProcess = SubProcess
{ spChIn :: TChan WorkerCmd
, spChOut :: TChan WorkerResponse
, spProcess :: ThreadId
}
-- | Try to find an already running process or start a new one if it
-- doesn’t already exist
ensureProcessRunning :: T.Text -> IdeM SubProcess
ensureProcessRunning filename =
do (APS v) <- get -- from extensible state
case v of
Nothing ->
do
-- Get the required packagedbs from ghc-mod
-- This won’t be necessary once we switch to one hie instance per project
cradle' <- findCradle' (takeDirectory (T.unpack filename))
pkgdbs <-
gmeLocal (\(GhcModEnv opts _) -> GhcModEnv opts cradle') getPackageDbStack
cin <- liftIO $ atomically newTChan
cout <- liftIO $ atomically newTChan
tid <- liftIO $ forkIO (workerProc pkgdbs cin cout)
let v' =
SubProcess {spChIn = cin
,spChOut = cout
,spProcess = tid}
put (APS (Just v')) -- into extensible state
return v'
Just v' -> return v'
-- | Log function to get ide-backend to use our logger
logFunc :: LogFunc
logFunc _loc _source level logStr =
logOtherm level (T.decodeUtf8 $ fromLogStr logStr)
-- | Long running worker process responsible for processing the commands
workerProc :: [GhcPkgDb] -> TChan WorkerCmd -> TChan WorkerResponse -> IO ()
workerProc pkgdbs cin cout =
do session <-
initSessionWithCallbacks
(IdeCallbacks logFunc)
(defaultSessionInitParams {sessionInitTargets = TargetsInclude []})
(defaultSessionConfig {configLocalWorkingDir = Nothing
,configLog = debugm
,configPackageDBStack = (GlobalPackageDB:) $ map convPkgDb pkgdbs})
updateSession session
(updateCodeGeneration True)
(debugm . show)
let loop :: Int -> IO ()
loop cnt =
do debugm "workerProc:top of loop"
req <- liftIO $ atomically $ readTChan cin
debugm $ "workerProc loop:got:" ++ show req
case req of
Type file startPos endPos ->
do liftIO $
handleTypeInfo session cout file startPos endPos
loop (cnt + 1)
loop 1
-- | Convert the package database from ghc-mod’s representation to cabal’s
-- representation
convPkgDb :: GhcPkgDb -> PackageDB
convPkgDb GlobalDb = GlobalPackageDB
convPkgDb UserDb = UserPackageDB
convPkgDb (PackageDb db) = SpecificPackageDB db
-- | Find the type for a region in a file. Add the supplied file to
-- the session targets.
handleTypeInfo :: IdeSession
-> TChan WorkerResponse
-> T.Text
-> Pos
-> Pos
-> IO ()
handleTypeInfo session cout file (startLine,startCol) (endLine,endCol) =
do updateSession session
(updateTargets (TargetsInclude . pure $ T.unpack file))
(debugm . show)
errors <- getSourceErrors session
case errors of
(_:_) -> atomically . writeTChan cout $ ErrorResp (T.pack $ show errors)
[] ->
do filemap <- getFileMap session
expTypes <- getExpTypes session
case filemap (T.unpack file) of
Nothing ->
atomically . writeTChan cout $ ErrorResp "No module found"
Just mod' ->
case expTypes (moduleName mod')
SourceSpan {spanFilePath = T.unpack file
,spanFromLine = startLine
,spanToLine = endLine
,spanFromColumn = startCol
,spanToColumn = endCol} of
ts ->
atomically . writeTChan cout . TypeResp . TypeInfo $
map toTypeResult ts
where toTypeResult
:: (SourceSpan,T.Text) -> TypeResult
toTypeResult (SourceSpan{..},t) =
TypeResult {trStart = (spanFromLine,spanFromColumn)
,trEnd = (spanToLine,spanToColumn)
,trText = t}