From edcf1f011590bdad92f9f2c6e1d5563633108a56 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Sat, 12 Oct 2024 19:35:33 +0200 Subject: [PATCH 01/11] wip --- haskell-language-server.cabal | 1 + .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 440 +++++++++++------- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 9 + .../src/Ide/Plugin/Cabal/Dependencies.hs | 54 +++ .../src/Ide/Plugin/Cabal/Parse.hs | 4 + 5 files changed, 336 insertions(+), 172 deletions(-) create mode 100644 plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index 447882a61e..65192ed070 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -237,6 +237,7 @@ library hls-cabal-plugin buildable: False exposed-modules: Ide.Plugin.Cabal + Ide.Plugin.Cabal.Dependencies Ide.Plugin.Cabal.Diagnostics Ide.Plugin.Cabal.Completion.CabalFields Ide.Plugin.Cabal.Completion.Completer.FilePath diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index d68f61639a..e23fbd0d39 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -1,71 +1,80 @@ -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} module Ide.Plugin.Cabal (descriptor, haskellInteractionDescriptor, Log (..)) where -import Control.Concurrent.Strict -import Control.DeepSeq -import Control.Lens ((^.)) -import Control.Monad.Extra -import Control.Monad.IO.Class -import Control.Monad.Trans.Class (lift) -import Control.Monad.Trans.Maybe (runMaybeT) -import qualified Data.ByteString as BS -import Data.Hashable -import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HashMap -import qualified Data.List.NonEmpty as NE -import qualified Data.Maybe as Maybe -import qualified Data.Text as T -import qualified Data.Text.Encoding as Encoding -import Data.Text.Utf16.Rope.Mixed as Rope -import Data.Typeable -import Development.IDE as D -import Development.IDE.Core.FileStore (getVersionedTextDoc) -import Development.IDE.Core.PluginUtils -import Development.IDE.Core.Shake (restartShakeSession) -import qualified Development.IDE.Core.Shake as Shake -import Development.IDE.Graph (Key, - alwaysRerun) -import Development.IDE.LSP.HoverDefinition (foundHover) -import qualified Development.IDE.Plugin.Completions.Logic as Ghcide -import Development.IDE.Types.Shake (toKey) -import qualified Distribution.Fields as Syntax -import Distribution.Package (Dependency) -import Distribution.PackageDescription (allBuildDepends, - depPkgName, - unPackageName) -import Distribution.PackageDescription.Configuration (flattenPackageDescription) -import qualified Distribution.Parsec.Position as Syntax -import GHC.Generics -import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields -import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes -import qualified Ide.Plugin.Cabal.Completion.Completions as Completions -import Ide.Plugin.Cabal.Completion.Types (ParseCabalCommonSections (ParseCabalCommonSections), - ParseCabalFields (..), - ParseCabalFile (..)) -import qualified Ide.Plugin.Cabal.Completion.Types as Types -import Ide.Plugin.Cabal.Definition (gotoDefinition) -import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics -import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest -import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest -import Ide.Plugin.Cabal.Orphans () -import Ide.Plugin.Cabal.Outline -import qualified Ide.Plugin.Cabal.Parse as Parse -import Ide.Plugin.Error -import Ide.Types -import qualified Language.LSP.Protocol.Lens as JL -import qualified Language.LSP.Protocol.Message as LSP -import Language.LSP.Protocol.Types -import qualified Language.LSP.VFS as VFS -import Text.Regex.TDFA - - -import qualified Data.Text () -import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd +import Control.Concurrent.Strict +import Control.DeepSeq +import Control.Lens ((^.)) +import Control.Lens.Fold ((^?)) +import Control.Lens.Prism (_Just) +import Control.Monad.Extra +import Control.Monad.IO.Class +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Maybe (runMaybeT) +import Data.ByteString qualified as BS +import Data.HashMap.Strict (HashMap) +import Data.HashMap.Strict qualified as HashMap +import Data.Hashable +import Data.List.NonEmpty qualified as NE +import Data.Maybe qualified as Maybe +import Data.Text qualified () +import Data.Text qualified as T +import Data.Text.Encoding qualified as Encoding +import Data.Text.Utf16.Rope.Mixed as Rope +import Data.Typeable +import Data.Aeson qualified as A +import Development.IDE as D +import Development.IDE.Core.FileStore (getVersionedTextDoc) +import Development.IDE.Core.PluginUtils +import Development.IDE.Core.Shake (restartShakeSession) +import Development.IDE.Core.Shake qualified as Shake +import Development.IDE.Graph + ( Key, + alwaysRerun, + ) +import Development.IDE.LSP.HoverDefinition (foundHover) +import Development.IDE.Plugin.Completions.Logic qualified as Ghcide +import Development.IDE.Types.Shake (toKey) +import Distribution.Fields qualified as Syntax +import Distribution.Package (Dependency) +import Distribution.PackageDescription + ( allBuildDepends, + depPkgName, + unPackageName, + ) +import Distribution.PackageDescription.Configuration (flattenPackageDescription) +import Distribution.Parsec.Position qualified as Syntax +import GHC.Generics +import Ide.Plugin.Cabal.CabalAdd qualified as CabalAdd +import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields +import Ide.Plugin.Cabal.Completion.Completer.Types qualified as CompleterTypes +import Ide.Plugin.Cabal.Completion.Completions qualified as Completions +import Ide.Plugin.Cabal.Completion.Types + ( ParseCabalCommonSections (ParseCabalCommonSections), + ParseCabalFields (..), + ParseCabalFile (..), + ParsePlanJson (..), + ) +import Ide.Plugin.Cabal.Completion.Types qualified as Types +import Ide.Plugin.Cabal.Definition (gotoDefinition) +import Ide.Plugin.Cabal.Dependencies +import Ide.Plugin.Cabal.Diagnostics qualified as Diagnostics +import Ide.Plugin.Cabal.FieldSuggest qualified as FieldSuggest +import Ide.Plugin.Cabal.LicenseSuggest qualified as LicenseSuggest +import Ide.Plugin.Cabal.Orphans () +import Ide.Plugin.Cabal.Outline +import Ide.Plugin.Cabal.Parse qualified as Parse +import Ide.Plugin.Error +import Ide.Types +import Language.LSP.Protocol.Lens qualified as JL +import Language.LSP.Protocol.Message qualified as LSP +import Language.LSP.Protocol.Types +import Language.LSP.VFS qualified as VFS +import Text.Regex.TDFA data Log = LogModificationTime NormalizedFilePath FileVersion @@ -112,79 +121,80 @@ haskellInteractionDescriptor recorder plId = { pluginHandlers = mconcat [ mkPluginHandler LSP.SMethod_TextDocumentCodeAction cabalAddCodeAction - ] - , pluginCommands = [PluginCommand CabalAdd.cabalAddCommand "add a dependency to a cabal file" (CabalAdd.command cabalAddRecorder)] - , pluginRules = pure () - , pluginNotificationHandlers = mempty + ], + pluginCommands = [PluginCommand CabalAdd.cabalAddCommand "add a dependency to a cabal file" (CabalAdd.command cabalAddRecorder)], + pluginRules = pure (), + pluginNotificationHandlers = mempty } - where - cabalAddRecorder = cmapWithPrio LogCabalAdd recorder - + where + cabalAddRecorder = cmapWithPrio LogCabalAdd recorder descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState descriptor recorder plId = (defaultCabalPluginDescriptor plId "Provides a variety of IDE features in cabal files") - { pluginRules = cabalRules recorder plId - , pluginHandlers = + { pluginRules = cabalRules recorder plId, + pluginHandlers = mconcat [ mkPluginHandler LSP.SMethod_TextDocumentCodeAction licenseSuggestCodeAction - , mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder - , mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline - , mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder - , mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition - , mkPluginHandler LSP.SMethod_TextDocumentHover hover - ] - , pluginNotificationHandlers = + , mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder + , mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline + , mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder + , mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition + , mkPluginHandler LSP.SMethod_TextDocumentHover hover + , mkPluginHandler LSP.SMethod_TextDocumentInlayHint hint + , mkPluginHandler LSP.SMethod_TextDocumentCodeLens lens + ], + pluginNotificationHandlers = mconcat [ mkPluginNotificationHandler LSP.SMethod_TextDocumentDidOpen $ - \ide vfs _ (DidOpenTextDocumentParams TextDocumentItem{_uri, _version}) -> liftIO $ do + \ide vfs _ (DidOpenTextDocumentParams TextDocumentItem {_uri, _version}) -> liftIO $ do whenUriFile _uri $ \file -> do log' Debug $ LogDocOpened _uri restartCabalShakeSession (shakeExtras ide) vfs file "(opened)" $ - addFileOfInterest recorder ide file Modified{firstOpen = True} - , mkPluginNotificationHandler LSP.SMethod_TextDocumentDidChange $ - \ide vfs _ (DidChangeTextDocumentParams VersionedTextDocumentIdentifier{_uri} _) -> liftIO $ do + addFileOfInterest recorder ide file Modified {firstOpen = True}, + mkPluginNotificationHandler LSP.SMethod_TextDocumentDidChange $ + \ide vfs _ (DidChangeTextDocumentParams VersionedTextDocumentIdentifier {_uri} _) -> liftIO $ do whenUriFile _uri $ \file -> do log' Debug $ LogDocModified _uri restartCabalShakeSession (shakeExtras ide) vfs file "(changed)" $ - addFileOfInterest recorder ide file Modified{firstOpen = False} - , mkPluginNotificationHandler LSP.SMethod_TextDocumentDidSave $ - \ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier{_uri} _) -> liftIO $ do + addFileOfInterest recorder ide file Modified {firstOpen = False}, + mkPluginNotificationHandler LSP.SMethod_TextDocumentDidSave $ + \ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier {_uri} _) -> liftIO $ do whenUriFile _uri $ \file -> do log' Debug $ LogDocSaved _uri restartCabalShakeSession (shakeExtras ide) vfs file "(saved)" $ - addFileOfInterest recorder ide file OnDisk - , mkPluginNotificationHandler LSP.SMethod_TextDocumentDidClose $ - \ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> liftIO $ do + addFileOfInterest recorder ide file OnDisk, + mkPluginNotificationHandler LSP.SMethod_TextDocumentDidClose $ + \ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier {_uri}) -> liftIO $ do whenUriFile _uri $ \file -> do log' Debug $ LogDocClosed _uri restartCabalShakeSession (shakeExtras ide) vfs file "(closed)" $ deleteFileOfInterest recorder ide file - ] - , pluginConfigDescriptor = defaultConfigDescriptor - { configHasDiagnostics = True - } + ], + pluginConfigDescriptor = + defaultConfigDescriptor + { configHasDiagnostics = True + } } - where - log' = logWith recorder - - whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO () - whenUriFile uri act = whenJust (uriToFilePath uri) $ act . toNormalizedFilePath' + where + log' = logWith recorder -{- | Helper function to restart the shake session, specifically for modifying .cabal files. -No special logic, just group up a bunch of functions you need for the base -Notification Handlers. + whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO () + whenUriFile uri act = whenJust (uriToFilePath uri) $ act . toNormalizedFilePath' -To make sure diagnostics are up to date, we need to tell shake that the file was touched and -needs to be re-parsed. That's what we do when we record the dirty key that our parsing -rule depends on. -Then we restart the shake session, so that changes to our virtual files are actually picked up. --} +-- | Helper function to restart the shake session, specifically for modifying .cabal files. +-- No special logic, just group up a bunch of functions you need for the base +-- Notification Handlers. +-- +-- To make sure diagnostics are up to date, we need to tell shake that the file was touched and +-- needs to be re-parsed. That's what we do when we record the dirty key that our parsing +-- rule depends on. +-- Then we restart the shake session, so that changes to our virtual files are actually picked up. restartCabalShakeSession :: ShakeExtras -> VFS.VFS -> NormalizedFilePath -> String -> IO [Key] -> IO () restartCabalShakeSession shakeExtras vfs file actionMsg actionBetweenSession = do restartShakeSession shakeExtras (VFSModified vfs) (fromNormalizedFilePath file ++ " " ++ actionMsg) [] $ do keys <- actionBetweenSession - return (toKey GetModificationTime file:keys) + return (toKey GetModificationTime file : keys) -- ---------------------------------------------------------------- -- Plugin Rules @@ -218,11 +228,18 @@ cabalRules recorder plId = do define (cmapWithPrio LogShake recorder) $ \ParseCabalCommonSections file -> do fields <- use_ ParseCabalFields file - let commonSections = Maybe.mapMaybe (\case - commonSection@(Syntax.Section (Syntax.Name _ "common") _ _) -> Just commonSection - _ -> Nothing) - fields + let commonSections = + Maybe.mapMaybe + ( \case + commonSection@(Syntax.Section (Syntax.Name _ "common") _ _) -> Just commonSection + _ -> Nothing + ) + fields pure ([], Just commonSections) + + define (cmapWithPrio LogShake recorder) $ \ParsePlanJson file -> do + (t, _) <- use_ GetFileContents file + pure ([], Just 0) define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do config <- getPluginConfigAction plId @@ -258,16 +275,15 @@ cabalRules recorder plId = do -- Must be careful to not impede the performance too much. Crucial to -- a snappy IDE experience. kick - where - log' = logWith recorder - -{- | This is the kick function for the cabal plugin. -We run this action, whenever we shake session us run/restarted, which triggers -actions to produce diagnostics for cabal files. + where + log' = logWith recorder -It is paramount that this kick-function can be run quickly, since it is a blocking -function invocation. --} +-- | This is the kick function for the cabal plugin. +-- We run this action, whenever we shake session us run/restarted, which triggers +-- actions to produce diagnostics for cabal files. +-- +-- It is paramount that this kick-function can be run quickly, since it is a blocking +-- function invocation. kick :: Action () kick = do files <- HashMap.keys <$> getCabalFilesOfInterestUntracked @@ -278,7 +294,7 @@ kick = do -- ---------------------------------------------------------------- licenseSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction -licenseSuggestCodeAction ideState _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext{_diagnostics=diags}) = do +licenseSuggestCodeAction ideState _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics = diags}) = do maxCompls <- fmap maxCompletions . liftIO $ runAction "cabal-plugin.suggestLicense" ideState getClientConfigAction pure $ InL $ diags >>= (fmap InR . LicenseSuggest.licenseErrorAction maxCompls uri) @@ -292,7 +308,7 @@ licenseSuggestCodeAction ideState _ (CodeActionParams _ _ (TextDocumentIdentifie -- TODO: Relying on completions here often does not produce the desired results, we should -- use some sort of fuzzy matching in the future, see issue #4357. fieldSuggestCodeAction :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction -fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext{_diagnostics=diags}) = do +fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics = diags}) = do mContents <- liftIO $ runAction "cabal-plugin.getUriContents" ide $ getUriContents $ toNormalizedUri uri case (,) <$> mContents <*> uriToFilePath' uri of Nothing -> pure $ InL [] @@ -308,7 +324,7 @@ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentif results <- forM fields (getSuggestion fileContents path cabalFields) pure $ InL $ map InR $ concat results where - getSuggestion fileContents fp cabalFields (fieldName,Diagnostic{ _range=_range@(Range (Position lineNr col) _) }) = do + getSuggestion fileContents fp cabalFields (fieldName, Diagnostic {_range = _range@(Range (Position lineNr col) _)}) = do let -- Compute where we would anticipate the cursor to be. fakeLspCursorPosition = Position lineNr (col + fromIntegral (T.length fieldName)) lspPrefixInfo = Ghcide.getCompletionPrefixFromRope fakeLspCursorPosition fileContents @@ -318,7 +334,7 @@ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentif pure $ FieldSuggest.fieldErrorAction uri fieldName completionTexts _range cabalAddCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction -cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext{_diagnostics=diags}) = do +cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics = diags}) = do maxCompls <- fmap maxCompletions . liftIO $ runAction "cabal.cabal-add" state getClientConfigAction let suggestions = take maxCompls $ concatMap CabalAdd.hiddenPackageSuggestion diags case suggestions of @@ -331,16 +347,24 @@ cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri) case mbCabalFile of Nothing -> pure $ InL [] Just cabalFilePath -> do - verTxtDocId <- runActionE "cabalAdd.getVersionedTextDoc" state $ - lift $ getVersionedTextDoc $ TextDocumentIdentifier (filePathToUri cabalFilePath) + verTxtDocId <- + runActionE "cabalAdd.getVersionedTextDoc" state $ + lift $ + getVersionedTextDoc $ + TextDocumentIdentifier (filePathToUri cabalFilePath) mbGPD <- liftIO $ runAction "cabal.cabal-add" state $ useWithStale ParseCabalFile $ toNormalizedFilePath cabalFilePath case mbGPD of Nothing -> pure $ InL [] Just (gpd, _) -> do - actions <- liftIO $ CabalAdd.addDependencySuggestCodeAction plId verTxtDocId - suggestions - haskellFilePath cabalFilePath - gpd + actions <- + liftIO $ + CabalAdd.addDependencySuggestCodeAction + plId + verTxtDocId + suggestions + haskellFilePath + cabalFilePath + gpd pure $ InL $ fmap InR actions -- | Handler for hover messages. @@ -362,8 +386,8 @@ hover ide _ msgParam = do Nothing -> pure $ InR Null Just txt -> if txt `elem` depsNames - then pure $ foundHover (Nothing, [txt <> "\n", documentationText txt]) - else pure $ InR Null + then pure $ foundHover (Nothing, [txt <> "\n", documentationText txt]) + else pure $ InR Null where cursor = Types.lspPositionToCabalPosition (msgParam ^. JL.position) uri = msgParam ^. JL.textDocument . JL.uri @@ -371,7 +395,7 @@ hover ide _ msgParam = do dependencyName :: Dependency -> T.Text dependencyName dep = T.pack $ unPackageName $ depPkgName dep - -- | Removes version requirements like + -- \| Removes version requirements like -- `==1.0.0.0`, `>= 2.1.1` that could be included in -- hover message. Assumes that the dependency consists -- of alphanums with dashes in between. Ends with an alphanum. @@ -387,43 +411,114 @@ hover ide _ msgParam = do getMatch :: (T.Text, T.Text, T.Text, [T.Text]) -> Maybe T.Text getMatch (_, _, _, [dependency]) = Just dependency - getMatch (_, _, _, _) = Nothing -- impossible case - + getMatch (_, _, _, _) = Nothing -- impossible case documentationText :: T.Text -> T.Text documentationText package = "[Documentation](https://hackage.haskell.org/package/" <> package <> ")" +-- ---------------------------------------------------------------- +-- Code Lenses +-- ---------------------------------------------------------------- + +lens :: PluginMethodHandler IdeState LSP.Method_TextDocumentCodeLens +lens state _plId clp = do + let uri = clp ^. JL.textDocument . JL.uri + + nfp <- getNormalizedFilePathE uri + cabalFields <- runActionE "cabal.cabal-hover" state $ useE ParseCabalFields nfp + let positionedDeps = concatMap parseDeps cabalFields + let lenses = map getCodeLens positionedDeps + + let testDep = PositionedDependency (Syntax.Position 0 0) (T.pack $ show positionedDeps) + + pure $ InL (getCodeLens testDep : lenses) + where + getCodeLens :: PositionedDependency -> CodeLens + getCodeLens (PositionedDependency pos dep) = + let cPos = Parse.fromSyntaxPosition pos in CodeLens + { _range = Range cPos cPos + , _command = Just $ mkActionlessCommand dep + , _data_ = Nothing + } + + mkActionlessCommand :: T.Text -> Command + mkActionlessCommand t = Command + { _title = t + , _command = "" + , _arguments = Nothing + } -- ---------------------------------------------------------------- --- Cabal file of Interest rules and global variable +-- Inlay Hints -- ---------------------------------------------------------------- -{- | Cabal files that are currently open in the lsp-client. -Specific actions happen when these files are saved, closed or modified, -such as generating diagnostics, re-parsing, etc... +-- | Handler for inlay hints +hint :: PluginMethodHandler IdeState LSP.Method_TextDocumentInlayHint +hint state _plId clp = + if isInlayHintsSupported state + then do + let uri = clp ^. JL.textDocument . JL.uri + + nfp <- getNormalizedFilePathE uri + cabalFields <- runActionE "cabal.cabal-inlayhint" state $ useE ParseCabalFields nfp + let positionedDeps = concatMap parseDeps cabalFields + let hints = map getInlayHint positionedDeps + + let testDep = PositionedDependency (Syntax.Position 1 1) "test" + + pure $ InL (getInlayHint testDep : hints) + else + pure $ InL [] + where + getInlayHint :: PositionedDependency -> InlayHint + getInlayHint (PositionedDependency pos dep) = InlayHint + { _position = Parse.fromSyntaxPosition pos + , _label = InL dep + , _kind = Nothing + , _textEdits = Nothing + , _tooltip = Nothing + , _paddingLeft = Nothing + , _paddingRight = Nothing + , _data_ = Nothing + } + +isInlayHintsSupported :: IdeState -> Bool +isInlayHintsSupported ideState = + let clientCaps = Shake.clientCapabilities $ shakeExtras ideState + in Maybe.isJust $ clientCaps ^? JL.textDocument . _Just . JL.inlayHint . _Just + +-- ----------------------------------------------------------- +-- Cabal file of Interest rules and global variable +-- ---------------------------------------------------------------- -We need to store the open files to parse them again if we restart the shake session. -Restarting of the shake session happens whenever these files are modified. --} +-- | Cabal files that are currently open in the lsp-client. +-- Specific actions happen when these files are saved, closed or modified, +-- such as generating diagnostics, re-parsing, etc... +-- +-- We need to store the open files to parse them again if we restart the shake session. +-- Restarting of the shake session happens whenever these files are modified. newtype OfInterestCabalVar = OfInterestCabalVar (Var (HashMap NormalizedFilePath FileOfInterestStatus)) instance Shake.IsIdeGlobal OfInterestCabalVar data IsCabalFileOfInterest = IsCabalFileOfInterest deriving (Eq, Show, Typeable, Generic) + instance Hashable IsCabalFileOfInterest + instance NFData IsCabalFileOfInterest type instance RuleResult IsCabalFileOfInterest = CabalFileOfInterestResult data CabalFileOfInterestResult = NotCabalFOI | IsCabalFOI FileOfInterestStatus deriving (Eq, Show, Typeable, Generic) + instance Hashable CabalFileOfInterestResult -instance NFData CabalFileOfInterestResult -{- | The rule that initialises the files of interest state. +instance NFData CabalFileOfInterestResult -Needs to be run on start-up. --} +-- | The rule that initialises the files of interest state. +-- +-- Needs to be run on start-up. ofInterestRules :: Recorder (WithPriority Log) -> Rules () ofInterestRules recorder = do Shake.addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap.empty) @@ -434,11 +529,11 @@ ofInterestRules recorder = do fp = summarize foi res = (Just fp, Just foi) return res - where - summarize NotCabalFOI = BS.singleton 0 - summarize (IsCabalFOI OnDisk) = BS.singleton 1 - summarize (IsCabalFOI (Modified False)) = BS.singleton 2 - summarize (IsCabalFOI (Modified True)) = BS.singleton 3 + where + summarize NotCabalFOI = BS.singleton 0 + summarize (IsCabalFOI OnDisk) = BS.singleton 1 + summarize (IsCabalFOI (Modified False)) = BS.singleton 2 + summarize (IsCabalFOI (Modified True)) = BS.singleton 3 getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus) getCabalFilesOfInterestUntracked = do @@ -453,11 +548,11 @@ addFileOfInterest recorder state f v = do pure (new, (prev, new)) if prev /= Just v then do - log' Debug $ LogFOI files - return [toKey IsCabalFileOfInterest f] + log' Debug $ LogFOI files + return [toKey IsCabalFileOfInterest f] else return [] - where - log' = logWith recorder + where + log' = logWith recorder deleteFileOfInterest :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> IO [Key] deleteFileOfInterest recorder state f = do @@ -465,8 +560,8 @@ deleteFileOfInterest recorder state f = do files <- modifyVar' var $ HashMap.delete f log' Debug $ LogFOI files return [toKey IsFileOfInterest f] - where - log' = logWith recorder + where + log' = logWith recorder -- ---------------------------------------------------------------- -- Completion @@ -499,20 +594,21 @@ computeCompletionsAt recorder ide prefInfo fp fields = do Just ctx -> do logWith recorder Debug $ LogCompletionContext ctx pos let completer = Completions.contextToCompleter ctx - let completerData = CompleterTypes.CompleterData - { getLatestGPD = do - -- We decide on useWithStaleFast here, since we mostly care about the file's meta information, - -- thus, a quick response gives us the desired result most of the time. - -- The `withStale` option is very important here, since we often call this rule with invalid cabal files. - mGPD <- runAction "cabal-plugin.modulesCompleter.gpd" ide $ useWithStale ParseCabalFile $ toNormalizedFilePath fp - pure $ fmap fst mGPD - , getCabalCommonSections = runAction "cabal-plugin.commonSections" ide $ use ParseCabalCommonSections $ toNormalizedFilePath fp - , cabalPrefixInfo = prefInfo - , stanzaName = - case fst ctx of - Types.Stanza _ name -> name - _ -> Nothing - } + let completerData = + CompleterTypes.CompleterData + { getLatestGPD = do + -- We decide on useWithStaleFast here, since we mostly care about the file's meta information, + -- thus, a quick response gives us the desired result most of the time. + -- The `withStale` option is very important here, since we often call this rule with invalid cabal files. + mGPD <- runAction "cabal-plugin.modulesCompleter.gpd" ide $ useWithStale ParseCabalFile $ toNormalizedFilePath fp + pure $ fmap fst mGPD, + getCabalCommonSections = runAction "cabal-plugin.commonSections" ide $ use ParseCabalCommonSections $ toNormalizedFilePath fp, + cabalPrefixInfo = prefInfo, + stanzaName = + case fst ctx of + Types.Stanza _ name -> name + _ -> Nothing + } completions <- completer completerRecorder completerData pure completions where diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 2655fbcaa6..6ab9d0748b 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -68,6 +68,15 @@ instance Hashable ParseCabalCommonSections instance NFData ParseCabalCommonSections +data ParsePlanJson = ParsePlanJson + deriving (Eq, Show, Typeable, Generic) + +instance Hashable ParsePlanJson + +instance NFData ParsePlanJson + +type instance RuleResult ParsePlanJson = Int + -- | The context a cursor can be in within a cabal file. -- -- We can be in stanzas or the top level, diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs new file mode 100644 index 0000000000..5879be6399 --- /dev/null +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -0,0 +1,54 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Ide.Plugin.Cabal.Dependencies where + +import Distribution.Fields qualified as Syntax +import Distribution.Parsec.Position qualified as Syntax + +import Data.Text.Encoding qualified as Encoding +import Data.Text qualified as T +import Data.Aeson qualified as A +import Data.Aeson ((.:)) + +import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches)) +import Data.ByteString (ByteString) + +data DependencyInstances = DependencyInstances + { installPlan :: [DependencyInstance] } + +data DependencyInstance = DependencyInstance + { _pkgName :: T.Text + , _pkgVersion :: T.Text + } + +instance A.FromJSON DependencyInstance where + parseJSON = A.withObject "InstallPlan" $ \obj -> do + pkgName <- obj .: "pkg-name" + pkgVersion <- obj .: "pkg-version" + return $ DependencyInstance pkgName pkgVersion + +instance A.FromJSON DependencyInstances where + parseJSON = A.withObject "PlanJson" $ \obj -> do + deps <- obj .: "install-plan" >>= A.parseJSON + return (DependencyInstances deps) + +data PositionedDependency = PositionedDependency Syntax.Position T.Text + deriving Show + +-- | Matches valid Cabal dependency names +packageRegex :: T.Text +packageRegex = ".+" + +-- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may +-- contain multiple dependencies. +mkPosDeps :: Syntax.FieldLine Syntax.Position -> [PositionedDependency] +mkPosDeps (Syntax.FieldLine pos dep) = map (PositionedDependency pos) $ getPackageNames dep + where + getPackageNames :: ByteString -> [T.Text] + getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) + +-- | Parses a Field that may contain dependencies +parseDeps :: Syntax.Field Syntax.Position -> [PositionedDependency] +parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDeps fls +parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls +parseDeps _ = [] \ No newline at end of file diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs index e949af1b1d..80747fc9ae 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs @@ -2,6 +2,7 @@ module Ide.Plugin.Cabal.Parse ( parseCabalFileContents , readCabalFields +, fromSyntaxPosition ) where import qualified Data.ByteString as BS @@ -38,3 +39,6 @@ readCabalFields file contents = do Right (fields, _warnings) -> do -- we don't want to double report diagnostics, all diagnostics are produced by 'ParseCabalFile'. Right fields + +fromSyntaxPosition :: Syntax.Position -> Position +fromSyntaxPosition (Syntax.Position row col) = Position (fromInteger $ toInteger (row - 1)) (fromInteger $ toInteger col) \ No newline at end of file From e9b65a15523db33173f05bfe1a39cfa7bde123b3 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Sun, 13 Oct 2024 13:24:29 +0200 Subject: [PATCH 02/11] implement plan json parsing --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 30 ++++++++---- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 30 +++++++++++- .../src/Ide/Plugin/Cabal/Dependencies.hs | 48 +++++++------------ .../src/Ide/Plugin/Cabal/Parse.hs | 6 +-- 4 files changed, 69 insertions(+), 45 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index e23fbd0d39..b06f83106b 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -75,6 +75,8 @@ import Language.LSP.Protocol.Message qualified as LSP import Language.LSP.Protocol.Types import Language.LSP.VFS qualified as VFS import Text.Regex.TDFA +import Debug.Trace +import System.FilePath (()) data Log = LogModificationTime NormalizedFilePath FileVersion @@ -238,8 +240,13 @@ cabalRules recorder plId = do pure ([], Just commonSections) define (cmapWithPrio LogShake recorder) $ \ParsePlanJson file -> do - (t, _) <- use_ GetFileContents file - pure ([], Just 0) + (_, planSrc) <- use_ GetFileContents file + + contents <- case planSrc of + Just sources -> pure $ Encoding.encodeUtf8 $ Rope.toText sources + Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file + + pure ([], installPlan <$> A.decodeStrict contents) define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do config <- getPluginConfigAction plId @@ -424,17 +431,21 @@ lens state _plId clp = do let uri = clp ^. JL.textDocument . JL.uri nfp <- getNormalizedFilePathE uri - cabalFields <- runActionE "cabal.cabal-hover" state $ useE ParseCabalFields nfp + cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp let positionedDeps = concatMap parseDeps cabalFields let lenses = map getCodeLens positionedDeps - - let testDep = PositionedDependency (Syntax.Position 0 0) (T.pack $ show positionedDeps) - pure $ InL (getCodeLens testDep : lenses) + let rfp = rootDir state + let planJson = toNormalizedFilePath $ rfp planJsonPath + deps <- runActionE "cabal.cabal-lens" state $ useE ParsePlanJson planJson + + traceShowM deps + + pure $ InL lenses where getCodeLens :: PositionedDependency -> CodeLens getCodeLens (PositionedDependency pos dep) = - let cPos = Parse.fromSyntaxPosition pos in CodeLens + let cPos = Types.cabalPositionToLSPPosition pos in CodeLens { _range = Range cPos cPos , _command = Just $ mkActionlessCommand dep , _data_ = Nothing @@ -457,11 +468,12 @@ hint state _plId clp = if isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri + let rfp = toNormalizedFilePath $ rootDir state nfp <- getNormalizedFilePathE uri cabalFields <- runActionE "cabal.cabal-inlayhint" state $ useE ParseCabalFields nfp let positionedDeps = concatMap parseDeps cabalFields - let hints = map getInlayHint positionedDeps + let hints = map getInlayHint positionedDeps let testDep = PositionedDependency (Syntax.Position 1 1) "test" @@ -471,7 +483,7 @@ hint state _plId clp = where getInlayHint :: PositionedDependency -> InlayHint getInlayHint (PositionedDependency pos dep) = InlayHint - { _position = Parse.fromSyntaxPosition pos + { _position = Types.cabalPositionToLSPPosition pos , _label = InL dep , _kind = Nothing , _textEdits = Nothing diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 6ab9d0748b..7cd1737030 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -15,6 +15,8 @@ import qualified Distribution.PackageDescription as PD import qualified Distribution.Parsec.Position as Syntax import GHC.Generics import qualified Language.LSP.Protocol.Lens as JL +import qualified Data.Aeson as A +import Data.Aeson ((.:)) data Log = LogFileSplitError Position @@ -75,7 +77,7 @@ instance Hashable ParsePlanJson instance NFData ParsePlanJson -type instance RuleResult ParsePlanJson = Int +type instance RuleResult ParsePlanJson = [DependencyInstance] -- | The context a cursor can be in within a cabal file. -- @@ -169,6 +171,32 @@ data CabalPrefixInfo = CabalPrefixInfo data Apostrophe = Surrounded | LeftSide deriving (Eq, Ord, Show) +data PositionedDependency = PositionedDependency Syntax.Position T.Text + deriving Show + +data DependencyInstances = DependencyInstances + { installPlan :: [DependencyInstance] } + deriving Show + +data DependencyInstance = DependencyInstance + { _pkgName :: T.Text + , _pkgVersion :: T.Text + } + deriving (Show, Generic) + +instance NFData DependencyInstance + +instance A.FromJSON DependencyInstance where + parseJSON = A.withObject "InstallPlan" $ \obj -> do + pkgName <- obj .: "pkg-name" + pkgVersion <- obj .: "pkg-version" + return $ DependencyInstance pkgName pkgVersion + +instance A.FromJSON DependencyInstances where + parseJSON = A.withObject "PlanJson" $ \obj -> do + deps <- obj .: "install-plan" >>= A.parseJSON + return (DependencyInstances deps) + -- | Wraps a completion in apostrophes where appropriate. -- -- If a completion starts with an apostrophe we want to end it with an apostrophe. diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index 5879be6399..5c4bd7e14d 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -1,39 +1,33 @@ {-# LANGUAGE OverloadedStrings #-} -module Ide.Plugin.Cabal.Dependencies where +module Ide.Plugin.Cabal.Dependencies ( + DependencyInstance(..), + DependencyInstances(..), + PositionedDependency(..), + parseDeps, + planJsonPath +) where import Distribution.Fields qualified as Syntax import Distribution.Parsec.Position qualified as Syntax import Data.Text.Encoding qualified as Encoding import Data.Text qualified as T -import Data.Aeson qualified as A -import Data.Aeson ((.:)) +import System.FilePath ((), (<.>)) import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches)) import Data.ByteString (ByteString) -data DependencyInstances = DependencyInstances - { installPlan :: [DependencyInstance] } - -data DependencyInstance = DependencyInstance - { _pkgName :: T.Text - , _pkgVersion :: T.Text - } +import Ide.Plugin.Cabal.Completion.Types -instance A.FromJSON DependencyInstance where - parseJSON = A.withObject "InstallPlan" $ \obj -> do - pkgName <- obj .: "pkg-name" - pkgVersion <- obj .: "pkg-version" - return $ DependencyInstance pkgName pkgVersion - -instance A.FromJSON DependencyInstances where - parseJSON = A.withObject "PlanJson" $ \obj -> do - deps <- obj .: "install-plan" >>= A.parseJSON - return (DependencyInstances deps) - -data PositionedDependency = PositionedDependency Syntax.Position T.Text - deriving Show +planJsonPath :: FilePath +planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now + +-- | Parses a Field that may contain dependencies +parseDeps :: Syntax.Field Syntax.Position -> [PositionedDependency] +parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDeps fls +parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls +parseDeps _ = [] -- | Matches valid Cabal dependency names packageRegex :: T.Text @@ -45,10 +39,4 @@ mkPosDeps :: Syntax.FieldLine Syntax.Position -> [PositionedDependency] mkPosDeps (Syntax.FieldLine pos dep) = map (PositionedDependency pos) $ getPackageNames dep where getPackageNames :: ByteString -> [T.Text] - getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) - --- | Parses a Field that may contain dependencies -parseDeps :: Syntax.Field Syntax.Position -> [PositionedDependency] -parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDeps fls -parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls -parseDeps _ = [] \ No newline at end of file + getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) \ No newline at end of file diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs index 80747fc9ae..82df03ba83 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs @@ -2,7 +2,6 @@ module Ide.Plugin.Cabal.Parse ( parseCabalFileContents , readCabalFields -, fromSyntaxPosition ) where import qualified Data.ByteString as BS @@ -38,7 +37,4 @@ readCabalFields file contents = do $ "Failed to parse cabal file: " <> T.pack (show parseError) Right (fields, _warnings) -> do -- we don't want to double report diagnostics, all diagnostics are produced by 'ParseCabalFile'. - Right fields - -fromSyntaxPosition :: Syntax.Position -> Position -fromSyntaxPosition (Syntax.Position row col) = Position (fromInteger $ toInteger (row - 1)) (fromInteger $ toInteger col) \ No newline at end of file + Right fields \ No newline at end of file From 83f55ebe563f3a77bc1a2c4dd7b3d038eec505d5 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Sun, 13 Oct 2024 15:38:41 +0200 Subject: [PATCH 03/11] first working version --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 25 +++++++++++++------ .../src/Ide/Plugin/Cabal/Completion/Types.hs | 21 +++++++++++++--- .../src/Ide/Plugin/Cabal/Dependencies.hs | 4 +-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index b06f83106b..ec6430610f 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -18,6 +18,7 @@ import Control.Monad.Trans.Maybe (runMaybeT) import Data.ByteString qualified as BS import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HashMap +import Data.Map qualified as Map import Data.Hashable import Data.List.NonEmpty qualified as NE import Data.Maybe qualified as Maybe @@ -57,7 +58,7 @@ import Ide.Plugin.Cabal.Completion.Types ( ParseCabalCommonSections (ParseCabalCommonSections), ParseCabalFields (..), ParseCabalFile (..), - ParsePlanJson (..), + ParsePlanJson (..), BuildDependencyVersionMapping (..), Versioned(..), ) import Ide.Plugin.Cabal.Completion.Types qualified as Types import Ide.Plugin.Cabal.Definition (gotoDefinition) @@ -247,6 +248,13 @@ cabalRules recorder plId = do Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file pure ([], installPlan <$> A.decodeStrict contents) + + define (cmapWithPrio LogShake recorder) $ \BuildDependencyVersionMapping file -> do + deps <- use_ ParsePlanJson file + + let versionMapping = Map.fromList $ map (\d -> (_pkgName d, _pkgVersion d)) deps + + pure ([], Just versionMapping) define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do config <- getPluginConfigAction plId @@ -433,21 +441,22 @@ lens state _plId clp = do nfp <- getNormalizedFilePathE uri cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp let positionedDeps = concatMap parseDeps cabalFields - let lenses = map getCodeLens positionedDeps let rfp = rootDir state let planJson = toNormalizedFilePath $ rfp planJsonPath - deps <- runActionE "cabal.cabal-lens" state $ useE ParsePlanJson planJson - - traceShowM deps + planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson + + let lenses = Maybe.mapMaybe + (\p@(PositionedDependency _ name) -> getCodeLens . Versioned p <$> Map.lookup name planDeps) + positionedDeps pure $ InL lenses where - getCodeLens :: PositionedDependency -> CodeLens - getCodeLens (PositionedDependency pos dep) = + getCodeLens :: Versioned PositionedDependency -> CodeLens + getCodeLens (Versioned (PositionedDependency pos _) v) = let cPos = Types.cabalPositionToLSPPosition pos in CodeLens { _range = Range cPos cPos - , _command = Just $ mkActionlessCommand dep + , _command = Just $ mkActionlessCommand v , _data_ = Nothing } diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 7cd1737030..7b19b9a6e3 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -8,6 +8,7 @@ import Control.DeepSeq (NFData) import Control.Lens ((^.)) import Data.Hashable import qualified Data.Text as T +import qualified Data.Map as M import Data.Typeable import Development.IDE as D import qualified Distribution.Fields as Syntax @@ -70,6 +71,15 @@ instance Hashable ParseCabalCommonSections instance NFData ParseCabalCommonSections +data BuildDependencyVersionMapping = BuildDependencyVersionMapping + deriving (Eq, Show, Typeable, Generic) + +instance Hashable BuildDependencyVersionMapping + +instance NFData BuildDependencyVersionMapping + +type instance RuleResult BuildDependencyVersionMapping = M.Map PkgName PkgVersion + data ParsePlanJson = ParsePlanJson deriving (Eq, Show, Typeable, Generic) @@ -170,17 +180,22 @@ data CabalPrefixInfo = CabalPrefixInfo -- while 'LeftSide' means, a closing apostrophe has to be added after the completion item. data Apostrophe = Surrounded | LeftSide deriving (Eq, Ord, Show) + +type PkgName = T.Text +type PkgVersion = T.Text -data PositionedDependency = PositionedDependency Syntax.Position T.Text +data PositionedDependency = PositionedDependency Syntax.Position PkgName deriving Show + +data Versioned a = Versioned a PkgVersion data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } deriving Show data DependencyInstance = DependencyInstance - { _pkgName :: T.Text - , _pkgVersion :: T.Text + { _pkgName :: PkgName + , _pkgVersion :: PkgVersion } deriving (Show, Generic) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index 5c4bd7e14d..f50dea8be9 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -31,7 +31,7 @@ parseDeps _ = [] -- | Matches valid Cabal dependency names packageRegex :: T.Text -packageRegex = ".+" +packageRegex = "[a-zA-Z0-9_-]+" -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. @@ -39,4 +39,4 @@ mkPosDeps :: Syntax.FieldLine Syntax.Position -> [PositionedDependency] mkPosDeps (Syntax.FieldLine pos dep) = map (PositionedDependency pos) $ getPackageNames dep where getPackageNames :: ByteString -> [T.Text] - getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) \ No newline at end of file + getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) From 816017e290f45c853c7aec09aaa03d2ffdc45552 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Sun, 13 Oct 2024 17:37:54 +0200 Subject: [PATCH 04/11] add inlayhint logic only show code lenses when inlay hints are not supported --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index ec6430610f..f23c0e390d 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -436,21 +436,25 @@ hover ide _ msgParam = do lens :: PluginMethodHandler IdeState LSP.Method_TextDocumentCodeLens lens state _plId clp = do - let uri = clp ^. JL.textDocument . JL.uri + if not $ isInlayHintsSupported state + then do + let uri = clp ^. JL.textDocument . JL.uri - nfp <- getNormalizedFilePathE uri - cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp - let positionedDeps = concatMap parseDeps cabalFields + nfp <- getNormalizedFilePathE uri + cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp + let positionedDeps = concatMap parseDeps cabalFields - let rfp = rootDir state - let planJson = toNormalizedFilePath $ rfp planJsonPath - planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson + let rfp = rootDir state + let planJson = toNormalizedFilePath $ rfp planJsonPath + planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\p@(PositionedDependency _ name) -> getCodeLens . Versioned p <$> Map.lookup name planDeps) - positionedDeps + let lenses = Maybe.mapMaybe + (\p@(PositionedDependency _ name) -> getCodeLens . Versioned p <$> Map.lookup name planDeps) + positionedDeps - pure $ InL lenses + pure $ InL lenses + else + pure $ InL [] where getCodeLens :: Versioned PositionedDependency -> CodeLens getCodeLens (Versioned (PositionedDependency pos _) v) = @@ -477,23 +481,27 @@ hint state _plId clp = if isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri - let rfp = toNormalizedFilePath $ rootDir state nfp <- getNormalizedFilePathE uri - cabalFields <- runActionE "cabal.cabal-inlayhint" state $ useE ParseCabalFields nfp + cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp let positionedDeps = concatMap parseDeps cabalFields - let hints = map getInlayHint positionedDeps - - let testDep = PositionedDependency (Syntax.Position 1 1) "test" - pure $ InL (getInlayHint testDep : hints) + let rfp = rootDir state + let planJson = toNormalizedFilePath $ rfp planJsonPath + planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson + + let hints = Maybe.mapMaybe + (\p@(PositionedDependency _ name) -> getInlayHint . Versioned p <$> Map.lookup name planDeps) + positionedDeps + + pure $ InL hints else pure $ InL [] where - getInlayHint :: PositionedDependency -> InlayHint - getInlayHint (PositionedDependency pos dep) = InlayHint + getInlayHint :: Versioned PositionedDependency -> InlayHint + getInlayHint (Versioned (PositionedDependency pos _) v) = InlayHint { _position = Types.cabalPositionToLSPPosition pos - , _label = InL dep + , _label = InL v , _kind = Nothing , _textEdits = Nothing , _tooltip = Nothing From 51763bb7298734a2b83b5c19ff6a914884778dd9 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Sun, 13 Oct 2024 18:10:22 +0200 Subject: [PATCH 05/11] refactor --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 24 ++++++++++--------- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 7 +++--- .../src/Ide/Plugin/Cabal/Dependencies.hs | 7 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index f23c0e390d..878a4e42ef 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -58,7 +58,10 @@ import Ide.Plugin.Cabal.Completion.Types ( ParseCabalCommonSections (ParseCabalCommonSections), ParseCabalFields (..), ParseCabalFile (..), - ParsePlanJson (..), BuildDependencyVersionMapping (..), Versioned(..), + ParsePlanJson (..), + BuildDependencyVersionMapping (..), + Positioned(..), + SimpleDependency(..) ) import Ide.Plugin.Cabal.Completion.Types qualified as Types import Ide.Plugin.Cabal.Definition (gotoDefinition) @@ -76,7 +79,6 @@ import Language.LSP.Protocol.Message qualified as LSP import Language.LSP.Protocol.Types import Language.LSP.VFS qualified as VFS import Text.Regex.TDFA -import Debug.Trace import System.FilePath (()) data Log @@ -449,15 +451,15 @@ lens state _plId clp = do planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson let lenses = Maybe.mapMaybe - (\p@(PositionedDependency _ name) -> getCodeLens . Versioned p <$> Map.lookup name planDeps) - positionedDeps + (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) + positionedDeps pure $ InL lenses else pure $ InL [] where - getCodeLens :: Versioned PositionedDependency -> CodeLens - getCodeLens (Versioned (PositionedDependency pos _) v) = + getCodeLens :: Positioned SimpleDependency -> CodeLens + getCodeLens (Positioned pos (Dependency _ v)) = let cPos = Types.cabalPositionToLSPPosition pos in CodeLens { _range = Range cPos cPos , _command = Just $ mkActionlessCommand v @@ -490,16 +492,16 @@ hint state _plId clp = let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let hints = Maybe.mapMaybe - (\p@(PositionedDependency _ name) -> getInlayHint . Versioned p <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps - pure $ InL hints + pure $ InL lenses else pure $ InL [] where - getInlayHint :: Versioned PositionedDependency -> InlayHint - getInlayHint (Versioned (PositionedDependency pos _) v) = InlayHint + getInlayHint :: Positioned SimpleDependency -> InlayHint + getInlayHint (Positioned pos (Dependency _ v)) = InlayHint { _position = Types.cabalPositionToLSPPosition pos , _label = InL v , _kind = Nothing diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 7b19b9a6e3..6c25558f15 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -184,10 +184,9 @@ data Apostrophe = Surrounded | LeftSide type PkgName = T.Text type PkgVersion = T.Text -data PositionedDependency = PositionedDependency Syntax.Position PkgName - deriving Show - -data Versioned a = Versioned a PkgVersion +data SimpleDependency = Dependency PkgName PkgVersion + +data Positioned a = Positioned Syntax.Position a data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index f50dea8be9..695819ae7f 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -3,7 +3,6 @@ module Ide.Plugin.Cabal.Dependencies ( DependencyInstance(..), DependencyInstances(..), - PositionedDependency(..), parseDeps, planJsonPath ) where @@ -24,7 +23,7 @@ planJsonPath :: FilePath planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now -- | Parses a Field that may contain dependencies -parseDeps :: Syntax.Field Syntax.Position -> [PositionedDependency] +parseDeps :: Syntax.Field Syntax.Position -> [Positioned PkgName] parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDeps fls parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls parseDeps _ = [] @@ -35,8 +34,8 @@ packageRegex = "[a-zA-Z0-9_-]+" -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. -mkPosDeps :: Syntax.FieldLine Syntax.Position -> [PositionedDependency] -mkPosDeps (Syntax.FieldLine pos dep) = map (PositionedDependency pos) $ getPackageNames dep +mkPosDeps :: Syntax.FieldLine Syntax.Position -> [Positioned PkgName] +mkPosDeps (Syntax.FieldLine pos dep) = map (\n -> Positioned pos n) $ getPackageNames dep where getPackageNames :: ByteString -> [T.Text] getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) From 961d7144b7caea50c6bbae0c8999180fac188fde Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Sun, 13 Oct 2024 18:25:12 +0200 Subject: [PATCH 06/11] add comments --- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 8 ++++++-- .../hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs | 2 +- plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 6c25558f15..6297a78ada 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -186,16 +186,19 @@ type PkgVersion = T.Text data SimpleDependency = Dependency PkgName PkgVersion +-- | Represents some element that has an associated position in a file data Positioned a = Positioned Syntax.Position a data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } deriving Show +-- | Represents a concrete dependency entry in plan.json data DependencyInstance = DependencyInstance { _pkgName :: PkgName , _pkgVersion :: PkgVersion - } + , _componentName :: T.Text + } -- missing some unneeded fields deriving (Show, Generic) instance NFData DependencyInstance @@ -204,7 +207,8 @@ instance A.FromJSON DependencyInstance where parseJSON = A.withObject "InstallPlan" $ \obj -> do pkgName <- obj .: "pkg-name" pkgVersion <- obj .: "pkg-version" - return $ DependencyInstance pkgName pkgVersion + cmpName <- obj .: "component-name" + return $ DependencyInstance pkgName pkgVersion cmpName instance A.FromJSON DependencyInstances where parseJSON = A.withObject "PlanJson" $ \obj -> do diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index 695819ae7f..6ff9608394 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -30,7 +30,7 @@ parseDeps _ = [] -- | Matches valid Cabal dependency names packageRegex :: T.Text -packageRegex = "[a-zA-Z0-9_-]+" +packageRegex = "[a-zA-Z0-9_-]+" -- not sure if this is correct -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs index 82df03ba83..e949af1b1d 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Parse.hs @@ -37,4 +37,4 @@ readCabalFields file contents = do $ "Failed to parse cabal file: " <> T.pack (show parseError) Right (fields, _warnings) -> do -- we don't want to double report diagnostics, all diagnostics are produced by 'ParseCabalFile'. - Right fields \ No newline at end of file + Right fields From f5fad79c68d853b8a91a5d2d07a5a8e2c19bafc8 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Mon, 14 Oct 2024 15:13:22 +0200 Subject: [PATCH 07/11] improve position handling --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 5 +++- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 8 ++++--- .../src/Ide/Plugin/Cabal/Dependencies.hs | 23 +++++++++++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index 878a4e42ef..ae7c5886dc 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -80,6 +80,7 @@ import Language.LSP.Protocol.Types import Language.LSP.VFS qualified as VFS import Text.Regex.TDFA import System.FilePath (()) +import Debug.Trace data Log = LogModificationTime NormalizedFilePath FileVersion @@ -444,6 +445,7 @@ lens state _plId clp = do nfp <- getNormalizedFilePathE uri cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp + let positionedDeps = concatMap parseDeps cabalFields let rfp = rootDir state @@ -460,7 +462,8 @@ lens state _plId clp = do where getCodeLens :: Positioned SimpleDependency -> CodeLens getCodeLens (Positioned pos (Dependency _ v)) = - let cPos = Types.cabalPositionToLSPPosition pos in CodeLens + let cPos = Types.cabalPositionToLSPPosition pos + in CodeLens { _range = Range cPos cPos , _command = Just $ mkActionlessCommand v , _data_ = Nothing diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 6297a78ada..acad6cc53e 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -185,9 +185,11 @@ type PkgName = T.Text type PkgVersion = T.Text data SimpleDependency = Dependency PkgName PkgVersion + deriving Show -- | Represents some element that has an associated position in a file data Positioned a = Positioned Syntax.Position a + deriving Show data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } @@ -197,7 +199,7 @@ data DependencyInstances = DependencyInstances data DependencyInstance = DependencyInstance { _pkgName :: PkgName , _pkgVersion :: PkgVersion - , _componentName :: T.Text + , _pkgType :: T.Text } -- missing some unneeded fields deriving (Show, Generic) @@ -207,8 +209,8 @@ instance A.FromJSON DependencyInstance where parseJSON = A.withObject "InstallPlan" $ \obj -> do pkgName <- obj .: "pkg-name" pkgVersion <- obj .: "pkg-version" - cmpName <- obj .: "component-name" - return $ DependencyInstance pkgName pkgVersion cmpName + pkgType <- obj .: "type" + return $ DependencyInstance pkgName pkgVersion pkgType instance A.FromJSON DependencyInstances where parseJSON = A.withObject "PlanJson" $ \obj -> do diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index 6ff9608394..8e99e8506f 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -4,7 +4,8 @@ module Ide.Plugin.Cabal.Dependencies ( DependencyInstance(..), DependencyInstances(..), parseDeps, - planJsonPath + planJsonPath, + packageRegex ) where import Distribution.Fields qualified as Syntax @@ -14,18 +15,20 @@ import Data.Text.Encoding qualified as Encoding import Data.Text qualified as T import System.FilePath ((), (<.>)) -import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches)) +import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches), AllMatches(getAllMatches)) import Data.ByteString (ByteString) import Ide.Plugin.Cabal.Completion.Types - +import Debug.Trace +import Data.Tuple.Extra (dupe) + planJsonPath :: FilePath planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now - + -- | Parses a Field that may contain dependencies parseDeps :: Syntax.Field Syntax.Position -> [Positioned PkgName] parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDeps fls -parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls +parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls parseDeps _ = [] -- | Matches valid Cabal dependency names @@ -35,7 +38,13 @@ packageRegex = "[a-zA-Z0-9_-]+" -- not sure if this is correct -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. mkPosDeps :: Syntax.FieldLine Syntax.Position -> [Positioned PkgName] -mkPosDeps (Syntax.FieldLine pos dep) = map (\n -> Positioned pos n) $ getPackageNames dep - where +mkPosDeps (Syntax.FieldLine pos dep) = zipWith + (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) + (getPackageNames dep) + (getPackageNameOffsets dep) + where getPackageNames :: ByteString -> [T.Text] getPackageNames dep = getAllTextMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) + + getPackageNameOffsets :: ByteString -> [(Int, Int)] + getPackageNameOffsets dep = getAllMatches (Encoding.decodeUtf8Lenient dep =~ packageRegex) From 147458c71c9544be83cf14a21284c5f7f750ef77 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Mon, 14 Oct 2024 15:34:27 +0200 Subject: [PATCH 08/11] fix imports and exports --- plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 1 - plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index ae7c5886dc..678c363313 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -80,7 +80,6 @@ import Language.LSP.Protocol.Types import Language.LSP.VFS qualified as VFS import Text.Regex.TDFA import System.FilePath (()) -import Debug.Trace data Log = LogModificationTime NormalizedFilePath FileVersion diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index 8e99e8506f..f0acb7a8ca 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -5,7 +5,6 @@ module Ide.Plugin.Cabal.Dependencies ( DependencyInstances(..), parseDeps, planJsonPath, - packageRegex ) where import Distribution.Fields qualified as Syntax @@ -17,10 +16,7 @@ import System.FilePath ((), (<.>)) import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches), AllMatches(getAllMatches)) import Data.ByteString (ByteString) - import Ide.Plugin.Cabal.Completion.Types -import Debug.Trace -import Data.Tuple.Extra (dupe) planJsonPath :: FilePath planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now From 0d9b44cdf5a15d7ebb33154f867e1e28e3fc4076 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Mon, 14 Oct 2024 16:08:27 +0200 Subject: [PATCH 09/11] formatting --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 236 +++++++++--------- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 26 +- .../src/Ide/Plugin/Cabal/Dependencies.hs | 28 ++- 3 files changed, 143 insertions(+), 147 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index 678c363313..ea523f571f 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -1,85 +1,79 @@ -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} module Ide.Plugin.Cabal (descriptor, haskellInteractionDescriptor, Log (..)) where -import Control.Concurrent.Strict -import Control.DeepSeq -import Control.Lens ((^.)) -import Control.Lens.Fold ((^?)) -import Control.Lens.Prism (_Just) -import Control.Monad.Extra -import Control.Monad.IO.Class -import Control.Monad.Trans.Class (lift) -import Control.Monad.Trans.Maybe (runMaybeT) -import Data.ByteString qualified as BS -import Data.HashMap.Strict (HashMap) -import Data.HashMap.Strict qualified as HashMap -import Data.Map qualified as Map -import Data.Hashable -import Data.List.NonEmpty qualified as NE -import Data.Maybe qualified as Maybe -import Data.Text qualified () -import Data.Text qualified as T -import Data.Text.Encoding qualified as Encoding -import Data.Text.Utf16.Rope.Mixed as Rope -import Data.Typeable -import Data.Aeson qualified as A -import Development.IDE as D -import Development.IDE.Core.FileStore (getVersionedTextDoc) -import Development.IDE.Core.PluginUtils -import Development.IDE.Core.Shake (restartShakeSession) -import Development.IDE.Core.Shake qualified as Shake -import Development.IDE.Graph - ( Key, - alwaysRerun, - ) -import Development.IDE.LSP.HoverDefinition (foundHover) -import Development.IDE.Plugin.Completions.Logic qualified as Ghcide -import Development.IDE.Types.Shake (toKey) -import Distribution.Fields qualified as Syntax -import Distribution.Package (Dependency) -import Distribution.PackageDescription - ( allBuildDepends, - depPkgName, - unPackageName, - ) -import Distribution.PackageDescription.Configuration (flattenPackageDescription) -import Distribution.Parsec.Position qualified as Syntax -import GHC.Generics -import Ide.Plugin.Cabal.CabalAdd qualified as CabalAdd -import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields -import Ide.Plugin.Cabal.Completion.Completer.Types qualified as CompleterTypes -import Ide.Plugin.Cabal.Completion.Completions qualified as Completions -import Ide.Plugin.Cabal.Completion.Types - ( ParseCabalCommonSections (ParseCabalCommonSections), - ParseCabalFields (..), - ParseCabalFile (..), - ParsePlanJson (..), - BuildDependencyVersionMapping (..), - Positioned(..), - SimpleDependency(..) - ) -import Ide.Plugin.Cabal.Completion.Types qualified as Types -import Ide.Plugin.Cabal.Definition (gotoDefinition) -import Ide.Plugin.Cabal.Dependencies -import Ide.Plugin.Cabal.Diagnostics qualified as Diagnostics -import Ide.Plugin.Cabal.FieldSuggest qualified as FieldSuggest -import Ide.Plugin.Cabal.LicenseSuggest qualified as LicenseSuggest -import Ide.Plugin.Cabal.Orphans () -import Ide.Plugin.Cabal.Outline -import Ide.Plugin.Cabal.Parse qualified as Parse -import Ide.Plugin.Error -import Ide.Types -import Language.LSP.Protocol.Lens qualified as JL -import Language.LSP.Protocol.Message qualified as LSP -import Language.LSP.Protocol.Types -import Language.LSP.VFS qualified as VFS -import Text.Regex.TDFA -import System.FilePath (()) +import Control.Concurrent.Strict +import Control.DeepSeq +import Control.Lens ((^.)) +import Control.Lens.Fold ((^?)) +import Control.Lens.Prism (_Just) +import Control.Monad.Extra +import Control.Monad.IO.Class +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Maybe (runMaybeT) +import qualified Data.Aeson as A +import qualified Data.ByteString as BS +import Data.Hashable +import Data.HashMap.Strict (HashMap) +import qualified Data.HashMap.Strict as HashMap +import qualified Data.List.NonEmpty as NE +import qualified Data.Map as Map +import qualified Data.Maybe as Maybe +import qualified Data.Text () +import qualified Data.Text as T +import qualified Data.Text.Encoding as Encoding +import Data.Text.Utf16.Rope.Mixed as Rope +import Data.Typeable +import Development.IDE as D +import Development.IDE.Core.FileStore (getVersionedTextDoc) +import Development.IDE.Core.PluginUtils +import Development.IDE.Core.Shake (restartShakeSession) +import qualified Development.IDE.Core.Shake as Shake +import Development.IDE.Graph (Key, + alwaysRerun) +import Development.IDE.LSP.HoverDefinition (foundHover) +import qualified Development.IDE.Plugin.Completions.Logic as Ghcide +import Development.IDE.Types.Shake (toKey) +import qualified Distribution.Fields as Syntax +import Distribution.Package (Dependency) +import Distribution.PackageDescription (allBuildDepends, + depPkgName, + unPackageName) +import Distribution.PackageDescription.Configuration (flattenPackageDescription) +import qualified Distribution.Parsec.Position as Syntax +import GHC.Generics +import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd +import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields +import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes +import qualified Ide.Plugin.Cabal.Completion.Completions as Completions +import Ide.Plugin.Cabal.Completion.Types (BuildDependencyVersionMapping (..), + ParseCabalCommonSections (ParseCabalCommonSections), + ParseCabalFields (..), + ParseCabalFile (..), + ParsePlanJson (..), + Positioned (..), + SimpleDependency (..)) +import qualified Ide.Plugin.Cabal.Completion.Types as Types +import Ide.Plugin.Cabal.Definition (gotoDefinition) +import Ide.Plugin.Cabal.Dependencies +import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics +import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest +import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest +import Ide.Plugin.Cabal.Orphans () +import Ide.Plugin.Cabal.Outline +import qualified Ide.Plugin.Cabal.Parse as Parse +import Ide.Plugin.Error +import Ide.Types +import qualified Language.LSP.Protocol.Lens as JL +import qualified Language.LSP.Protocol.Message as LSP +import Language.LSP.Protocol.Types +import qualified Language.LSP.VFS as VFS +import System.FilePath (()) +import Text.Regex.TDFA data Log = LogModificationTime NormalizedFilePath FileVersion @@ -141,13 +135,13 @@ descriptor recorder plId = pluginHandlers = mconcat [ mkPluginHandler LSP.SMethod_TextDocumentCodeAction licenseSuggestCodeAction - , mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder - , mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline - , mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder - , mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition - , mkPluginHandler LSP.SMethod_TextDocumentHover hover - , mkPluginHandler LSP.SMethod_TextDocumentInlayHint hint - , mkPluginHandler LSP.SMethod_TextDocumentCodeLens lens + , mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder + , mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline + , mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder + , mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition + , mkPluginHandler LSP.SMethod_TextDocumentHover hover + , mkPluginHandler LSP.SMethod_TextDocumentInlayHint hint + , mkPluginHandler LSP.SMethod_TextDocumentCodeLens lens ], pluginNotificationHandlers = mconcat @@ -241,21 +235,21 @@ cabalRules recorder plId = do ) fields pure ([], Just commonSections) - + define (cmapWithPrio LogShake recorder) $ \ParsePlanJson file -> do (_, planSrc) <- use_ GetFileContents file - + contents <- case planSrc of Just sources -> pure $ Encoding.encodeUtf8 $ Rope.toText sources - Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file - + Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file + pure ([], installPlan <$> A.decodeStrict contents) - + define (cmapWithPrio LogShake recorder) $ \BuildDependencyVersionMapping file -> do - deps <- use_ ParsePlanJson file - + deps <- use_ ParsePlanJson file + let versionMapping = Map.fromList $ map (\d -> (_pkgName d, _pkgVersion d)) deps - + pure ([], Just versionMapping) define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do @@ -428,7 +422,7 @@ hover ide _ msgParam = do getMatch :: (T.Text, T.Text, T.Text, [T.Text]) -> Maybe T.Text getMatch (_, _, _, [dependency]) = Just dependency - getMatch (_, _, _, _) = Nothing -- impossible case + getMatch (_, _, _, _) = Nothing -- impossible case documentationText :: T.Text -> T.Text documentationText package = "[Documentation](https://hackage.haskell.org/package/" <> package <> ")" @@ -437,42 +431,42 @@ hover ide _ msgParam = do -- ---------------------------------------------------------------- lens :: PluginMethodHandler IdeState LSP.Method_TextDocumentCodeLens -lens state _plId clp = do +lens state _plId clp = do if not $ isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri nfp <- getNormalizedFilePathE uri cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp - + let positionedDeps = concatMap parseDeps cabalFields let rfp = rootDir state let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps - + pure $ InL lenses else pure $ InL [] where getCodeLens :: Positioned SimpleDependency -> CodeLens - getCodeLens (Positioned pos (Dependency _ v)) = + getCodeLens (Positioned pos (Dependency _ v)) = let cPos = Types.cabalPositionToLSPPosition pos - in CodeLens + in CodeLens { _range = Range cPos cPos - , _command = Just $ mkActionlessCommand v - , _data_ = Nothing + , _command = Just $ mkActionlessCommand v + , _data_ = Nothing } - + mkActionlessCommand :: T.Text -> Command mkActionlessCommand t = Command { _title = t , _command = "" - , _arguments = Nothing + , _arguments = Nothing } -- ---------------------------------------------------------------- @@ -481,8 +475,8 @@ lens state _plId clp = do -- | Handler for inlay hints hint :: PluginMethodHandler IdeState LSP.Method_TextDocumentInlayHint -hint state _plId clp = - if isInlayHintsSupported state +hint state _plId clp = + if isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri @@ -494,26 +488,26 @@ hint state _plId clp = let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps pure $ InL lenses - else + else pure $ InL [] - where + where getInlayHint :: Positioned SimpleDependency -> InlayHint - getInlayHint (Positioned pos (Dependency _ v)) = InlayHint + getInlayHint (Positioned pos (Dependency _ v)) = InlayHint { _position = Types.cabalPositionToLSPPosition pos , _label = InL v , _kind = Nothing - , _textEdits = Nothing - , _tooltip = Nothing - , _paddingLeft = Nothing - , _paddingRight = Nothing + , _textEdits = Nothing + , _tooltip = Nothing + , _paddingLeft = Nothing + , _paddingRight = Nothing , _data_ = Nothing } - + isInlayHintsSupported :: IdeState -> Bool isInlayHintsSupported ideState = let clientCaps = Shake.clientCapabilities $ shakeExtras ideState @@ -563,10 +557,10 @@ ofInterestRules recorder = do res = (Just fp, Just foi) return res where - summarize NotCabalFOI = BS.singleton 0 - summarize (IsCabalFOI OnDisk) = BS.singleton 1 + summarize NotCabalFOI = BS.singleton 0 + summarize (IsCabalFOI OnDisk) = BS.singleton 1 summarize (IsCabalFOI (Modified False)) = BS.singleton 2 - summarize (IsCabalFOI (Modified True)) = BS.singleton 3 + summarize (IsCabalFOI (Modified True)) = BS.singleton 3 getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus) getCabalFilesOfInterestUntracked = do @@ -640,7 +634,7 @@ computeCompletionsAt recorder ide prefInfo fp fields = do stanzaName = case fst ctx of Types.Stanza _ name -> name - _ -> Nothing + _ -> Nothing } completions <- completer completerRecorder completerData pure completions diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index acad6cc53e..6244584270 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -6,9 +6,11 @@ module Ide.Plugin.Cabal.Completion.Types where import Control.DeepSeq (NFData) import Control.Lens ((^.)) +import Data.Aeson ((.:)) +import qualified Data.Aeson as A import Data.Hashable -import qualified Data.Text as T import qualified Data.Map as M +import qualified Data.Text as T import Data.Typeable import Development.IDE as D import qualified Distribution.Fields as Syntax @@ -16,8 +18,6 @@ import qualified Distribution.PackageDescription as PD import qualified Distribution.Parsec.Position as Syntax import GHC.Generics import qualified Language.LSP.Protocol.Lens as JL -import qualified Data.Aeson as A -import Data.Aeson ((.:)) data Log = LogFileSplitError Position @@ -71,11 +71,11 @@ instance Hashable ParseCabalCommonSections instance NFData ParseCabalCommonSections -data BuildDependencyVersionMapping = BuildDependencyVersionMapping +data BuildDependencyVersionMapping = BuildDependencyVersionMapping deriving (Eq, Show, Typeable, Generic) - + instance Hashable BuildDependencyVersionMapping - + instance NFData BuildDependencyVersionMapping type instance RuleResult BuildDependencyVersionMapping = M.Map PkgName PkgVersion @@ -180,7 +180,7 @@ data CabalPrefixInfo = CabalPrefixInfo -- while 'LeftSide' means, a closing apostrophe has to be added after the completion item. data Apostrophe = Surrounded | LeftSide deriving (Eq, Ord, Show) - + type PkgName = T.Text type PkgVersion = T.Text @@ -191,18 +191,18 @@ data SimpleDependency = Dependency PkgName PkgVersion data Positioned a = Positioned Syntax.Position a deriving Show -data DependencyInstances = DependencyInstances +data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } deriving Show -- | Represents a concrete dependency entry in plan.json -data DependencyInstance = DependencyInstance - { _pkgName :: PkgName +data DependencyInstance = DependencyInstance + { _pkgName :: PkgName , _pkgVersion :: PkgVersion - , _pkgType :: T.Text + , _pkgType :: T.Text } -- missing some unneeded fields deriving (Show, Generic) - + instance NFData DependencyInstance instance A.FromJSON DependencyInstance where @@ -215,7 +215,7 @@ instance A.FromJSON DependencyInstance where instance A.FromJSON DependencyInstances where parseJSON = A.withObject "PlanJson" $ \obj -> do deps <- obj .: "install-plan" >>= A.parseJSON - return (DependencyInstances deps) + return (DependencyInstances deps) -- | Wraps a completion in apostrophes where appropriate. -- diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index f0acb7a8ca..ce17600a08 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings #-} module Ide.Plugin.Cabal.Dependencies ( DependencyInstance(..), @@ -7,16 +7,18 @@ module Ide.Plugin.Cabal.Dependencies ( planJsonPath, ) where -import Distribution.Fields qualified as Syntax -import Distribution.Parsec.Position qualified as Syntax +import qualified Distribution.Fields as Syntax +import qualified Distribution.Parsec.Position as Syntax -import Data.Text.Encoding qualified as Encoding -import Data.Text qualified as T -import System.FilePath ((), (<.>)) +import qualified Data.Text as T +import qualified Data.Text.Encoding as Encoding +import System.FilePath ((<.>), ()) -import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches), AllMatches(getAllMatches)) -import Data.ByteString (ByteString) -import Ide.Plugin.Cabal.Completion.Types +import Data.ByteString (ByteString) +import Ide.Plugin.Cabal.Completion.Types +import Text.Regex.TDFA (AllMatches (getAllMatches), + AllTextMatches (getAllTextMatches), + (=~)) planJsonPath :: FilePath planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now @@ -27,16 +29,16 @@ parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDe parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls parseDeps _ = [] --- | Matches valid Cabal dependency names +-- | Matches valid Cabal dependency names packageRegex :: T.Text packageRegex = "[a-zA-Z0-9_-]+" -- not sure if this is correct -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. mkPosDeps :: Syntax.FieldLine Syntax.Position -> [Positioned PkgName] -mkPosDeps (Syntax.FieldLine pos dep) = zipWith - (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) - (getPackageNames dep) +mkPosDeps (Syntax.FieldLine pos dep) = zipWith + (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) + (getPackageNames dep) (getPackageNameOffsets dep) where getPackageNames :: ByteString -> [T.Text] From d949f651116140f2c048c8ec961a2d3cf9543233 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Mon, 14 Oct 2024 16:16:54 +0200 Subject: [PATCH 10/11] Revert "formatting" VSCode formatting introduced too many changes --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 236 +++++++++--------- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 26 +- .../src/Ide/Plugin/Cabal/Dependencies.hs | 28 +-- 3 files changed, 147 insertions(+), 143 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index ea523f571f..678c363313 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -1,79 +1,85 @@ -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} module Ide.Plugin.Cabal (descriptor, haskellInteractionDescriptor, Log (..)) where -import Control.Concurrent.Strict -import Control.DeepSeq -import Control.Lens ((^.)) -import Control.Lens.Fold ((^?)) -import Control.Lens.Prism (_Just) -import Control.Monad.Extra -import Control.Monad.IO.Class -import Control.Monad.Trans.Class (lift) -import Control.Monad.Trans.Maybe (runMaybeT) -import qualified Data.Aeson as A -import qualified Data.ByteString as BS -import Data.Hashable -import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HashMap -import qualified Data.List.NonEmpty as NE -import qualified Data.Map as Map -import qualified Data.Maybe as Maybe -import qualified Data.Text () -import qualified Data.Text as T -import qualified Data.Text.Encoding as Encoding -import Data.Text.Utf16.Rope.Mixed as Rope -import Data.Typeable -import Development.IDE as D -import Development.IDE.Core.FileStore (getVersionedTextDoc) -import Development.IDE.Core.PluginUtils -import Development.IDE.Core.Shake (restartShakeSession) -import qualified Development.IDE.Core.Shake as Shake -import Development.IDE.Graph (Key, - alwaysRerun) -import Development.IDE.LSP.HoverDefinition (foundHover) -import qualified Development.IDE.Plugin.Completions.Logic as Ghcide -import Development.IDE.Types.Shake (toKey) -import qualified Distribution.Fields as Syntax -import Distribution.Package (Dependency) -import Distribution.PackageDescription (allBuildDepends, - depPkgName, - unPackageName) -import Distribution.PackageDescription.Configuration (flattenPackageDescription) -import qualified Distribution.Parsec.Position as Syntax -import GHC.Generics -import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd -import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields -import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes -import qualified Ide.Plugin.Cabal.Completion.Completions as Completions -import Ide.Plugin.Cabal.Completion.Types (BuildDependencyVersionMapping (..), - ParseCabalCommonSections (ParseCabalCommonSections), - ParseCabalFields (..), - ParseCabalFile (..), - ParsePlanJson (..), - Positioned (..), - SimpleDependency (..)) -import qualified Ide.Plugin.Cabal.Completion.Types as Types -import Ide.Plugin.Cabal.Definition (gotoDefinition) -import Ide.Plugin.Cabal.Dependencies -import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics -import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest -import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest -import Ide.Plugin.Cabal.Orphans () -import Ide.Plugin.Cabal.Outline -import qualified Ide.Plugin.Cabal.Parse as Parse -import Ide.Plugin.Error -import Ide.Types -import qualified Language.LSP.Protocol.Lens as JL -import qualified Language.LSP.Protocol.Message as LSP -import Language.LSP.Protocol.Types -import qualified Language.LSP.VFS as VFS -import System.FilePath (()) -import Text.Regex.TDFA +import Control.Concurrent.Strict +import Control.DeepSeq +import Control.Lens ((^.)) +import Control.Lens.Fold ((^?)) +import Control.Lens.Prism (_Just) +import Control.Monad.Extra +import Control.Monad.IO.Class +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Maybe (runMaybeT) +import Data.ByteString qualified as BS +import Data.HashMap.Strict (HashMap) +import Data.HashMap.Strict qualified as HashMap +import Data.Map qualified as Map +import Data.Hashable +import Data.List.NonEmpty qualified as NE +import Data.Maybe qualified as Maybe +import Data.Text qualified () +import Data.Text qualified as T +import Data.Text.Encoding qualified as Encoding +import Data.Text.Utf16.Rope.Mixed as Rope +import Data.Typeable +import Data.Aeson qualified as A +import Development.IDE as D +import Development.IDE.Core.FileStore (getVersionedTextDoc) +import Development.IDE.Core.PluginUtils +import Development.IDE.Core.Shake (restartShakeSession) +import Development.IDE.Core.Shake qualified as Shake +import Development.IDE.Graph + ( Key, + alwaysRerun, + ) +import Development.IDE.LSP.HoverDefinition (foundHover) +import Development.IDE.Plugin.Completions.Logic qualified as Ghcide +import Development.IDE.Types.Shake (toKey) +import Distribution.Fields qualified as Syntax +import Distribution.Package (Dependency) +import Distribution.PackageDescription + ( allBuildDepends, + depPkgName, + unPackageName, + ) +import Distribution.PackageDescription.Configuration (flattenPackageDescription) +import Distribution.Parsec.Position qualified as Syntax +import GHC.Generics +import Ide.Plugin.Cabal.CabalAdd qualified as CabalAdd +import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields +import Ide.Plugin.Cabal.Completion.Completer.Types qualified as CompleterTypes +import Ide.Plugin.Cabal.Completion.Completions qualified as Completions +import Ide.Plugin.Cabal.Completion.Types + ( ParseCabalCommonSections (ParseCabalCommonSections), + ParseCabalFields (..), + ParseCabalFile (..), + ParsePlanJson (..), + BuildDependencyVersionMapping (..), + Positioned(..), + SimpleDependency(..) + ) +import Ide.Plugin.Cabal.Completion.Types qualified as Types +import Ide.Plugin.Cabal.Definition (gotoDefinition) +import Ide.Plugin.Cabal.Dependencies +import Ide.Plugin.Cabal.Diagnostics qualified as Diagnostics +import Ide.Plugin.Cabal.FieldSuggest qualified as FieldSuggest +import Ide.Plugin.Cabal.LicenseSuggest qualified as LicenseSuggest +import Ide.Plugin.Cabal.Orphans () +import Ide.Plugin.Cabal.Outline +import Ide.Plugin.Cabal.Parse qualified as Parse +import Ide.Plugin.Error +import Ide.Types +import Language.LSP.Protocol.Lens qualified as JL +import Language.LSP.Protocol.Message qualified as LSP +import Language.LSP.Protocol.Types +import Language.LSP.VFS qualified as VFS +import Text.Regex.TDFA +import System.FilePath (()) data Log = LogModificationTime NormalizedFilePath FileVersion @@ -135,13 +141,13 @@ descriptor recorder plId = pluginHandlers = mconcat [ mkPluginHandler LSP.SMethod_TextDocumentCodeAction licenseSuggestCodeAction - , mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder - , mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline - , mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder - , mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition - , mkPluginHandler LSP.SMethod_TextDocumentHover hover - , mkPluginHandler LSP.SMethod_TextDocumentInlayHint hint - , mkPluginHandler LSP.SMethod_TextDocumentCodeLens lens + , mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder + , mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline + , mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder + , mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition + , mkPluginHandler LSP.SMethod_TextDocumentHover hover + , mkPluginHandler LSP.SMethod_TextDocumentInlayHint hint + , mkPluginHandler LSP.SMethod_TextDocumentCodeLens lens ], pluginNotificationHandlers = mconcat @@ -235,21 +241,21 @@ cabalRules recorder plId = do ) fields pure ([], Just commonSections) - + define (cmapWithPrio LogShake recorder) $ \ParsePlanJson file -> do (_, planSrc) <- use_ GetFileContents file - + contents <- case planSrc of Just sources -> pure $ Encoding.encodeUtf8 $ Rope.toText sources - Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file - + Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file + pure ([], installPlan <$> A.decodeStrict contents) - + define (cmapWithPrio LogShake recorder) $ \BuildDependencyVersionMapping file -> do - deps <- use_ ParsePlanJson file - + deps <- use_ ParsePlanJson file + let versionMapping = Map.fromList $ map (\d -> (_pkgName d, _pkgVersion d)) deps - + pure ([], Just versionMapping) define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do @@ -422,7 +428,7 @@ hover ide _ msgParam = do getMatch :: (T.Text, T.Text, T.Text, [T.Text]) -> Maybe T.Text getMatch (_, _, _, [dependency]) = Just dependency - getMatch (_, _, _, _) = Nothing -- impossible case + getMatch (_, _, _, _) = Nothing -- impossible case documentationText :: T.Text -> T.Text documentationText package = "[Documentation](https://hackage.haskell.org/package/" <> package <> ")" @@ -431,42 +437,42 @@ hover ide _ msgParam = do -- ---------------------------------------------------------------- lens :: PluginMethodHandler IdeState LSP.Method_TextDocumentCodeLens -lens state _plId clp = do +lens state _plId clp = do if not $ isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri nfp <- getNormalizedFilePathE uri cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp - + let positionedDeps = concatMap parseDeps cabalFields let rfp = rootDir state let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps - + pure $ InL lenses else pure $ InL [] where getCodeLens :: Positioned SimpleDependency -> CodeLens - getCodeLens (Positioned pos (Dependency _ v)) = + getCodeLens (Positioned pos (Dependency _ v)) = let cPos = Types.cabalPositionToLSPPosition pos - in CodeLens + in CodeLens { _range = Range cPos cPos - , _command = Just $ mkActionlessCommand v - , _data_ = Nothing + , _command = Just $ mkActionlessCommand v + , _data_ = Nothing } - + mkActionlessCommand :: T.Text -> Command mkActionlessCommand t = Command { _title = t , _command = "" - , _arguments = Nothing + , _arguments = Nothing } -- ---------------------------------------------------------------- @@ -475,8 +481,8 @@ lens state _plId clp = do -- | Handler for inlay hints hint :: PluginMethodHandler IdeState LSP.Method_TextDocumentInlayHint -hint state _plId clp = - if isInlayHintsSupported state +hint state _plId clp = + if isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri @@ -488,26 +494,26 @@ hint state _plId clp = let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps pure $ InL lenses - else + else pure $ InL [] - where + where getInlayHint :: Positioned SimpleDependency -> InlayHint - getInlayHint (Positioned pos (Dependency _ v)) = InlayHint + getInlayHint (Positioned pos (Dependency _ v)) = InlayHint { _position = Types.cabalPositionToLSPPosition pos , _label = InL v , _kind = Nothing - , _textEdits = Nothing - , _tooltip = Nothing - , _paddingLeft = Nothing - , _paddingRight = Nothing + , _textEdits = Nothing + , _tooltip = Nothing + , _paddingLeft = Nothing + , _paddingRight = Nothing , _data_ = Nothing } - + isInlayHintsSupported :: IdeState -> Bool isInlayHintsSupported ideState = let clientCaps = Shake.clientCapabilities $ shakeExtras ideState @@ -557,10 +563,10 @@ ofInterestRules recorder = do res = (Just fp, Just foi) return res where - summarize NotCabalFOI = BS.singleton 0 - summarize (IsCabalFOI OnDisk) = BS.singleton 1 + summarize NotCabalFOI = BS.singleton 0 + summarize (IsCabalFOI OnDisk) = BS.singleton 1 summarize (IsCabalFOI (Modified False)) = BS.singleton 2 - summarize (IsCabalFOI (Modified True)) = BS.singleton 3 + summarize (IsCabalFOI (Modified True)) = BS.singleton 3 getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus) getCabalFilesOfInterestUntracked = do @@ -634,7 +640,7 @@ computeCompletionsAt recorder ide prefInfo fp fields = do stanzaName = case fst ctx of Types.Stanza _ name -> name - _ -> Nothing + _ -> Nothing } completions <- completer completerRecorder completerData pure completions diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index 6244584270..acad6cc53e 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -6,11 +6,9 @@ module Ide.Plugin.Cabal.Completion.Types where import Control.DeepSeq (NFData) import Control.Lens ((^.)) -import Data.Aeson ((.:)) -import qualified Data.Aeson as A import Data.Hashable -import qualified Data.Map as M import qualified Data.Text as T +import qualified Data.Map as M import Data.Typeable import Development.IDE as D import qualified Distribution.Fields as Syntax @@ -18,6 +16,8 @@ import qualified Distribution.PackageDescription as PD import qualified Distribution.Parsec.Position as Syntax import GHC.Generics import qualified Language.LSP.Protocol.Lens as JL +import qualified Data.Aeson as A +import Data.Aeson ((.:)) data Log = LogFileSplitError Position @@ -71,11 +71,11 @@ instance Hashable ParseCabalCommonSections instance NFData ParseCabalCommonSections -data BuildDependencyVersionMapping = BuildDependencyVersionMapping +data BuildDependencyVersionMapping = BuildDependencyVersionMapping deriving (Eq, Show, Typeable, Generic) - + instance Hashable BuildDependencyVersionMapping - + instance NFData BuildDependencyVersionMapping type instance RuleResult BuildDependencyVersionMapping = M.Map PkgName PkgVersion @@ -180,7 +180,7 @@ data CabalPrefixInfo = CabalPrefixInfo -- while 'LeftSide' means, a closing apostrophe has to be added after the completion item. data Apostrophe = Surrounded | LeftSide deriving (Eq, Ord, Show) - + type PkgName = T.Text type PkgVersion = T.Text @@ -191,18 +191,18 @@ data SimpleDependency = Dependency PkgName PkgVersion data Positioned a = Positioned Syntax.Position a deriving Show -data DependencyInstances = DependencyInstances +data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } deriving Show -- | Represents a concrete dependency entry in plan.json -data DependencyInstance = DependencyInstance - { _pkgName :: PkgName +data DependencyInstance = DependencyInstance + { _pkgName :: PkgName , _pkgVersion :: PkgVersion - , _pkgType :: T.Text + , _pkgType :: T.Text } -- missing some unneeded fields deriving (Show, Generic) - + instance NFData DependencyInstance instance A.FromJSON DependencyInstance where @@ -215,7 +215,7 @@ instance A.FromJSON DependencyInstance where instance A.FromJSON DependencyInstances where parseJSON = A.withObject "PlanJson" $ \obj -> do deps <- obj .: "install-plan" >>= A.parseJSON - return (DependencyInstances deps) + return (DependencyInstances deps) -- | Wraps a completion in apostrophes where appropriate. -- diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index ce17600a08..f0acb7a8ca 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings #-} module Ide.Plugin.Cabal.Dependencies ( DependencyInstance(..), @@ -7,18 +7,16 @@ module Ide.Plugin.Cabal.Dependencies ( planJsonPath, ) where -import qualified Distribution.Fields as Syntax -import qualified Distribution.Parsec.Position as Syntax +import Distribution.Fields qualified as Syntax +import Distribution.Parsec.Position qualified as Syntax -import qualified Data.Text as T -import qualified Data.Text.Encoding as Encoding -import System.FilePath ((<.>), ()) +import Data.Text.Encoding qualified as Encoding +import Data.Text qualified as T +import System.FilePath ((), (<.>)) -import Data.ByteString (ByteString) -import Ide.Plugin.Cabal.Completion.Types -import Text.Regex.TDFA (AllMatches (getAllMatches), - AllTextMatches (getAllTextMatches), - (=~)) +import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches), AllMatches(getAllMatches)) +import Data.ByteString (ByteString) +import Ide.Plugin.Cabal.Completion.Types planJsonPath :: FilePath planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now @@ -29,16 +27,16 @@ parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDe parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls parseDeps _ = [] --- | Matches valid Cabal dependency names +-- | Matches valid Cabal dependency names packageRegex :: T.Text packageRegex = "[a-zA-Z0-9_-]+" -- not sure if this is correct -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. mkPosDeps :: Syntax.FieldLine Syntax.Position -> [Positioned PkgName] -mkPosDeps (Syntax.FieldLine pos dep) = zipWith - (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) - (getPackageNames dep) +mkPosDeps (Syntax.FieldLine pos dep) = zipWith + (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) + (getPackageNames dep) (getPackageNameOffsets dep) where getPackageNames :: ByteString -> [T.Text] From 73c1c3d2b0fc00ab81b36739bcca03efd7e97404 Mon Sep 17 00:00:00 2001 From: lucalabs-de Date: Mon, 14 Oct 2024 16:21:02 +0200 Subject: [PATCH 11/11] fix formatting --- .../hls-cabal-plugin/src/Ide/Plugin/Cabal.hs | 222 +++++++++--------- .../src/Ide/Plugin/Cabal/Completion/Types.hs | 26 +- .../src/Ide/Plugin/Cabal/Dependencies.hs | 28 ++- 3 files changed, 136 insertions(+), 140 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs index 678c363313..af65cf5168 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs @@ -1,85 +1,79 @@ -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} module Ide.Plugin.Cabal (descriptor, haskellInteractionDescriptor, Log (..)) where -import Control.Concurrent.Strict -import Control.DeepSeq -import Control.Lens ((^.)) -import Control.Lens.Fold ((^?)) -import Control.Lens.Prism (_Just) -import Control.Monad.Extra -import Control.Monad.IO.Class -import Control.Monad.Trans.Class (lift) -import Control.Monad.Trans.Maybe (runMaybeT) -import Data.ByteString qualified as BS -import Data.HashMap.Strict (HashMap) -import Data.HashMap.Strict qualified as HashMap -import Data.Map qualified as Map -import Data.Hashable -import Data.List.NonEmpty qualified as NE -import Data.Maybe qualified as Maybe -import Data.Text qualified () -import Data.Text qualified as T -import Data.Text.Encoding qualified as Encoding -import Data.Text.Utf16.Rope.Mixed as Rope -import Data.Typeable -import Data.Aeson qualified as A -import Development.IDE as D -import Development.IDE.Core.FileStore (getVersionedTextDoc) -import Development.IDE.Core.PluginUtils -import Development.IDE.Core.Shake (restartShakeSession) -import Development.IDE.Core.Shake qualified as Shake -import Development.IDE.Graph - ( Key, - alwaysRerun, - ) -import Development.IDE.LSP.HoverDefinition (foundHover) -import Development.IDE.Plugin.Completions.Logic qualified as Ghcide -import Development.IDE.Types.Shake (toKey) -import Distribution.Fields qualified as Syntax -import Distribution.Package (Dependency) -import Distribution.PackageDescription - ( allBuildDepends, - depPkgName, - unPackageName, - ) -import Distribution.PackageDescription.Configuration (flattenPackageDescription) -import Distribution.Parsec.Position qualified as Syntax -import GHC.Generics -import Ide.Plugin.Cabal.CabalAdd qualified as CabalAdd -import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields -import Ide.Plugin.Cabal.Completion.Completer.Types qualified as CompleterTypes -import Ide.Plugin.Cabal.Completion.Completions qualified as Completions -import Ide.Plugin.Cabal.Completion.Types - ( ParseCabalCommonSections (ParseCabalCommonSections), - ParseCabalFields (..), - ParseCabalFile (..), - ParsePlanJson (..), - BuildDependencyVersionMapping (..), - Positioned(..), - SimpleDependency(..) - ) -import Ide.Plugin.Cabal.Completion.Types qualified as Types -import Ide.Plugin.Cabal.Definition (gotoDefinition) -import Ide.Plugin.Cabal.Dependencies -import Ide.Plugin.Cabal.Diagnostics qualified as Diagnostics -import Ide.Plugin.Cabal.FieldSuggest qualified as FieldSuggest -import Ide.Plugin.Cabal.LicenseSuggest qualified as LicenseSuggest -import Ide.Plugin.Cabal.Orphans () -import Ide.Plugin.Cabal.Outline -import Ide.Plugin.Cabal.Parse qualified as Parse -import Ide.Plugin.Error -import Ide.Types -import Language.LSP.Protocol.Lens qualified as JL -import Language.LSP.Protocol.Message qualified as LSP -import Language.LSP.Protocol.Types -import Language.LSP.VFS qualified as VFS -import Text.Regex.TDFA -import System.FilePath (()) +import Control.Concurrent.Strict +import Control.DeepSeq +import Control.Lens ((^.)) +import Control.Lens.Fold ((^?)) +import Control.Lens.Prism (_Just) +import Control.Monad.Extra +import Control.Monad.IO.Class +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Maybe (runMaybeT) +import qualified Data.Aeson as A +import qualified Data.ByteString as BS +import Data.Hashable +import Data.HashMap.Strict (HashMap) +import qualified Data.HashMap.Strict as HashMap +import qualified Data.List.NonEmpty as NE +import qualified Data.Map as Map +import qualified Data.Maybe as Maybe +import qualified Data.Text () +import qualified Data.Text as T +import qualified Data.Text.Encoding as Encoding +import Data.Text.Utf16.Rope.Mixed as Rope +import Data.Typeable +import Development.IDE as D +import Development.IDE.Core.FileStore (getVersionedTextDoc) +import Development.IDE.Core.PluginUtils +import Development.IDE.Core.Shake (restartShakeSession) +import qualified Development.IDE.Core.Shake as Shake +import Development.IDE.Graph (Key, + alwaysRerun) +import Development.IDE.LSP.HoverDefinition (foundHover) +import qualified Development.IDE.Plugin.Completions.Logic as Ghcide +import Development.IDE.Types.Shake (toKey) +import qualified Distribution.Fields as Syntax +import Distribution.Package (Dependency) +import Distribution.PackageDescription (allBuildDepends, + depPkgName, + unPackageName) +import Distribution.PackageDescription.Configuration (flattenPackageDescription) +import qualified Distribution.Parsec.Position as Syntax +import GHC.Generics +import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd +import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields +import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes +import qualified Ide.Plugin.Cabal.Completion.Completions as Completions +import Ide.Plugin.Cabal.Completion.Types (BuildDependencyVersionMapping (..), + ParseCabalCommonSections (ParseCabalCommonSections), + ParseCabalFields (..), + ParseCabalFile (..), + ParsePlanJson (..), + Positioned (..), + SimpleDependency (..)) +import qualified Ide.Plugin.Cabal.Completion.Types as Types +import Ide.Plugin.Cabal.Definition (gotoDefinition) +import Ide.Plugin.Cabal.Dependencies +import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics +import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest +import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest +import Ide.Plugin.Cabal.Orphans () +import Ide.Plugin.Cabal.Outline +import qualified Ide.Plugin.Cabal.Parse as Parse +import Ide.Plugin.Error +import Ide.Types +import qualified Language.LSP.Protocol.Lens as JL +import qualified Language.LSP.Protocol.Message as LSP +import Language.LSP.Protocol.Types +import qualified Language.LSP.VFS as VFS +import System.FilePath (()) +import Text.Regex.TDFA data Log = LogModificationTime NormalizedFilePath FileVersion @@ -241,21 +235,21 @@ cabalRules recorder plId = do ) fields pure ([], Just commonSections) - + define (cmapWithPrio LogShake recorder) $ \ParsePlanJson file -> do (_, planSrc) <- use_ GetFileContents file - + contents <- case planSrc of Just sources -> pure $ Encoding.encodeUtf8 $ Rope.toText sources - Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file - + Nothing -> do liftIO $ BS.readFile $ fromNormalizedFilePath file + pure ([], installPlan <$> A.decodeStrict contents) - + define (cmapWithPrio LogShake recorder) $ \BuildDependencyVersionMapping file -> do - deps <- use_ ParsePlanJson file - + deps <- use_ ParsePlanJson file + let versionMapping = Map.fromList $ map (\d -> (_pkgName d, _pkgVersion d)) deps - + pure ([], Just versionMapping) define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do @@ -428,7 +422,7 @@ hover ide _ msgParam = do getMatch :: (T.Text, T.Text, T.Text, [T.Text]) -> Maybe T.Text getMatch (_, _, _, [dependency]) = Just dependency - getMatch (_, _, _, _) = Nothing -- impossible case + getMatch (_, _, _, _) = Nothing -- impossible case documentationText :: T.Text -> T.Text documentationText package = "[Documentation](https://hackage.haskell.org/package/" <> package <> ")" @@ -437,42 +431,42 @@ hover ide _ msgParam = do -- ---------------------------------------------------------------- lens :: PluginMethodHandler IdeState LSP.Method_TextDocumentCodeLens -lens state _plId clp = do +lens state _plId clp = do if not $ isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri nfp <- getNormalizedFilePathE uri cabalFields <- runActionE "cabal.cabal-lens" state $ useE ParseCabalFields nfp - + let positionedDeps = concatMap parseDeps cabalFields let rfp = rootDir state let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getCodeLens . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps - + pure $ InL lenses else pure $ InL [] where getCodeLens :: Positioned SimpleDependency -> CodeLens - getCodeLens (Positioned pos (Dependency _ v)) = + getCodeLens (Positioned pos (Dependency _ v)) = let cPos = Types.cabalPositionToLSPPosition pos - in CodeLens + in CodeLens { _range = Range cPos cPos - , _command = Just $ mkActionlessCommand v - , _data_ = Nothing + , _command = Just $ mkActionlessCommand v + , _data_ = Nothing } - + mkActionlessCommand :: T.Text -> Command mkActionlessCommand t = Command { _title = t , _command = "" - , _arguments = Nothing + , _arguments = Nothing } -- ---------------------------------------------------------------- @@ -481,8 +475,8 @@ lens state _plId clp = do -- | Handler for inlay hints hint :: PluginMethodHandler IdeState LSP.Method_TextDocumentInlayHint -hint state _plId clp = - if isInlayHintsSupported state +hint state _plId clp = + if isInlayHintsSupported state then do let uri = clp ^. JL.textDocument . JL.uri @@ -494,26 +488,26 @@ hint state _plId clp = let planJson = toNormalizedFilePath $ rfp planJsonPath planDeps <- runActionE "cabal.cabal-lens" state $ useE BuildDependencyVersionMapping planJson - let lenses = Maybe.mapMaybe - (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) + let lenses = Maybe.mapMaybe + (\(Positioned pos name) -> getInlayHint . Positioned pos . Dependency name <$> Map.lookup name planDeps) positionedDeps pure $ InL lenses - else + else pure $ InL [] - where + where getInlayHint :: Positioned SimpleDependency -> InlayHint - getInlayHint (Positioned pos (Dependency _ v)) = InlayHint + getInlayHint (Positioned pos (Dependency _ v)) = InlayHint { _position = Types.cabalPositionToLSPPosition pos , _label = InL v , _kind = Nothing - , _textEdits = Nothing - , _tooltip = Nothing - , _paddingLeft = Nothing - , _paddingRight = Nothing + , _textEdits = Nothing + , _tooltip = Nothing + , _paddingLeft = Nothing + , _paddingRight = Nothing , _data_ = Nothing } - + isInlayHintsSupported :: IdeState -> Bool isInlayHintsSupported ideState = let clientCaps = Shake.clientCapabilities $ shakeExtras ideState @@ -563,10 +557,10 @@ ofInterestRules recorder = do res = (Just fp, Just foi) return res where - summarize NotCabalFOI = BS.singleton 0 - summarize (IsCabalFOI OnDisk) = BS.singleton 1 + summarize NotCabalFOI = BS.singleton 0 + summarize (IsCabalFOI OnDisk) = BS.singleton 1 summarize (IsCabalFOI (Modified False)) = BS.singleton 2 - summarize (IsCabalFOI (Modified True)) = BS.singleton 3 + summarize (IsCabalFOI (Modified True)) = BS.singleton 3 getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus) getCabalFilesOfInterestUntracked = do @@ -640,7 +634,7 @@ computeCompletionsAt recorder ide prefInfo fp fields = do stanzaName = case fst ctx of Types.Stanza _ name -> name - _ -> Nothing + _ -> Nothing } completions <- completer completerRecorder completerData pure completions diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs index acad6cc53e..6244584270 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs @@ -6,9 +6,11 @@ module Ide.Plugin.Cabal.Completion.Types where import Control.DeepSeq (NFData) import Control.Lens ((^.)) +import Data.Aeson ((.:)) +import qualified Data.Aeson as A import Data.Hashable -import qualified Data.Text as T import qualified Data.Map as M +import qualified Data.Text as T import Data.Typeable import Development.IDE as D import qualified Distribution.Fields as Syntax @@ -16,8 +18,6 @@ import qualified Distribution.PackageDescription as PD import qualified Distribution.Parsec.Position as Syntax import GHC.Generics import qualified Language.LSP.Protocol.Lens as JL -import qualified Data.Aeson as A -import Data.Aeson ((.:)) data Log = LogFileSplitError Position @@ -71,11 +71,11 @@ instance Hashable ParseCabalCommonSections instance NFData ParseCabalCommonSections -data BuildDependencyVersionMapping = BuildDependencyVersionMapping +data BuildDependencyVersionMapping = BuildDependencyVersionMapping deriving (Eq, Show, Typeable, Generic) - + instance Hashable BuildDependencyVersionMapping - + instance NFData BuildDependencyVersionMapping type instance RuleResult BuildDependencyVersionMapping = M.Map PkgName PkgVersion @@ -180,7 +180,7 @@ data CabalPrefixInfo = CabalPrefixInfo -- while 'LeftSide' means, a closing apostrophe has to be added after the completion item. data Apostrophe = Surrounded | LeftSide deriving (Eq, Ord, Show) - + type PkgName = T.Text type PkgVersion = T.Text @@ -191,18 +191,18 @@ data SimpleDependency = Dependency PkgName PkgVersion data Positioned a = Positioned Syntax.Position a deriving Show -data DependencyInstances = DependencyInstances +data DependencyInstances = DependencyInstances { installPlan :: [DependencyInstance] } deriving Show -- | Represents a concrete dependency entry in plan.json -data DependencyInstance = DependencyInstance - { _pkgName :: PkgName +data DependencyInstance = DependencyInstance + { _pkgName :: PkgName , _pkgVersion :: PkgVersion - , _pkgType :: T.Text + , _pkgType :: T.Text } -- missing some unneeded fields deriving (Show, Generic) - + instance NFData DependencyInstance instance A.FromJSON DependencyInstance where @@ -215,7 +215,7 @@ instance A.FromJSON DependencyInstance where instance A.FromJSON DependencyInstances where parseJSON = A.withObject "PlanJson" $ \obj -> do deps <- obj .: "install-plan" >>= A.parseJSON - return (DependencyInstances deps) + return (DependencyInstances deps) -- | Wraps a completion in apostrophes where appropriate. -- diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs index f0acb7a8ca..ce17600a08 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Dependencies.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings #-} module Ide.Plugin.Cabal.Dependencies ( DependencyInstance(..), @@ -7,16 +7,18 @@ module Ide.Plugin.Cabal.Dependencies ( planJsonPath, ) where -import Distribution.Fields qualified as Syntax -import Distribution.Parsec.Position qualified as Syntax +import qualified Distribution.Fields as Syntax +import qualified Distribution.Parsec.Position as Syntax -import Data.Text.Encoding qualified as Encoding -import Data.Text qualified as T -import System.FilePath ((), (<.>)) +import qualified Data.Text as T +import qualified Data.Text.Encoding as Encoding +import System.FilePath ((<.>), ()) -import Text.Regex.TDFA ((=~), AllTextMatches (getAllTextMatches), AllMatches(getAllMatches)) -import Data.ByteString (ByteString) -import Ide.Plugin.Cabal.Completion.Types +import Data.ByteString (ByteString) +import Ide.Plugin.Cabal.Completion.Types +import Text.Regex.TDFA (AllMatches (getAllMatches), + AllTextMatches (getAllTextMatches), + (=~)) planJsonPath :: FilePath planJsonPath = "dist-newstyle" "cache" "plan" <.> "json" -- hard coded for now @@ -27,16 +29,16 @@ parseDeps (Syntax.Field (Syntax.Name _ "build-depends") fls) = concatMap mkPosDe parseDeps (Syntax.Section _ _ fls) = concatMap parseDeps fls parseDeps _ = [] --- | Matches valid Cabal dependency names +-- | Matches valid Cabal dependency names packageRegex :: T.Text packageRegex = "[a-zA-Z0-9_-]+" -- not sure if this is correct -- | Parses a single FieldLine of Cabal dependencies. Returns a list since a single line may -- contain multiple dependencies. mkPosDeps :: Syntax.FieldLine Syntax.Position -> [Positioned PkgName] -mkPosDeps (Syntax.FieldLine pos dep) = zipWith - (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) - (getPackageNames dep) +mkPosDeps (Syntax.FieldLine pos dep) = zipWith + (\n (o, _) -> Positioned (Syntax.Position (Syntax.positionRow pos) (Syntax.positionCol pos + o + 1)) n) + (getPackageNames dep) (getPackageNameOffsets dep) where getPackageNames :: ByteString -> [T.Text]