99{-# LANGUAGE TupleSections #-}
1010{-# LANGUAGE TypeFamilies #-}
1111
12- module Ide.Plugin.Cabal (descriptor , Log (.. )) where
12+ module Ide.Plugin.Cabal (descriptor , Log (.. )) where
1313
1414import Control.Concurrent.STM
1515import Control.Concurrent.Strict
@@ -22,21 +22,23 @@ import Data.HashMap.Strict (HashMap)
2222import qualified Data.HashMap.Strict as HashMap
2323import qualified Data.List.NonEmpty as NE
2424import qualified Data.Text.Encoding as Encoding
25+ import qualified Data.Text.Utf16.Rope as Rope
2526import Data.Typeable
2627import Development.IDE as D
2728import Development.IDE.Core.Shake (restartShakeSession )
2829import qualified Development.IDE.Core.Shake as Shake
2930import Development.IDE.Graph (alwaysRerun )
31+ import Distribution.Compat.Lens ((^.) )
3032import GHC.Generics
33+ import qualified Ide.Plugin.Cabal.Completions as Completions
3134import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
3235import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
3336import qualified Ide.Plugin.Cabal.Parse as Parse
34- import Ide.Plugin.Config (Config )
3537import Ide.Types
38+ import qualified Language.LSP.Protocol.Lens as JL
3639import qualified Language.LSP.Protocol.Message as LSP
3740import Language.LSP.Protocol.Types
38- import qualified Language.LSP.Protocol.Types as LSP
39- import Language.LSP.Server (LspM )
41+ import Language.LSP.Server (LspM , getVirtualFile )
4042import qualified Language.LSP.VFS as VFS
4143
4244data Log
@@ -47,12 +49,14 @@ data Log
4749 | LogDocSaved Uri
4850 | LogDocClosed Uri
4951 | LogFOI (HashMap NormalizedFilePath FileOfInterestStatus )
50- deriving Show
52+ | LogCompletionContext Completions. Context Position
53+ | LogCompletions Completions. Log
54+ deriving (Show )
5155
5256instance Pretty Log where
5357 pretty = \ case
5458 LogShake log' -> pretty log'
55- LogModificationTime nfp modTime ->
59+ LogModificationTime nfp modTime ->
5660 " Modified:" <+> pretty (fromNormalizedFilePath nfp) <+> pretty (show modTime)
5761 LogDocOpened uri ->
5862 " Opened text document:" <+> pretty (getUri uri)
@@ -64,12 +68,18 @@ instance Pretty Log where
6468 " Closed text document:" <+> pretty (getUri uri)
6569 LogFOI files ->
6670 " Set files of interest to:" <+> viaShow files
67-
71+ LogCompletionContext context position->
72+ " Determined completion context:" <+> viaShow context
73+ <+> " for cursor position:" <+> viaShow position
74+ LogCompletions logs -> pretty logs
6875
6976descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
7077descriptor recorder plId = (defaultCabalPluginDescriptor plId)
7178 { pluginRules = cabalRules recorder
72- , pluginHandlers = mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
79+ , pluginHandlers = mconcat
80+ [ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
81+ , mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
82+ ]
7383 , pluginNotificationHandlers = mconcat
7484 [ mkPluginNotificationHandler LSP. SMethod_TextDocumentDidOpen $
7585 \ ide vfs _ (DidOpenTextDocumentParams TextDocumentItem {_uri,_version}) -> liftIO $ do
@@ -104,7 +114,7 @@ descriptor recorder plId = (defaultCabalPluginDescriptor plId)
104114 log' = logWith recorder
105115
106116 whenUriFile :: Uri -> (NormalizedFilePath -> IO () ) -> IO ()
107- whenUriFile uri act = whenJust (LSP. uriToFilePath uri) $ act . toNormalizedFilePath'
117+ whenUriFile uri act = whenJust (uriToFilePath uri) $ act . toNormalizedFilePath'
108118
109119-- | Helper function to restart the shake session, specifically for modifying .cabal files.
110120-- No special logic, just group up a bunch of functions you need for the base
@@ -124,9 +134,9 @@ restartCabalShakeSession shakeExtras vfs file actionMsg = do
124134-- ----------------------------------------------------------------
125135
126136data ParseCabal = ParseCabal
127- deriving (Eq , Show , Typeable , Generic )
137+ deriving (Eq , Show , Typeable , Generic )
128138instance Hashable ParseCabal
129- instance NFData ParseCabal
139+ instance NFData ParseCabal
130140
131141type instance RuleResult ParseCabal = ()
132142
@@ -141,7 +151,8 @@ cabalRules recorder = do
141151 (t, mCabalSource) <- use_ GetFileContents file
142152 log' Debug $ LogModificationTime file t
143153 contents <- case mCabalSource of
144- Just sources -> pure $ Encoding. encodeUtf8 sources
154+ Just sources ->
155+ pure $ Encoding. encodeUtf8 sources
145156 Nothing -> do
146157 liftIO $ BS. readFile $ fromNormalizedFilePath file
147158
@@ -160,15 +171,16 @@ cabalRules recorder = do
160171 -- Must be careful to not impede the performance too much. Crucial to
161172 -- a snappy IDE experience.
162173 kick
163- where
164- log' = logWith recorder
174+ where
175+ log' = logWith recorder
165176
166- -- | This is the kick function for the cabal plugin.
167- -- We run this action, whenever we shake session us run/restarted, which triggers
168- -- actions to produce diagnostics for cabal files.
169- --
170- -- It is paramount that this kick-function can be run quickly, since it is a blocking
171- -- function invocation.
177+ {- | This is the kick function for the cabal plugin.
178+ We run this action, whenever we shake session us run/restarted, which triggers
179+ actions to produce diagnostics for cabal files.
180+
181+ It is paramount that this kick-function can be run quickly, since it is a blocking
182+ function invocation.
183+ -}
172184kick :: Action ()
173185kick = do
174186 files <- HashMap. keys <$> getCabalFilesOfInterestUntracked
@@ -190,69 +202,100 @@ licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri)
190202-- Cabal file of Interest rules and global variable
191203-- ----------------------------------------------------------------
192204
193- -- | Cabal files that are currently open in the lsp-client.
194- -- Specific actions happen when these files are saved, closed or modified,
195- -- such as generating diagnostics, re-parsing, etc...
196- --
197- -- We need to store the open files to parse them again if we restart the shake session.
198- -- Restarting of the shake session happens whenever these files are modified.
205+ {- | Cabal files that are currently open in the lsp-client.
206+ Specific actions happen when these files are saved, closed or modified,
207+ such as generating diagnostics, re-parsing, etc...
208+
209+ We need to store the open files to parse them again if we restart the shake session.
210+ Restarting of the shake session happens whenever these files are modified.
211+ -}
199212newtype OfInterestCabalVar = OfInterestCabalVar (Var (HashMap NormalizedFilePath FileOfInterestStatus ))
200213
201214instance Shake. IsIdeGlobal OfInterestCabalVar
202215
203216data IsCabalFileOfInterest = IsCabalFileOfInterest
204- deriving (Eq , Show , Typeable , Generic )
217+ deriving (Eq , Show , Typeable , Generic )
205218instance Hashable IsCabalFileOfInterest
206- instance NFData IsCabalFileOfInterest
219+ instance NFData IsCabalFileOfInterest
207220
208221type instance RuleResult IsCabalFileOfInterest = CabalFileOfInterestResult
209222
210223data CabalFileOfInterestResult = NotCabalFOI | IsCabalFOI FileOfInterestStatus
211224 deriving (Eq , Show , Typeable , Generic )
212225instance Hashable CabalFileOfInterestResult
213- instance NFData CabalFileOfInterestResult
226+ instance NFData CabalFileOfInterestResult
214227
215- -- | The rule that initialises the files of interest state.
216- --
217- -- Needs to be run on start-up.
228+ {- | The rule that initialises the files of interest state.
229+
230+ Needs to be run on start-up.
231+ -}
218232ofInterestRules :: Recorder (WithPriority Log ) -> Rules ()
219233ofInterestRules recorder = do
220- Shake. addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap. empty)
221- Shake. defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \ IsCabalFileOfInterest f -> do
222- alwaysRerun
223- filesOfInterest <- getCabalFilesOfInterestUntracked
224- let foi = maybe NotCabalFOI IsCabalFOI $ f `HashMap.lookup` filesOfInterest
225- fp = summarize foi
226- res = (Just fp, Just foi)
227- return res
228- where
229- summarize NotCabalFOI = BS. singleton 0
230- summarize (IsCabalFOI OnDisk ) = BS. singleton 1
231- summarize (IsCabalFOI (Modified False )) = BS. singleton 2
232- summarize (IsCabalFOI (Modified True )) = BS. singleton 3
234+ Shake. addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap. empty)
235+ Shake. defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \ IsCabalFileOfInterest f -> do
236+ alwaysRerun
237+ filesOfInterest <- getCabalFilesOfInterestUntracked
238+ let foi = maybe NotCabalFOI IsCabalFOI $ f `HashMap.lookup` filesOfInterest
239+ fp = summarize foi
240+ res = (Just fp, Just foi)
241+ return res
242+ where
243+ summarize NotCabalFOI = BS. singleton 0
244+ summarize (IsCabalFOI OnDisk ) = BS. singleton 1
245+ summarize (IsCabalFOI (Modified False )) = BS. singleton 2
246+ summarize (IsCabalFOI (Modified True )) = BS. singleton 3
233247
234248getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus )
235249getCabalFilesOfInterestUntracked = do
236- OfInterestCabalVar var <- Shake. getIdeGlobalAction
237- liftIO $ readVar var
250+ OfInterestCabalVar var <- Shake. getIdeGlobalAction
251+ liftIO $ readVar var
238252
239253addFileOfInterest :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> FileOfInterestStatus -> IO ()
240254addFileOfInterest recorder state f v = do
241- OfInterestCabalVar var <- Shake. getIdeGlobalState state
242- (prev, files) <- modifyVar var $ \ dict -> do
243- let (prev, new) = HashMap. alterF (, Just v) f dict
244- pure (new, (prev, new))
245- when (prev /= Just v) $ do
246- join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
247- log' Debug $ LogFOI files
248- where
249- log' = logWith recorder
255+ OfInterestCabalVar var <- Shake. getIdeGlobalState state
256+ (prev, files) <- modifyVar var $ \ dict -> do
257+ let (prev, new) = HashMap. alterF (,Just v) f dict
258+ pure (new, (prev, new))
259+ when (prev /= Just v) $ do
260+ join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
261+ log' Debug $ LogFOI files
262+ where
263+ log' = logWith recorder
250264
251265deleteFileOfInterest :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> IO ()
252266deleteFileOfInterest recorder state f = do
253- OfInterestCabalVar var <- Shake. getIdeGlobalState state
254- files <- modifyVar' var $ HashMap. delete f
255- join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
256- log' Debug $ LogFOI files
257- where
258- log' = logWith recorder
267+ OfInterestCabalVar var <- Shake. getIdeGlobalState state
268+ files <- modifyVar' var $ HashMap. delete f
269+ join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
270+ log' Debug $ LogFOI files
271+ where
272+ log' = logWith recorder
273+
274+ -- ----------------------------------------------------------------
275+ -- Completion
276+ -- ----------------------------------------------------------------
277+
278+ completion :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
279+ completion recorder _ide _ complParams = do
280+ let (TextDocumentIdentifier uri) = complParams ^. JL. textDocument
281+ position = complParams ^. JL. position
282+ contents <- getVirtualFile $ toNormalizedUri uri
283+ fmap (Right . InL ) $ case (contents, uriToFilePath' uri) of
284+ (Just cnts, Just path) -> do
285+ pref <- VFS. getCompletionPrefix position cnts
286+ liftIO $ result pref path cnts
287+ _ -> return []
288+ where
289+ result :: Maybe VFS. PosPrefixInfo -> FilePath -> VFS. VirtualFile -> IO [CompletionItem ]
290+ result Nothing _ _ = pure []
291+ result (Just prefix) fp cnts
292+ | Just ctx <- context = do
293+ logWith recorder Debug $ LogCompletionContext ctx pos
294+ let completer = Completions. contextToCompleter ctx
295+ completions <- completer (cmapWithPrio LogCompletions recorder) completionContext
296+ pure $ Completions. mkCompletionItems completions
297+ | otherwise = pure []
298+ where
299+ pos = VFS. cursorPos prefix
300+ context = Completions. getContext completionContext (Rope. lines $ cnts ^. VFS. file_text)
301+ completionContext = Completions. getCabalCompletionContext fp prefix
0 commit comments