hls-cabal-plugin 2.1.0.0 → 2.2.0.0
raw patch · 13 files changed
+297/−235 lines, 13 filesdep ~ghcidedep ~hls-graphdep ~hls-plugin-apiPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: ghcide, hls-graph, hls-plugin-api, hls-test-utils, lsp, lsp-types
API changes (from Hackage documentation)
- Ide.Plugin.Cabal.Completion.Completer.FilePath: PathCompletionInfo :: Text -> FilePath -> FilePath -> Maybe Apostrophe -> PathCompletionInfo
- Ide.Plugin.Cabal.Completion.Completer.FilePath: [isStringNotationPath] :: PathCompletionInfo -> Maybe Apostrophe
- Ide.Plugin.Cabal.Completion.Completer.FilePath: [pathSegment] :: PathCompletionInfo -> Text
- Ide.Plugin.Cabal.Completion.Completer.FilePath: [queryDirectory] :: PathCompletionInfo -> FilePath
- Ide.Plugin.Cabal.Completion.Completer.FilePath: [workingDirectory] :: PathCompletionInfo -> FilePath
- Ide.Plugin.Cabal.Completion.Completer.FilePath: data PathCompletionInfo
- Ide.Plugin.Cabal.Completion.Completer.FilePath: instance GHC.Classes.Eq Ide.Plugin.Cabal.Completion.Completer.FilePath.PathCompletionInfo
- Ide.Plugin.Cabal.Completion.Completer.FilePath: instance GHC.Show.Show Ide.Plugin.Cabal.Completion.Completer.FilePath.PathCompletionInfo
- Ide.Plugin.Cabal.Completion.Completer.FilePath: pathCompletionInfoFromCabalPrefixInfo :: CabalPrefixInfo -> PathCompletionInfo
- Ide.Plugin.Cabal.Completion.Completer.Module: extractRelativeDirsFromStanza :: Maybe StanzaName -> GenericPackageDescription -> (GenericPackageDescription -> [(UnqualComponentName, CondTree b c a)]) -> (a -> BuildInfo) -> [FilePath]
- Ide.Plugin.Cabal.Completion.Completer.Module: sourceDirsExtractionBenchmark :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
- Ide.Plugin.Cabal.Completion.Completer.Module: sourceDirsExtractionExecutable :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
- Ide.Plugin.Cabal.Completion.Completer.Module: sourceDirsExtractionLibrary :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
- Ide.Plugin.Cabal.Completion.Completer.Module: sourceDirsExtractionTestSuite :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
+ Ide.Plugin.Cabal.Completion.Completer.FilePath: mainIsCompleter :: (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> Completer
+ Ide.Plugin.Cabal.Completion.Completer.Paths: PathCompletionInfo :: Text -> FilePath -> FilePath -> Maybe Apostrophe -> PathCompletionInfo
+ Ide.Plugin.Cabal.Completion.Completer.Paths: [isStringNotationPath] :: PathCompletionInfo -> Maybe Apostrophe
+ Ide.Plugin.Cabal.Completion.Completer.Paths: [pathSegment] :: PathCompletionInfo -> Text
+ Ide.Plugin.Cabal.Completion.Completer.Paths: [queryDirectory] :: PathCompletionInfo -> FilePath
+ Ide.Plugin.Cabal.Completion.Completer.Paths: [workingDirectory] :: PathCompletionInfo -> FilePath
+ Ide.Plugin.Cabal.Completion.Completer.Paths: data PathCompletionInfo
+ Ide.Plugin.Cabal.Completion.Completer.Paths: extractRelativeDirsFromStanza :: Maybe StanzaName -> GenericPackageDescription -> (GenericPackageDescription -> [(UnqualComponentName, CondTree b c a)]) -> (a -> BuildInfo) -> [FilePath]
+ Ide.Plugin.Cabal.Completion.Completer.Paths: instance GHC.Classes.Eq Ide.Plugin.Cabal.Completion.Completer.Paths.PathCompletionInfo
+ Ide.Plugin.Cabal.Completion.Completer.Paths: instance GHC.Show.Show Ide.Plugin.Cabal.Completion.Completer.Paths.PathCompletionInfo
+ Ide.Plugin.Cabal.Completion.Completer.Paths: pathCompletionInfoFromCabalPrefixInfo :: FilePath -> CabalPrefixInfo -> PathCompletionInfo
+ Ide.Plugin.Cabal.Completion.Completer.Paths: sourceDirsExtractionBenchmark :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
+ Ide.Plugin.Cabal.Completion.Completer.Paths: sourceDirsExtractionExecutable :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
+ Ide.Plugin.Cabal.Completion.Completer.Paths: sourceDirsExtractionLibrary :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
+ Ide.Plugin.Cabal.Completion.Completer.Paths: sourceDirsExtractionTestSuite :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]
Files
- hls-cabal-plugin.cabal +8/−7
- src/Ide/Plugin/Cabal.hs +1/−1
- src/Ide/Plugin/Cabal/Completion/Completer/FilePath.hs +37/−42
- src/Ide/Plugin/Cabal/Completion/Completer/Module.hs +20/−78
- src/Ide/Plugin/Cabal/Completion/Completer/Paths.hs +118/−0
- src/Ide/Plugin/Cabal/Completion/Completions.hs +1/−1
- src/Ide/Plugin/Cabal/Completion/Data.hs +6/−5
- src/Ide/Plugin/Cabal/Completion/Types.hs +3/−3
- test/Completer.hs +60/−51
- test/Context.hs +15/−20
- test/Main.hs +1/−18
- test/Utils.hs +15/−9
- test/testdata/completer.cabal +12/−0
hls-cabal-plugin.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: hls-cabal-plugin-version: 2.1.0.0+version: 2.2.0.0 synopsis: Cabal integration plugin with Haskell Language Server description: Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>@@ -29,6 +29,7 @@ Ide.Plugin.Cabal.Diagnostics Ide.Plugin.Cabal.Completion.Completer.FilePath Ide.Plugin.Cabal.Completion.Completer.Module+ Ide.Plugin.Cabal.Completion.Completer.Paths Ide.Plugin.Cabal.Completion.Completer.Simple Ide.Plugin.Cabal.Completion.Completer.Snippet Ide.Plugin.Cabal.Completion.Completer.Types@@ -48,13 +49,13 @@ , directory , filepath , extra >=1.7.4- , ghcide == 2.1.0.0+ , ghcide == 2.2.0.0 , hashable- , hls-plugin-api == 2.1.0.0- , hls-graph == 2.1.0.0+ , hls-plugin-api == 2.2.0.0+ , hls-graph == 2.2.0.0 , lens- , lsp ^>=2.1.0.0- , lsp-types ^>=2.0.1.0+ , lsp ^>=2.2+ , lsp-types ^>=2.0.2 , regex-tdfa ^>=1.3.1 , stm , text@@ -83,7 +84,7 @@ , filepath , ghcide , hls-cabal-plugin- , hls-test-utils == 2.1.0.0+ , hls-test-utils == 2.2.0.0 , lens , lsp , lsp-types
src/Ide/Plugin/Cabal.hs view
@@ -75,7 +75,7 @@ "Determined completion context:" <+> viaShow context <+> "for cursor position:"- <+> viaShow position+ <+> pretty position LogCompletions logs -> pretty logs descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
src/Ide/Plugin/Cabal/Completion/Completer/FilePath.hs view
@@ -5,9 +5,11 @@ import Control.Exception (evaluate, try) import Control.Monad (filterM)-import Control.Monad.Extra (forM)+import Control.Monad.Extra (concatForM, forM) import qualified Data.Text as T+import Distribution.PackageDescription (GenericPackageDescription) import Ide.Logger+import Ide.Plugin.Cabal.Completion.Completer.Paths import Ide.Plugin.Cabal.Completion.Completer.Simple import Ide.Plugin.Cabal.Completion.Completer.Types import Ide.Plugin.Cabal.Completion.Types@@ -23,7 +25,7 @@ filePathCompleter :: Completer filePathCompleter recorder cData = do let prefInfo = cabalPrefixInfo cData- complInfo = pathCompletionInfoFromCabalPrefixInfo prefInfo+ complInfo = pathCompletionInfoFromCabalPrefixInfo "" prefInfo filePathCompletions <- listFileCompletions recorder complInfo let scored = Fuzzy.simpleFilter@@ -39,12 +41,44 @@ pure $ mkCompletionItem (completionRange prefInfo) fullFilePath fullFilePath ) +mainIsCompleter :: (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> Completer+mainIsCompleter extractionFunction recorder cData = do+ mGPD <- getLatestGPD cData+ case mGPD of+ Just gpd -> do+ let srcDirs = extractionFunction sName gpd+ concatForM srcDirs+ (\dir' -> do+ let dir = FP.normalise dir'+ let pathInfo = pathCompletionInfoFromCabalPrefixInfo dir prefInfo+ completions <- listFileCompletions recorder pathInfo+ let scored = Fuzzy.simpleFilter+ Fuzzy.defChunkSize+ Fuzzy.defMaxResults+ (pathSegment pathInfo)+ (map T.pack completions)+ forM+ scored+ ( \compl' -> do+ let compl = Fuzzy.original compl'+ fullFilePath <- mkFilePathCompletion pathInfo compl+ pure $ mkCompletionItem (completionRange prefInfo) fullFilePath fullFilePath+ )+ )+ Nothing -> do+ logWith recorder Debug LogUseWithStaleFastNoResult+ pure []+ where+ sName = stanzaName cData+ prefInfo = cabalPrefixInfo cData++ -- | Completer to be used when a directory can be completed for the field. -- Only completes directories. directoryCompleter :: Completer directoryCompleter recorder cData = do let prefInfo = cabalPrefixInfo cData- complInfo = pathCompletionInfoFromCabalPrefixInfo prefInfo+ complInfo = pathCompletionInfoFromCabalPrefixInfo "" prefInfo directoryCompletions <- listDirectoryCompletions recorder complInfo let scored = Fuzzy.simpleFilter@@ -73,33 +107,6 @@ be used for file path completions to be written to the cabal file. -} --- | Information used to query and build path completions.------ Note that pathSegment combined with queryDirectory results in--- the original prefix.------ Example:--- When given the written prefix, @dir1\/dir2\/fi@, the--- resulting PathCompletionInfo would be:------ @--- pathSegment = "fi"--- queryDirectory = "dir1\/dir2\/fi"--- ...--- @-data PathCompletionInfo = PathCompletionInfo- { -- | partly written segment of the next part of the path- pathSegment :: T.Text,- -- | written part of path, platform dependent- queryDirectory :: FilePath,- -- | directory relative to which relative paths are interpreted, platform dependent- workingDirectory :: FilePath,- -- | Did the completion happen in the context of a string notation,- -- if yes, contains the state of the string notation- isStringNotationPath :: Maybe Apostrophe- }- deriving (Eq, Show)- -- | Takes a PathCompletionInfo and returns the list of files and directories -- in the directory which match the path completion info in posix style. --@@ -125,18 +132,6 @@ listDirectoryCompletions recorder complInfo = do filepaths <- listFileCompletions recorder complInfo filterM (doesDirectoryExist . mkDirFromCWD complInfo) filepaths--pathCompletionInfoFromCabalPrefixInfo :: CabalPrefixInfo -> PathCompletionInfo-pathCompletionInfoFromCabalPrefixInfo ctx =- PathCompletionInfo- { pathSegment = T.pack pathSegment',- queryDirectory = queryDirectory',- workingDirectory = completionWorkingDir ctx,- isStringNotationPath = isStringNotation ctx- }- where- prefix = T.unpack $ completionPrefix ctx- (queryDirectory', pathSegment') = Posix.splitFileName prefix -- | Returns the directory where files and directories can be queried from -- for the passed PathCompletionInfo.
src/Ide/Plugin/Cabal/Completion/Completer/Module.hs view
@@ -6,26 +6,16 @@ import Control.Monad.Extra (concatForM, forM) import Data.List (stripPrefix)-import qualified Data.List as List import Data.Maybe (fromMaybe) import qualified Data.Text as T-import Distribution.PackageDescription (Benchmark (..),- BuildInfo (..),- CondTree (condTreeData),- Executable (..),- GenericPackageDescription (..),- Library (..),- UnqualComponentName,- mkUnqualComponentName,- testBuildInfo)-import Distribution.Utils.Path (getSymbolicPath)+import Distribution.PackageDescription (GenericPackageDescription) import Ide.Logger (Priority (..), Recorder, WithPriority, logWith)-import Ide.Plugin.Cabal.Completion.Completer.FilePath (PathCompletionInfo (..),- listFileCompletions,+import Ide.Plugin.Cabal.Completion.Completer.FilePath (listFileCompletions, mkCompletionDirectory)+import Ide.Plugin.Cabal.Completion.Completer.Paths import Ide.Plugin.Cabal.Completion.Completer.Simple import Ide.Plugin.Cabal.Completion.Completer.Types import Ide.Plugin.Cabal.Completion.Types@@ -53,56 +43,6 @@ sName = stanzaName cData prefInfo = cabalPrefixInfo cData --- | Extracts the source directories of the library stanza.-sourceDirsExtractionLibrary :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]-sourceDirsExtractionLibrary Nothing gpd =- -- we use condLibrary to get the information contained in the library stanza- -- since the library in PackageDescription is not populated by us- case libM of- Just lib -> do- map getSymbolicPath $ hsSourceDirs $ libBuildInfo $ condTreeData lib- Nothing -> []- where- libM = condLibrary gpd-sourceDirsExtractionLibrary name gpd = extractRelativeDirsFromStanza name gpd condSubLibraries libBuildInfo---- | Extracts the source directories of the executable stanza with the given name.-sourceDirsExtractionExecutable :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]-sourceDirsExtractionExecutable name gpd = extractRelativeDirsFromStanza name gpd condExecutables buildInfo---- | Extracts the source directories of the test suite stanza with the given name.-sourceDirsExtractionTestSuite :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]-sourceDirsExtractionTestSuite name gpd = extractRelativeDirsFromStanza name gpd condTestSuites testBuildInfo---- | Extracts the source directories of benchmark stanza with the given name.-sourceDirsExtractionBenchmark :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]-sourceDirsExtractionBenchmark name gpd = extractRelativeDirsFromStanza name gpd condBenchmarks benchmarkBuildInfo---- | Takes a possible stanza name, a GenericPackageDescription,--- a function to access the stanza information we are interested in--- and a function to access the build info from the specific stanza.------ Returns a list of relative source directory paths specified for the extracted stanza.-extractRelativeDirsFromStanza ::- Maybe StanzaName ->- GenericPackageDescription ->- (GenericPackageDescription -> [(UnqualComponentName, CondTree b c a)]) ->- (a -> BuildInfo) ->- [FilePath]-extractRelativeDirsFromStanza Nothing _ _ _ = []-extractRelativeDirsFromStanza (Just name) gpd getStanza getBuildInfo- | Just stanza <- stanzaM = map getSymbolicPath $ hsSourceDirs $ getBuildInfo stanza- | otherwise = []- where- stanzaM = fmap (condTreeData . snd) res- allStanzasM = getStanza gpd- res =- List.find- ( \(n, _) ->- n == mkUnqualComponentName (T.unpack name)- )- allStanzasM- -- | Takes a list of source directories and returns a list of path completions -- relative to any of the passed source directories which fit the passed prefix info. filePathsForExposedModules :: Recorder (WithPriority Log) -> [FilePath] -> CabalPrefixInfo -> IO [T.Text]@@ -111,34 +51,36 @@ srcDirs ( \dir' -> do let dir = FP.normalise dir'- let pInfo =- PathCompletionInfo- { isStringNotationPath = Nothing,- pathSegment = T.pack $ FP.takeFileName prefix,- queryDirectory = FP.addTrailingPathSeparator $ FP.takeDirectory prefix,- workingDirectory = completionWorkingDir prefInfo FP.</> dir- }- completions <- listFileCompletions recorder pInfo- validExposedCompletions <- filterM (isValidExposedModulePath pInfo) completions- let toMatch = pathSegment pInfo- scored = Fuzzy.simpleFilter Fuzzy.defChunkSize Fuzzy.defMaxResults toMatch (map T.pack validExposedCompletions)+ pathInfo = pathCompletionInfoFromCabalPrefixInfo dir modPrefInfo+ completions <- listFileCompletions recorder pathInfo+ validExposedCompletions <- filterM (isValidExposedModulePath pathInfo) completions+ let toMatch = pathSegment pathInfo+ scored = Fuzzy.simpleFilter+ Fuzzy.defChunkSize+ Fuzzy.defMaxResults+ toMatch+ (map T.pack validExposedCompletions) forM scored ( \compl' -> do let compl = Fuzzy.original compl'- fullFilePath <- mkExposedModulePathCompletion pInfo $ T.unpack compl+ fullFilePath <- mkExposedModulePathCompletion pathInfo $ T.unpack compl pure fullFilePath ) ) where prefix =- exposedModulePathToFp $+ T.pack $ exposedModulePathToFp $ completionPrefix prefInfo- -- \| Takes a PathCompletionInfo and a path segment and checks whether+ -- build completion info relative to the source dir,+ -- we overwrite the prefix written in the cabal file with its translation+ -- to filepath syntax, since it is in exposed module syntax+ modPrefInfo = prefInfo{completionPrefix=prefix}++ -- Takes a PathCompletionInfo and a path segment and checks whether -- the path segment can be completed for an exposed module. -- -- This is the case if the segment represents either a directory or a Haskell file.- -- isValidExposedModulePath :: PathCompletionInfo -> FilePath -> IO Bool isValidExposedModulePath pInfo path = do let dir = mkCompletionDirectory pInfo
+ src/Ide/Plugin/Cabal/Completion/Completer/Paths.hs view
@@ -0,0 +1,118 @@+module Ide.Plugin.Cabal.Completion.Completer.Paths where++import qualified Data.List as List+import qualified Data.Text as T+import Distribution.PackageDescription (Benchmark (..),+ BuildInfo (..),+ CondTree (condTreeData),+ Executable (..),+ GenericPackageDescription (..),+ Library (..),+ UnqualComponentName,+ mkUnqualComponentName,+ testBuildInfo)+import Distribution.Utils.Path (getSymbolicPath)+import Ide.Plugin.Cabal.Completion.Types+import qualified System.FilePath as FP+import qualified System.FilePath.Posix as Posix+++{- | Information used to query and build path completions.++ Note that pathSegment combined with queryDirectory results in+ the original prefix.++ Example:+ When given the written prefix, @dir1\/dir2\/fi@, the+ resulting PathCompletionInfo would be:++ @+ pathSegment = "fi"+ queryDirectory = "dir1\/dir2\/fi"+ ...+ @+-}+data PathCompletionInfo = PathCompletionInfo+ { pathSegment :: T.Text,+ -- ^ Partly written segment of the next part of the path.+ queryDirectory :: FilePath,+ -- ^ Written part of path, in posix format.+ workingDirectory :: FilePath,+ -- ^ Directory relative to which relative paths are interpreted, platform dependent.+ isStringNotationPath :: Maybe Apostrophe+ -- ^ Did the completion happen in the context of a string notation,+ -- if yes, contains the state of the string notation.+ }+ deriving (Eq, Show)++{- | Takes an optional source subdirectory and a prefix info+ and creates a path completion info accordingly.++ The source directory represents some subdirectory of the working directory such as a+ path from the field @hs-source-dirs@.++ If the source subdirectory is empty, then the working directory is simply set to+ the currently handled cabal file's directory.+-}+pathCompletionInfoFromCabalPrefixInfo :: FilePath -> CabalPrefixInfo -> PathCompletionInfo+pathCompletionInfoFromCabalPrefixInfo srcDir prefInfo =+ PathCompletionInfo+ { pathSegment = T.pack pathSegment',+ queryDirectory = queryDirectory',+ workingDirectory = completionWorkingDir prefInfo FP.</> srcDir,+ isStringNotationPath = isStringNotation prefInfo+ }+ where+ prefix = T.unpack $ completionPrefix prefInfo+ (queryDirectory', pathSegment') = Posix.splitFileName prefix++-- | Extracts the source directories of the library stanza.+sourceDirsExtractionLibrary :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]+sourceDirsExtractionLibrary Nothing gpd =+ -- we use condLibrary to get the information contained in the library stanza+ -- since the library in PackageDescription is not populated by us+ case libM of+ Just lib -> do+ map getSymbolicPath $ hsSourceDirs $ libBuildInfo $ condTreeData lib+ Nothing -> []+ where+ libM = condLibrary gpd+sourceDirsExtractionLibrary name gpd = extractRelativeDirsFromStanza name gpd condSubLibraries libBuildInfo++-- | Extracts the source directories of the executable stanza with the given name.+sourceDirsExtractionExecutable :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]+sourceDirsExtractionExecutable name gpd = extractRelativeDirsFromStanza name gpd condExecutables buildInfo++-- | Extracts the source directories of the test suite stanza with the given name.+sourceDirsExtractionTestSuite :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]+sourceDirsExtractionTestSuite name gpd = extractRelativeDirsFromStanza name gpd condTestSuites testBuildInfo++-- | Extracts the source directories of benchmark stanza with the given name.+sourceDirsExtractionBenchmark :: Maybe StanzaName -> GenericPackageDescription -> [FilePath]+sourceDirsExtractionBenchmark name gpd = extractRelativeDirsFromStanza name gpd condBenchmarks benchmarkBuildInfo++{- | Takes a possible stanza name, a GenericPackageDescription,+ a function to access the stanza information we are interested in+ and a function to access the build info from the specific stanza.++ Returns a list of relative source directory paths specified for the extracted stanza.+-}+extractRelativeDirsFromStanza ::+ Maybe StanzaName ->+ GenericPackageDescription ->+ (GenericPackageDescription -> [(UnqualComponentName, CondTree b c a)]) ->+ (a -> BuildInfo) ->+ [FilePath]+extractRelativeDirsFromStanza Nothing _ _ _ = []+extractRelativeDirsFromStanza (Just name) gpd getStanza getBuildInfo+ | Just stanza <- stanzaM = map getSymbolicPath $ hsSourceDirs $ getBuildInfo stanza+ | otherwise = []+ where+ stanzaM = fmap (condTreeData . snd) res+ allStanzasM = getStanza gpd+ res =+ List.find+ ( \(n, _) ->+ n == mkUnqualComponentName (T.unpack name)+ )+ allStanzasM
src/Ide/Plugin/Cabal/Completion/Completions.hs view
@@ -49,7 +49,7 @@ contextToCompleter (Stanza s _, None) = case Map.lookup s stanzaKeywordMap of Nothing -> errorNoopCompleter (LogUnknownStanzaNameInContextError s)- Just l -> constantCompleter $ Map.keys l ++ Map.keys stanzaKeywordMap+ Just l -> constantCompleter $ Map.keys l -- if we are in a stanza's keyword's context we can complete possible values of that keyword contextToCompleter (Stanza s _, KeyWord kw) = case Map.lookup s stanzaKeywordMap of
src/Ide/Plugin/Cabal/Completion/Data.hs view
@@ -14,6 +14,7 @@ showCabalSpecVersion) import Ide.Plugin.Cabal.Completion.Completer.FilePath import Ide.Plugin.Cabal.Completion.Completer.Module+import Ide.Plugin.Cabal.Completion.Completer.Paths import Ide.Plugin.Cabal.Completion.Completer.Simple import Ide.Plugin.Cabal.Completion.Completer.Types (Completer) import Ide.Plugin.Cabal.Completion.Types@@ -92,16 +93,16 @@ executableFields :: Map KeyWordName Completer executableFields = Map.fromList- [ ("main-is:", filePathCompleter),+ [ ("main-is:", mainIsCompleter sourceDirsExtractionExecutable), ("scope:", constantCompleter ["public", "private"]),- ("other-modules:", modulesCompleter (sourceDirsExtractionExecutable))+ ("other-modules:", modulesCompleter sourceDirsExtractionExecutable) ] testSuiteFields :: Map KeyWordName Completer testSuiteFields = Map.fromList [ ("type:", constantCompleter ["exitcode-stdio-1.0", "detailed-0.9"]),- ("main-is:", filePathCompleter),+ ("main-is:", mainIsCompleter sourceDirsExtractionTestSuite), ("other-modules:", modulesCompleter sourceDirsExtractionTestSuite) ] @@ -109,8 +110,8 @@ benchmarkFields = Map.fromList [ ("type:", noopCompleter),- ("main-is:", filePathCompleter),- ("other-modules:", modulesCompleter (sourceDirsExtractionBenchmark))+ ("main-is:", mainIsCompleter sourceDirsExtractionBenchmark),+ ("other-modules:", modulesCompleter sourceDirsExtractionBenchmark) ] foreignLibraryFields :: Map KeyWordName Completer
src/Ide/Plugin/Cabal/Completion/Types.hs view
@@ -27,15 +27,15 @@ instance Pretty Log where pretty = \case- LogFileSplitError pos -> "An error occured when trying to separate the lines of the cabal file at position:" <+> viaShow pos+ LogFileSplitError pos -> "An error occured when trying to separate the lines of the cabal file at position:" <+> pretty pos LogUnknownKeyWordInContextError kw -> "Lookup of key word failed for:" <+> viaShow kw LogUnknownStanzaNameInContextError sn -> "Lookup of stanza name failed for:" <+> viaShow sn LogFilePathCompleterIOError fp ioErr ->- "When trying to complete the file path:" <+> viaShow fp <+> "the following unexpected IO error occured" <+> viaShow ioErr+ "When trying to complete the file path:" <+> pretty fp <+> "the following unexpected IO error occured" <+> viaShow ioErr LogUseWithStaleFastNoResult -> "Package description couldn't be read"- LogMapLookUpOfKnownKeyFailed key -> "Lookup of key in map failed even though it should exist" <+> viaShow key+ LogMapLookUpOfKnownKeyFailed key -> "Lookup of key in map failed even though it should exist" <+> pretty key type instance RuleResult ParseCabal = Parse.GenericPackageDescription
test/Completer.hs view
@@ -3,15 +3,19 @@ module Completer where -import Control.Lens ((^.))+import Control.Lens ((^.), (^?))+import Control.Lens.Prism import qualified Data.ByteString as ByteString+import Data.Maybe (mapMaybe) import qualified Data.Text as T import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe) import Ide.Plugin.Cabal.Completion.Completer.FilePath import Ide.Plugin.Cabal.Completion.Completer.Module+import Ide.Plugin.Cabal.Completion.Completer.Paths import Ide.Plugin.Cabal.Completion.Completer.Types (CompleterData (..)) import Ide.Plugin.Cabal.Completion.Completions-import Ide.Plugin.Cabal.Completion.Types+import Ide.Plugin.Cabal.Completion.Types (CabalPrefixInfo (..),+ StanzaName) import Ide.Plugin.Cabal.Parse (GenericPackageDescription) import qualified Language.LSP.Protocol.Lens as L import qualified Language.LSP.VFS as VFS@@ -23,7 +27,8 @@ completerTests = testGroup "Completer Tests"- [ fileCompleterTests,+ [ basicCompleterTests,+ fileCompleterTests, filePathCompletionContextTests, directoryCompleterTests, completionHelperTests,@@ -31,41 +36,59 @@ exposedModuleCompleterTests ] +basicCompleterTests :: TestTree+basicCompleterTests =+ testGroup+ "Basic Completer Tests"+ [ runCabalTestCaseSession "In stanza context - stanza should not be suggested" "" $ do+ doc <- openDoc "completer.cabal" "cabal"+ compls <- getCompletions doc (Position 11 7)+ let complTexts = getTextEditTexts compls+ liftIO $ assertBool "does not suggest library" $ "library" `notElem` complTexts+ liftIO $ assertBool "suggests library keyword" $ "extra-libraries:" `elem` complTexts+ , runCabalTestCaseSession "In top level context - stanza should be suggested" "" $ do+ doc <- openDoc "completer.cabal" "cabal"+ compls <- getCompletions doc (Position 8 2)+ let complTexts = getTextEditTexts compls+ liftIO $ assertBool "suggests benchmark" $ "benchmark" `elem` complTexts+ , runCabalTestCaseSession "Main-is completions should be relative to hs-source-dirs of same stanza" "filepath-completions" $ do+ doc <- openDoc "main-is.cabal" "cabal"+ compls <- getCompletions doc (Position 10 12)+ let complTexts = getTextEditTexts compls+ liftIO $ assertBool "suggests f2" $ "./f2.hs" `elem` complTexts+ liftIO $ assertBool "does not suggest" $ "./Content.hs" `notElem` complTexts+ ]+ where+ getTextEditTexts :: [CompletionItem] -> [T.Text]+ getTextEditTexts compls = mapMaybe (^? L.textEdit . _Just . _L . L.newText) compls+ fileCompleterTests :: TestTree fileCompleterTests = testGroup "File Completer Tests" [ testCase "Current Directory" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "" testDir- completions @?== ["./.hidden", "./Content.hs", "./dir1/", "./dir2/", "./textfile.txt"],+ completions <- completeFilePath "" filePathComplTestDir+ completions @?== ["./.hidden", "./Content.hs", "./dir1/", "./dir2/", "./textfile.txt", "./main-is.cabal"], testCase "Current Directory - alternative writing" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "./" testDir- completions @?== ["./.hidden", "./Content.hs", "./dir1/", "./dir2/", "./textfile.txt"],+ completions <- completeFilePath "./" filePathComplTestDir+ completions @?== ["./.hidden", "./Content.hs", "./dir1/", "./dir2/", "./textfile.txt", "./main-is.cabal"], testCase "Current Directory - hidden file start" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "." testDir- completions @?== ["./Content.hs", "./.hidden", "./textfile.txt"],+ completions <- completeFilePath "." filePathComplTestDir+ completions @?== ["./Content.hs", "./.hidden", "./textfile.txt", "./main-is.cabal"], testCase "Current Directory - incomplete directory path written" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "di" testDir+ completions <- completeFilePath "di" filePathComplTestDir completions @?== ["./dir1/", "./dir2/"], testCase "Current Directory - incomplete filepath written" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "te" testDir+ completions <- completeFilePath "te" filePathComplTestDir completions @?== ["./Content.hs", "./textfile.txt"], testCase "Subdirectory" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "dir1/" testDir+ completions <- completeFilePath "dir1/" filePathComplTestDir completions @?== ["dir1/f1.txt", "dir1/f2.hs"], testCase "Subdirectory - incomplete filepath written" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "dir2/dir3/MA" testDir+ completions <- completeFilePath "dir2/dir3/MA" filePathComplTestDir completions @?== ["dir2/dir3/MARKDOWN.md"], testCase "Nonexistent directory" $ do- testDir <- getFilePathComplTestDir- completions <- completeFilePath "dir2/dir4/" testDir+ completions <- completeFilePath "dir2/dir4/" filePathComplTestDir completions @?== [] ] where@@ -106,7 +129,6 @@ let complContext = getCabalPrefixInfo "" (simplePosPrefixInfo "\"fp.txt\" \"src fp2.txt" 0 13) completionPrefix complContext @?= "src", testCase "Current Directory" $ do- testDir <- getFilePathComplTestDir compls <- listFileCompletions mempty@@ -114,11 +136,10 @@ { isStringNotationPath = Nothing, pathSegment = "", queryDirectory = "",- workingDirectory = testDir+ workingDirectory = filePathComplTestDir }- compls @?== [".hidden", "Content.hs", "dir1/", "dir2/", "textfile.txt"],+ compls @?== [".hidden", "Content.hs", "dir1/", "dir2/", "textfile.txt", "main-is.cabal"], testCase "In directory" $ do- testDir <- getFilePathComplTestDir compls <- listFileCompletions mempty@@ -126,7 +147,7 @@ { isStringNotationPath = Nothing, pathSegment = "", queryDirectory = "dir1/",- workingDirectory = testDir+ workingDirectory = filePathComplTestDir } compls @?== ["f1.txt", "f2.hs"] ]@@ -145,32 +166,25 @@ testGroup "Directory Completer Tests" [ testCase "Current Directory" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "" testDir+ completions <- completeDirectory "" filePathComplTestDir completions @?== ["./dir1/", "./dir2/"], testCase "Current Directory - alternative writing" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "./" testDir+ completions <- completeDirectory "./" filePathComplTestDir completions @?== ["./dir1/", "./dir2/"], testCase "Current Directory - incomplete directory path written" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "di" testDir+ completions <- completeDirectory "di" filePathComplTestDir completions @?== ["./dir1/", "./dir2/"], testCase "Current Directory - incomplete filepath written" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "te" testDir+ completions <- completeDirectory "te" filePathComplTestDir completions @?== [], testCase "Subdirectory - no more directories found" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "dir1/" testDir+ completions <- completeDirectory "dir1/" filePathComplTestDir completions @?== [], testCase "Subdirectory - available subdirectory" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "dir2/" testDir+ completions <- completeDirectory "dir2/" filePathComplTestDir completions @?== ["dir2/dir3/"], testCase "Nonexistent directory" $ do- testDir <- getFilePathComplTestDir- completions <- completeDirectory "dir2/dir4/" testDir+ completions <- completeDirectory "dir2/dir4/" filePathComplTestDir completions @?== [] ] where@@ -241,8 +255,7 @@ where callFilePathsForExposedModules :: [FilePath] -> IO [T.Text] callFilePathsForExposedModules srcDirs = do- cwd <- getExposedTestDir- let prefInfo = simpleCabalPrefixInfoFromFp "" cwd+ let prefInfo = simpleCabalPrefixInfoFromFp "" exposedTestDir filePathsForExposedModules mempty srcDirs prefInfo exposedModuleCompleterTests :: TestTree@@ -277,25 +290,21 @@ CompleterData { cabalPrefixInfo = simpleExposedCabalPrefixInfo pref dir, getLatestGPD = do- testDir <- getTestDir- cabalContents <- ByteString.readFile $ testDir </> "exposed.cabal"+ cabalContents <- ByteString.readFile $ testDataDir </> "exposed.cabal" pure $ parseGenericPackageDescriptionMaybe cabalContents, stanzaName = sName } callModulesCompleter :: Maybe StanzaName -> (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> T.Text -> IO [T.Text] callModulesCompleter sName func prefix = do- cwd <- getTestDir- let cData = simpleCompleterData sName cwd prefix+ let cData = simpleCompleterData sName testDataDir prefix completer <- modulesCompleter func mempty cData pure $ fmap extract completer mkCompleterData :: CabalPrefixInfo -> CompleterData mkCompleterData prefInfo = CompleterData {getLatestGPD = undefined, cabalPrefixInfo = prefInfo, stanzaName = Nothing} -getExposedTestDir :: IO FilePath-getExposedTestDir = do- testDir <- getTestDir- pure $ addTrailingPathSeparator $ testDir </> "src-modules"+exposedTestDir :: FilePath+exposedTestDir = addTrailingPathSeparator $ testDataDir </> "src-modules" simpleExposedCabalPrefixInfo :: T.Text -> FilePath -> CabalPrefixInfo simpleExposedCabalPrefixInfo prefix fp =
test/Context.hs view
@@ -4,17 +4,17 @@ module Context where -import Control.Monad.Trans.Maybe (runMaybeT)-import qualified Data.Text as T-import qualified Data.Text.Utf16.Rope as Rope+import Control.Monad.Trans.Maybe (runMaybeT)+import qualified Data.Text as T+import qualified Data.Text.Utf16.Rope as Rope import Ide.Plugin.Cabal-import Ide.Plugin.Cabal.Completion.Completer.FilePath+import Ide.Plugin.Cabal.Completion.Completer.Paths import Ide.Plugin.Cabal.Completion.Completions-import Ide.Plugin.Cabal.Completion.Types (Context,- FieldContext (KeyWord, None),- StanzaContext (Stanza, TopLevel))+import Ide.Plugin.Cabal.Completion.Types (Context,+ FieldContext (KeyWord, None),+ StanzaContext (Stanza, TopLevel)) import Test.Hls-import Utils as T+import Utils as T cabalPlugin :: PluginTestDescriptor Ide.Plugin.Cabal.Log cabalPlugin = mkPluginTestDescriptor descriptor "cabal context"@@ -32,31 +32,25 @@ testGroup "Completion Info to Completion Context Tests" [ testCase "Current Directory" $ do- testDir <- getTestDir- let complInfo = pathCompletionInfoFromCabalPrefixInfo $ simpleCabalPrefixInfoFromFp "" testDir+ let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "" testDataDir queryDirectory complInfo @?= "./" , testCase "Current Directory - partly written next" $ do- testDir <- getTestDir- let complInfo = pathCompletionInfoFromCabalPrefixInfo $ simpleCabalPrefixInfoFromFp "di" testDir+ let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "di" testDataDir queryDirectory complInfo @?= "./" pathSegment complInfo @?= "di" , testCase "Current Directory - alternative writing" $ do- testDir <- getTestDir- let complInfo = pathCompletionInfoFromCabalPrefixInfo $ simpleCabalPrefixInfoFromFp "./" testDir+ let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "./" testDataDir queryDirectory complInfo @?= "./" , testCase "Subdirectory" $ do- testDir <- getTestDir- let complInfo = pathCompletionInfoFromCabalPrefixInfo $ simpleCabalPrefixInfoFromFp "dir1/" testDir+ let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "dir1/" testDataDir queryDirectory complInfo @?= "dir1/" pathSegment complInfo @?= "" , testCase "Subdirectory - partly written next" $ do- testDir <- getTestDir- let complInfo = pathCompletionInfoFromCabalPrefixInfo $ simpleCabalPrefixInfoFromFp "dir1/d" testDir+ let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "dir1/d" testDataDir queryDirectory complInfo @?= "dir1/" pathSegment complInfo @?= "d" , testCase "Subdirectory - partly written next" $ do- testDir <- getTestDir- let complInfo = pathCompletionInfoFromCabalPrefixInfo $ simpleCabalPrefixInfoFromFp "dir1/dir2/d" testDir+ let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "dir1/dir2/d" testDataDir queryDirectory complInfo @?= "dir1/dir2/" pathSegment complInfo @?= "d" ]@@ -172,6 +166,7 @@ -- ------------------------------------------------------------------------ -- Test Data -- ------------------------------------------------------------------------+ libraryStanzaData :: [T.Text] libraryStanzaData = [ "cabal-version: 3.0"
test/Main.hs view
@@ -17,15 +17,12 @@ import Data.Row import qualified Data.Text as T import qualified Data.Text as Text-import Ide.Plugin.Cabal import Ide.Plugin.Cabal.LicenseSuggest (licenseErrorSuggestion) import qualified Ide.Plugin.Cabal.Parse as Lib import qualified Language.LSP.Protocol.Lens as L import System.FilePath import Test.Hls--cabalPlugin :: PluginTestDescriptor Ide.Plugin.Cabal.Log-cabalPlugin = mkPluginTestDescriptor descriptor "cabal"+import Utils main :: IO () main = do@@ -195,17 +192,3 @@ InR action@CodeAction{_title} <- codeActions guard (_title == "Replace with " <> license) pure action---- --------------------------------------------------------------------------- Runner utils--- --------------------------------------------------------------------------runCabalTestCaseSession :: TestName -> FilePath -> Session () -> TestTree-runCabalTestCaseSession title subdir = testCase title . runCabalSession subdir--runCabalSession :: FilePath -> Session a -> IO a-runCabalSession subdir =- failIfSessionTimeout . runSessionWithServer cabalPlugin (testDataDir </> subdir)--testDataDir :: FilePath-testDataDir = "test" </> "testdata"
test/Utils.hs view
@@ -5,11 +5,14 @@ import Data.List (sort) import qualified Data.Text as T+import Ide.Plugin.Cabal (descriptor)+import qualified Ide.Plugin.Cabal import Ide.Plugin.Cabal.Completion.Types-import System.Directory (getCurrentDirectory) import System.FilePath import Test.Hls +cabalPlugin :: PluginTestDescriptor Ide.Plugin.Cabal.Log+cabalPlugin = mkPluginTestDescriptor descriptor "cabal" simpleCabalPrefixInfoFromPos :: Position -> T.Text -> CabalPrefixInfo simpleCabalPrefixInfoFromPos pos prefix =@@ -33,15 +36,18 @@ , completionFileName = "test" } -getTestDir :: IO FilePath-getTestDir = do- cwd <- getCurrentDirectory- pure $ addTrailingPathSeparator $ cwd </> "test" </> "testdata"+filePathComplTestDir :: FilePath+filePathComplTestDir = addTrailingPathSeparator $ testDataDir </> "filepath-completions" -getFilePathComplTestDir :: IO FilePath-getFilePathComplTestDir = do- testDir <- getTestDir- pure $ addTrailingPathSeparator $ testDir </> "filepath-completions"+runCabalTestCaseSession :: TestName -> FilePath -> Session () -> TestTree+runCabalTestCaseSession title subdir = testCase title . runCabalSession subdir++runCabalSession :: FilePath -> Session a -> IO a+runCabalSession subdir =+ failIfSessionTimeout . runSessionWithServer def cabalPlugin (testDataDir </> subdir)++testDataDir :: FilePath+testDataDir = "test" </> "testdata" -- | list comparison where the order in the list is irrelevant (@?==) :: (HasCallStack, Ord a, Show a) => [a] -> [a] -> Assertion
+ test/testdata/completer.cabal view
@@ -0,0 +1,12 @@+cabal-version: 3.4+name: test-hls+version: 0.1.0.0+maintainer: milky+synopsis: example cabal file :)+license: Apache-2.0+build-type: Simple++be++library+ lib