From 6a0834b9802600c540aa081ce334f05a9d66623f Mon Sep 17 00:00:00 2001 From: Jana Chadt Date: Fri, 16 Jun 2023 11:32:37 +0200 Subject: [PATCH] Refactor and document code --- .../src/Ide/Plugin/Cabal/Completions.hs | 233 ++++++++------- plugins/hls-cabal-plugin/test/Main.hs | 280 +++++++++--------- 2 files changed, 277 insertions(+), 236 deletions(-) diff --git a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completions.hs b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completions.hs index 379568e14cb..facfef737be 100644 --- a/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completions.hs +++ b/plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completions.hs @@ -28,8 +28,9 @@ import qualified System.FilePath as FP import qualified System.FilePath.Posix as Posix import qualified Text.Fuzzy.Parallel as Fuzzy --- | Takes information needed to build possible completion items --- and returns the list of possible completion items +{- | Takes information needed to build possible completion items +and returns the list of possible completion items +-} type Completer = CabalCompletionContext -> IO [CabalCompletionItem] -- | Contains information needed for a completion action @@ -48,15 +49,15 @@ data CabalCompletionItem = CabalCompletionItem and additionally we can be in a context where we have already written a keyword but no value for it yet -} -type Context = (LevelContext, KeyWordContext) +type Context = (NestingContext, KeyWordContext) -- | Context inside a cabal file, used to decide which keywords to suggest -data LevelContext +data NestingContext = -- | Top level context in a cabal file such as 'author' TopLevel | -- | Nested context in a cabal file, such as 'library', -- which has nested keywords, specific to the stanza - Stanza T.Text + Stanza StanzaName deriving (Eq, Show, Read) {- | Keyword context in cabal file @@ -66,22 +67,25 @@ data KeyWordContext = -- | Key word context, where a keyword -- occurs right before the current position, -- with no value associated to it - KeyWord T.Text + KeyWord KeyWordName | -- | Keyword context where no keyword occurs -- right before the current position None deriving (Eq, Show, Read) --- Information about a partly written filepath to complete +type KeyWordName = T.Text +type StanzaName = T.Text + +-- Information about the current completion status data CabalCompletionContext = CabalCompletionContext - { completionPrefix :: T.Text + { completionPrefix :: T.Text -- ^ text prefix to complete - , completionSuffix :: Maybe T.Text + , completionSuffix :: Maybe T.Text -- ^ possible wrapping text, to write after -- the filepath has been completed - , completionRange :: Range + , completionRange :: Range -- ^ range where completion is to be inserted - , completionCabalFilePath :: FilePath + , completionCabalFileDir :: FilePath -- ^ filepath of the handled file } deriving (Eq, Show, Read) @@ -90,11 +94,15 @@ data CabalCompletionContext = CabalCompletionContext -- Public API for Completions -- ---------------------------------------------------------------- +{- | Takes information about the completion status within the file + and finds the correct completer to be applied +-} contextToCompleter :: Context -> Completer -- if we are in the top level of the cabal file and not in a keyword context, -- we can write any top level keywords or a stanza declaration contextToCompleter (TopLevel, None) = - constantCompleter $ Map.keys (cabalVersionKeyword <> cabalKeywords) ++ Map.keys stanzaKeywordMap + constantCompleter $ + Map.keys (cabalVersionKeyword <> cabalKeywords) ++ Map.keys stanzaKeywordMap -- if we are in a keyword context in the top level, -- we look up that keyword in the top level context and can complete its possible values contextToCompleter (TopLevel, KeyWord kw) = @@ -115,7 +123,7 @@ contextToCompleter (Stanza s, KeyWord kw) = Nothing -> noopCompleter Just l -> l -{- | Takes info about the current cursor position, +{- | Takes information about the current cursor position, the handled cabal file and a set of possible keywords and creates completion suggestions that fit the current input from the given list @@ -126,7 +134,7 @@ makeCompletionItems l = map buildCompletion l {- | Takes a position and a list of lines (representing a file) and returns the context of the current position can return Nothing if an error occurs -TODO: first line can only have cabal-version: keyword + TODO: first line can only have cabal-version: keyword -} getContext :: Position -> [T.Text] -> Maybe Context getContext pos ls = @@ -155,7 +163,7 @@ getContext pos ls = and a map of keywords and returns a keyword context if the previously written keyword matches one in the map -} -getKeyWordContext :: Position -> [T.Text] -> Map T.Text a -> Maybe KeyWordContext +getKeyWordContext :: Position -> [T.Text] -> Map KeyWordName a -> Maybe KeyWordContext getKeyWordContext pos ls keywords = do case lastNonEmptyLineM of Nothing -> Just None @@ -166,8 +174,7 @@ getKeyWordContext pos ls keywords = do -- in order to be in a keyword context the cursor needs -- to be indented more than the keyword if cursorIndentation > keywordIndentation - then - -- if the last thing written was a keyword without a value + then -- if the last thing written was a keyword without a value case List.find (`T.isPrefixOf` lastLine) (Map.keys keywords) of Nothing -> Just None Just kw -> Just $ KeyWord kw @@ -180,9 +187,9 @@ getKeyWordContext pos ls keywords = do {- | Parse the given set of lines (starting before current cursor position up to the start of the file) to find the nearest stanza declaration, - if none is found we are in the top level context + if none is found we are in the top level context. -} -currentLevel :: [T.Text] -> LevelContext +currentLevel :: [T.Text] -> NestingContext currentLevel [] = TopLevel currentLevel (cur : xs) | Just s <- stanza = Stanza s @@ -192,7 +199,7 @@ currentLevel (cur : xs) {- | Returns a CabalCompletionItem with the given starting position and text to be inserted, - where the displayed text is the same as the inserted text + where the displayed text is the same as the inserted text. -} makeSimpleCabalCompletionItem :: Range -> T.Text -> CabalCompletionItem makeSimpleCabalCompletionItem r txt = CabalCompletionItem txt Nothing r @@ -202,7 +209,7 @@ makeSimpleCabalCompletionItem r txt = CabalCompletionItem txt Nothing r -} makeCabalCompletionItem :: Range -> T.Text -> T.Text -> CabalCompletionItem makeCabalCompletionItem r insertTxt displayTxt = - CabalCompletionItem insertTxt (Just displayTxt) r + CabalCompletionItem insertTxt (Just displayTxt) r {- | Get all lines before the given cursor position in the given file and reverse their order to traverse backwards starting from the current position @@ -225,7 +232,7 @@ getFilePathCompletionContext dir prefixInfo = { completionPrefix = filepathPrefix , completionSuffix = Just suffix , completionRange = Range completionStart completionEnd - , completionCabalFilePath = dir + , completionCabalFileDir = dir } where completionEnd = VFS.cursorPos prefixInfo @@ -233,8 +240,8 @@ getFilePathCompletionContext dir prefixInfo = Position (_line completionEnd) (_character completionEnd - (fromIntegral $ T.length filepathPrefix)) - filepathPrefix = T.takeWhileEnd (not . (`elem` stopConditionChars)) beforeCursorText (beforeCursorText, afterCursorText) = T.splitAt cursorColumn $ VFS.fullLine prefixInfo + filepathPrefix = T.takeWhileEnd (not . (`elem` stopConditionChars)) beforeCursorText suffix = if apostropheOrSpaceSeparator == '\"' && even (T.count "\"" afterCursorText) then "\"" @@ -251,25 +258,24 @@ getFilePathCompletionContext dir prefixInfo = buildCompletion :: CabalCompletionItem -> J.CompletionItem buildCompletion completionItem = J.CompletionItem - { - Compls._label = toDisplay, - Compls._kind = Just J.CiKeyword, - Compls._tags = Nothing, - Compls._detail = Nothing, - Compls._documentation = Nothing, - Compls._deprecated = Nothing, - Compls._preselect = Nothing, - Compls._sortText = Nothing, - Compls._filterText = Nothing, - Compls._insertText = Nothing, - Compls._insertTextFormat = Nothing, - Compls._insertTextMode = Nothing, - Compls._textEdit = Just $ J.CompletionEditText (J.TextEdit (itemRange completionItem) $ itemInsert completionItem), - Compls._additionalTextEdits = Nothing, - Compls._commitCharacters = Nothing, - Compls._command = Nothing, - Compls._xdata = Nothing - } + { Compls._label = toDisplay + , Compls._kind = Just J.CiKeyword + , Compls._tags = Nothing + , Compls._detail = Nothing + , Compls._documentation = Nothing + , Compls._deprecated = Nothing + , Compls._preselect = Nothing + , Compls._sortText = Nothing + , Compls._filterText = Nothing + , Compls._insertText = Nothing + , Compls._insertTextFormat = Nothing + , Compls._insertTextMode = Nothing + , Compls._textEdit = Just $ J.CompletionEditText (J.TextEdit (itemRange completionItem) $ itemInsert completionItem) + , Compls._additionalTextEdits = Nothing + , Compls._commitCharacters = Nothing + , Compls._command = Nothing + , Compls._xdata = Nothing + } where toDisplay = fromMaybe (itemInsert completionItem) (itemDisplay completionItem) @@ -301,7 +307,7 @@ filePathCompleter ctx = do let suffix = fromMaybe "" $ completionSuffix ctx complInfo = pathCompletionInfoFromCompletionContext ctx filePathCompletions <- listFileCompletions complInfo - let scored = Fuzzy.simpleFilter 1000 10 (prefixLeftOver complInfo) (map T.pack filePathCompletions) + let scored = Fuzzy.simpleFilter 1000 10 (partialFileName complInfo) (map T.pack filePathCompletions) forM scored ( \compl' -> do @@ -309,16 +315,16 @@ filePathCompleter ctx = do fullFilePath <- makeFullFilePath suffix compl complInfo pure $ makeCabalCompletionItem (completionRange ctx) fullFilePath fullFilePath ) - where - -- | Takes a suffix, a completed path and a pathCompletionInfo and - -- generates the whole filepath including the already written prefix - -- and the suffix in case the completed path is a filepath - makeFullFilePath :: T.Text -> T.Text -> PathCompletionInfo -> IO T.Text - makeFullFilePath suffix' completion' complInfo = do - let fullPath' = prefixPathInfo complInfo Posix. T.unpack completion' - isFilePath <- doesFileExist fullPath' - let fullPath = if isFilePath then fullPath' ++ T.unpack suffix' else fullPath' - pure $ T.pack fullPath + where + -- \| Takes a suffix, a completed path and a pathCompletionInfo and + -- generates the whole filepath including the already written prefix + -- and the suffix in case the completed path is a filepath + makeFullFilePath :: T.Text -> T.Text -> PathCompletionInfo -> IO T.Text + makeFullFilePath suffix' completion' complInfo = do + let fullPath' = partialFileDir complInfo Posix. T.unpack completion' + isFilePath <- doesFileExist fullPath' + let fullPath = if isFilePath then fullPath' ++ T.unpack suffix' else fullPath' + pure $ T.pack fullPath {- | Completer to be used when a directory can be completed for the field, takes the file path of the directory to start from, @@ -328,7 +334,7 @@ directoryCompleter :: Completer directoryCompleter ctx = do let complInfo = pathCompletionInfoFromCompletionContext ctx directoryCompletions <- listDirectoryCompletions complInfo - let scored = Fuzzy.simpleFilter 1000 10 (prefixLeftOver complInfo) (map T.pack directoryCompletions) + let scored = Fuzzy.simpleFilter 1000 10 (partialFileName complInfo) (map T.pack directoryCompletions) forM scored ( \compl' -> do @@ -336,13 +342,13 @@ directoryCompleter ctx = do fullDirPath <- makeFullDirPath compl complInfo pure $ makeCabalCompletionItem (completionRange ctx) fullDirPath fullDirPath ) - where - -- | Takes a directory and PathCompletionInfo and - -- returns the whole path including the prefix that was already written - makeFullDirPath :: T.Text -> PathCompletionInfo -> IO T.Text - makeFullDirPath completion' complInfo = do - let fullPath = prefixPathInfo complInfo Posix. T.unpack completion' - pure $ T.pack fullPath + where + -- \| Takes a directory and PathCompletionInfo and + -- returns the whole path including the prefix that was already written + makeFullDirPath :: T.Text -> PathCompletionInfo -> IO T.Text + makeFullDirPath completion' complInfo = do + let fullPath = partialFileDir complInfo Posix. T.unpack completion' + pure $ T.pack fullPath {- | Takes a path completion info and returns the list of files in the directory the path completion info describes @@ -351,14 +357,15 @@ listFileCompletions :: PathCompletionInfo -> IO [FilePath] listFileCompletions complInfo = do try (evaluate =<< listDirectory (mkCompletionDirectory complInfo)) >>= \case Right dirs -> do - fixedDirs <- mapM - (\d -> do - isDir <- doesDirectoryExist $ mkDirFromCWD complInfo d - pure $ if isDir then Posix.addTrailingPathSeparator d else d - ) - dirs + fixedDirs <- + mapM + ( \d -> do + isDir <- doesDirectoryExist $ mkDirFromCWD complInfo d + pure $ if isDir then Posix.addTrailingPathSeparator d else d + ) + dirs pure fixedDirs - Left (_::IOError) -> pure [] + Left (_ :: IOError) -> pure [] {- | Returns a list of all (and only) directories in the directory described by path completion info @@ -369,38 +376,46 @@ listDirectoryCompletions complInfo = do filterM (doesDirectoryExist . mkDirFromCWD complInfo) filepaths pathCompletionInfoFromCompletionContext :: CabalCompletionContext -> PathCompletionInfo -pathCompletionInfoFromCompletionContext ctx = PathCompletionInfo - { prefixLeftOver = dirNamePrefix - , prefixPathInfo = Posix.addTrailingPathSeparator $ Posix.takeDirectory prefix - , cabalFilePathInfo = fp} - where - prefix = T.unpack $ completionPrefix ctx - dirNamePrefix = T.pack $ Posix.takeFileName prefix - fp = Posix.takeDirectory $ completionCabalFilePath ctx - -{- | Returns the directory, the currently handled cabal file is in, - we let System.FilePath handle the separator syntax since this is used - to query filepaths from the system +pathCompletionInfoFromCompletionContext ctx = + PathCompletionInfo + { partialFileName = dirNamePrefix + , partialFileDir = Posix.addTrailingPathSeparator $ Posix.takeDirectory prefix + , cabalFileDir = dir + } + where + prefix = T.unpack $ completionPrefix ctx + dirNamePrefix = T.pack $ Posix.takeFileName prefix + dir = Posix.takeDirectory $ completionCabalFileDir ctx + +{- | Returns the directory, the currently handled cabal file is in. + + We let System.FilePath handle the separator syntax since this is used + to query filepaths from the system. -} mkCompletionDirectory :: PathCompletionInfo -> FilePath mkCompletionDirectory complInfo = - FP.addTrailingPathSeparator - $ cabalFilePathInfo complInfo FP. (FP.normalise $ prefixPathInfo complInfo) + FP.addTrailingPathSeparator $ + cabalFileDir complInfo FP. (FP.normalise $ partialFileDir complInfo) + +{- | Returns the complete filepath for the given filepath. -{- | Returns the complete filepath for the given filepath, - since cabal files only use posix syntax, and this is used for completions - we use posix separators here + Since cabal files only use posix syntax, and this is used for completions + we use posix separators here. -} mkDirFromCWD :: PathCompletionInfo -> FilePath -> FilePath mkDirFromCWD complInfo fp = Posix.addTrailingPathSeparator $ mkCompletionDirectory complInfo Posix. Posix.normalise fp --- | Information used to query and build file path/directory completions +{- | Information used to query and build file path/directory completions. + + Note that partialFileName combined with partialFileDir results in + the original prefix. +-} data PathCompletionInfo = PathCompletionInfo - { prefixLeftOver :: T.Text + { partialFileName :: T.Text -- ^ partly written start of next part of path - , prefixPathInfo :: FilePath + , partialFileDir :: FilePath -- ^ written part of path - , cabalFilePathInfo :: FilePath + , cabalFileDir :: FilePath -- ^ current working directory of the handled file } deriving (Eq, Show, Read) @@ -410,15 +425,17 @@ data PathCompletionInfo = PathCompletionInfo -- ---------------------------------------------------------------- -- | Keyword for cabal version; required to be the top line in a cabal file -cabalVersionKeyword :: Map T.Text Completer +cabalVersionKeyword :: Map KeyWordName Completer cabalVersionKeyword = Map.singleton "cabal-version:" $ constantCompleter $ map (T.pack . showCabalSpecVersion) [CabalSpecV2_2 .. maxBound] --- | Top level keywords of a cabal file --- TODO: we could add descriptions of field values and then show them when inside the field's context -cabalKeywords :: Map T.Text Completer +{- | Top level keywords of a cabal file. + + TODO: we could add descriptions of field values and then show them when inside the field's context +-} +cabalKeywords :: Map KeyWordName Completer cabalKeywords = Map.fromList [ ("name:", noopCompleter) -- TODO: should complete to filename, needs meta info @@ -446,7 +463,7 @@ cabalKeywords = ] -- | Map, containing all stanzas in a cabal file as keys and lists of their possible nested keywords as values -stanzaKeywordMap :: Map T.Text (Map T.Text Completer) +stanzaKeywordMap :: Map StanzaName (Map KeyWordName Completer) stanzaKeywordMap = Map.fromList [ @@ -509,14 +526,26 @@ stanzaKeywordMap = , ( "source-repository" , Map.fromList $ - [ ("type:", constantCompleter - ["darcs", "git", "svn","cvs", "mercurial", "hg", - "bazaar", "bzr", "arch","monotone"]), - ("location:", noopCompleter), - ("module:", noopCompleter), - ("branch:", noopCompleter), - ("tag:", noopCompleter), - ("subdir:", directoryCompleter) + [ + ( "type:" + , constantCompleter + [ "darcs" + , "git" + , "svn" + , "cvs" + , "mercurial" + , "hg" + , "bazaar" + , "bzr" + , "arch" + , "monotone" + ] + ) + , ("location:", noopCompleter) + , ("module:", noopCompleter) + , ("branch:", noopCompleter) + , ("tag:", noopCompleter) + , ("subdir:", directoryCompleter) ] ) ] diff --git a/plugins/hls-cabal-plugin/test/Main.hs b/plugins/hls-cabal-plugin/test/Main.hs index f8148eb4fb2..4539f472b98 100644 --- a/plugins/hls-cabal-plugin/test/Main.hs +++ b/plugins/hls-cabal-plugin/test/Main.hs @@ -131,10 +131,10 @@ filePathCompletionContextTests = let complContext = getFilePathCompletionContext "" (simplePosPrefixInfo " " 0 3) completionSuffix complContext @?= Just "" completionPrefix complContext @?= "" - , testCase "simple filepath" $ do - let complContext = getFilePathCompletionContext "" (simplePosPrefixInfo " src/" 0 7) - completionSuffix complContext @?= Just "" - completionPrefix complContext @?= "src/" + , testCase "simple filepath" $ do + let complContext = getFilePathCompletionContext "" (simplePosPrefixInfo " src/" 0 7) + completionSuffix complContext @?= Just "" + completionPrefix complContext @?= "src/" , testCase "simple filepath - starting apostrophe" $ do let complContext = getFilePathCompletionContext "" (simplePosPrefixInfo " \"src/" 0 8) completionSuffix complContext @?= Just "\"" @@ -171,163 +171,175 @@ filePathCompletionContextTests = } pathCompleterTests :: TestTree -pathCompleterTests = testGroup "Path Completion Tests" - [ fileCompleterTests - , directoryCompleterTests - , pathCompletionInfoFromCompletionContextTests - , testGroup "Helper - List File Completion Tests" - [ testCase "Current Directory" $ do - testDir <- getTestDir - compls <- listFileCompletions - PathCompletionInfo - { prefixLeftOver = "" - , prefixPathInfo = "" - , cabalFilePathInfo = testDir} - sort compls @?= [".hidden", "dir1/", "dir2/", "textfile.txt"], - testCase "In directory" $ do - testDir <- getTestDir - compls <- listFileCompletions $ - PathCompletionInfo - { prefixLeftOver = "" - , prefixPathInfo = "dir1/" - , cabalFilePathInfo = testDir} - sort compls @?= ["f1.txt", "f2.hs"] +pathCompleterTests = + testGroup + "Path Completion Tests" + [ fileCompleterTests + , directoryCompleterTests + , pathCompletionInfoFromCompletionContextTests + , testGroup + "Helper - List File Completion Tests" + [ testCase "Current Directory" $ do + testDir <- getTestDir + compls <- + listFileCompletions + PathCompletionInfo + { partialFileName = "" + , partialFileDir = "" + , cabalFileDir = testDir + } + sort compls @?= [".hidden", "dir1/", "dir2/", "textfile.txt"] + , testCase "In directory" $ do + testDir <- getTestDir + compls <- + listFileCompletions $ + PathCompletionInfo + { partialFileName = "" + , partialFileDir = "dir1/" + , cabalFileDir = testDir + } + sort compls @?= ["f1.txt", "f2.hs"] + ] ] - ] - where - simpleCabalCompletionContext :: T.Text -> FilePath -> CabalCompletionContext - simpleCabalCompletionContext prefix fp = - CabalCompletionContext + where + simpleCabalCompletionContext :: T.Text -> FilePath -> CabalCompletionContext + simpleCabalCompletionContext prefix fp = + CabalCompletionContext { completionPrefix = prefix , completionSuffix = Nothing , completionRange = Range (Position 0 0) (Position 0 0) - , completionCabalFilePath = fp "test.cabal" + , completionCabalFileDir = fp "test.cabal" } - getTestDir :: IO FilePath - getTestDir = do - cwd <- getCurrentDirectory - pure $ cwd "test/testdata/filepath-completions/" - pathCompletionInfoFromCompletionContextTests :: TestTree - pathCompletionInfoFromCompletionContextTests = testGroup "Completion Info to Completion Context Tests" + getTestDir :: IO FilePath + getTestDir = do + cwd <- getCurrentDirectory + pure $ cwd "test/testdata/filepath-completions/" + pathCompletionInfoFromCompletionContextTests :: TestTree + pathCompletionInfoFromCompletionContextTests = + testGroup + "Completion Info to Completion Context Tests" [ testCase "Current Directory" $ do testDir <- getTestDir let complInfo = pathCompletionInfoFromCompletionContext $ simpleCabalCompletionContext "" testDir - prefixPathInfo complInfo @?= "./", - testCase "Current Directory - partly written next" $ do + partialFileDir complInfo @?= "./" + , testCase "Current Directory - partly written next" $ do testDir <- getTestDir let complInfo = pathCompletionInfoFromCompletionContext $ simpleCabalCompletionContext "di" testDir - prefixPathInfo complInfo @?= "./" - prefixLeftOver complInfo @?= "di", - testCase "Current Directory - alternative writing" $ do + partialFileDir complInfo @?= "./" + partialFileName complInfo @?= "di" + , testCase "Current Directory - alternative writing" $ do testDir <- getTestDir let complInfo = pathCompletionInfoFromCompletionContext $ simpleCabalCompletionContext "./" testDir - prefixPathInfo complInfo @?= "./", - testCase "Subdirectory" $ do + partialFileDir complInfo @?= "./" + , testCase "Subdirectory" $ do testDir <- getTestDir let complInfo = pathCompletionInfoFromCompletionContext $ simpleCabalCompletionContext "dir1/" testDir - prefixPathInfo complInfo @?= "dir1/" - prefixLeftOver complInfo @?= "", - testCase "Subdirectory - partly written next" $ do + partialFileDir complInfo @?= "dir1/" + partialFileName complInfo @?= "" + , testCase "Subdirectory - partly written next" $ do testDir <- getTestDir let complInfo = pathCompletionInfoFromCompletionContext $ simpleCabalCompletionContext "dir1/d" testDir - prefixPathInfo complInfo @?= "dir1/" - prefixLeftOver complInfo @?= "d", - testCase "Subdirectory - partly written next" $ do + partialFileDir complInfo @?= "dir1/" + partialFileName complInfo @?= "d" + , testCase "Subdirectory - partly written next" $ do testDir <- getTestDir let complInfo = pathCompletionInfoFromCompletionContext $ simpleCabalCompletionContext "dir1/dir2/d" testDir - prefixPathInfo complInfo @?= "dir1/dir2/" - prefixLeftOver complInfo @?= "d" + partialFileDir complInfo @?= "dir1/dir2/" + partialFileName complInfo @?= "d" ] - directoryCompleterTests :: TestTree - directoryCompleterTests = testGroup "Directory Completer Tests" + directoryCompleterTests :: TestTree + directoryCompleterTests = + testGroup + "Directory Completer Tests" [ testCase "Current Directory" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["dir1/", "dir2/"], - testCase "Current Directory - alternative writing" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "./" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["dir1/", "dir2/"], - testCase "Current Directory - incomplete directory path written" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "di" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["dir1/", "dir2/"], - testCase "Current Directory - incomplete filepath written" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "te" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= [], - testCase "Subdirectory - no more directories found" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "dir1/" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= [], - testCase "Subdirectory - available subdirectory" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "dir2/" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["dir3/"], - testCase "Nonexistent directory" $ do - testDir <- getTestDir - completions <- directoryCompleter $ simpleCabalCompletionContext "dir2/dir4/" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= [] + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["dir1/", "dir2/"] + , testCase "Current Directory - alternative writing" $ do + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "./" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["dir1/", "dir2/"] + , testCase "Current Directory - incomplete directory path written" $ do + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "di" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["dir1/", "dir2/"] + , testCase "Current Directory - incomplete filepath written" $ do + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "te" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= [] + , testCase "Subdirectory - no more directories found" $ do + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "dir1/" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= [] + , testCase "Subdirectory - available subdirectory" $ do + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "dir2/" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["dir3/"] + , testCase "Nonexistent directory" $ do + testDir <- getTestDir + completions <- directoryCompleter $ simpleCabalCompletionContext "dir2/dir4/" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= [] ] - fileCompleterTests :: TestTree - fileCompleterTests = testGroup "File Completer Tests" + fileCompleterTests :: TestTree + fileCompleterTests = + testGroup + "File Completer Tests" [ testCase "Current Directory" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["./.hidden", "./dir1/", "./dir2/", "./textfile.txt"], - testCase "Current Directory - alternative writing" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "./" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["./.hidden", "./dir1/", "./dir2/", "./textfile.txt"], - testCase "Current Directory - hidden file start" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "." testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["./.hidden", "./textfile.txt"], - testCase "Current Directory - incomplete directory path written" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "di" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["./dir1/", "./dir2/"], - testCase "Current Directory - incomplete filepath written" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "te" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["./textfile.txt"], - testCase "Subdirectory" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "dir1/" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["dir1/f1.txt", "dir1/f2.hs"], - testCase "Subdirectory - incomplete filepath written" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "dir2/dir3/MA" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= ["dir2/dir3/MARKDOWN.md"], - testCase "Nonexistent directory" $ do - testDir <- getTestDir - completions <- filePathCompleter $ simpleCabalCompletionContext "dir2/dir4/" testDir - let insertCompletions = map itemInsert completions - sort insertCompletions @?= [] + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["./.hidden", "./dir1/", "./dir2/", "./textfile.txt"] + , testCase "Current Directory - alternative writing" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "./" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["./.hidden", "./dir1/", "./dir2/", "./textfile.txt"] + , testCase "Current Directory - hidden file start" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "." testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["./.hidden", "./textfile.txt"] + , testCase "Current Directory - incomplete directory path written" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "di" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["./dir1/", "./dir2/"] + , testCase "Current Directory - incomplete filepath written" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "te" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["./textfile.txt"] + , testCase "Subdirectory" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "dir1/" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["dir1/f1.txt", "dir1/f2.hs"] + , testCase "Subdirectory - incomplete filepath written" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "dir2/dir3/MA" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= ["dir2/dir3/MARKDOWN.md"] + , testCase "Nonexistent directory" $ do + testDir <- getTestDir + completions <- filePathCompleter $ simpleCabalCompletionContext "dir2/dir4/" testDir + let insertCompletions = map itemInsert completions + sort insertCompletions @?= [] ] - contextTests :: TestTree contextTests = testGroup "Context Tests" [ testCase "Empty File - Start" $ do -- for a completely empty file, the context needs to - -- be toplevel without a specified keyword + -- be top level without a specified keyword getContext (Position 0 0) [""] @?= Just (TopLevel, None) , testCase "Cabal version keyword - no value" $ do -- on a file, where the "cabal-version:" keyword is already written @@ -340,7 +352,7 @@ contextTests = getContext (Position 0 5) ["cabal-version:"] @?= Just (TopLevel, KeyWord "cabal-version:") , testCase "Cabal version keyword - no value, many spaces" $ do -- on a file, where the "cabal-version:" keyword is already written - -- the context should still be toplevel but the keyword should be recognized + -- the context should still be top level but the keyword should be recognized getContext (Position 0 45) ["cabal-version:" <> T.replicate 50 " "] @?= Just (TopLevel, KeyWord "cabal-version:") , testCase "Cabal version keyword - no value, many spaces" $ do -- in the first line of the file, if the keyword @@ -394,7 +406,7 @@ contextTests = getContext (Position 5 2) libraryStanzaData @?= Just (Stanza "library", None) , testCase "Keyword inside stanza - cursor at start of next line" $ do -- in a stanza context with no value the value may not be written in the next line, - -- when the cursor is not indented and we are in the toplevel context + -- when the cursor is not indented and we are in the top level context getContext (Position 5 0) libraryStanzaData @?= Just (TopLevel, None) ] @@ -432,7 +444,7 @@ pluginTests = expectNoMoreDiagnostics 1 hsDoc "typechecking" cabalDoc <- openDoc "simple-cabal.cabal" "cabal" expectNoMoreDiagnostics 1 cabalDoc "parsing" - , ignoreTestBecause "Testcase is flaky for certain GHC versions (e.g. 9.2.5). See #3333 for details." $ do + , ignoreTestBecause "Test case is flaky for certain GHC versions (e.g. 9.2.5). See #3333 for details." $ do runCabalTestCaseSession "Diagnostics in .hs files from invalid .cabal file" "simple-cabal" $ do hsDoc <- openDoc "A.hs" "haskell" expectNoMoreDiagnostics 1 hsDoc "typechecking"