Skip to content

Commit f951657

Browse files
committed
Refactor module structure of cabal completion system
1 parent 17f0ddd commit f951657

File tree

12 files changed

+998
-903
lines changed

12 files changed

+998
-903
lines changed

plugins/hls-cabal-plugin/hls-cabal-plugin.cabal

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@ library
2727
exposed-modules:
2828
Ide.Plugin.Cabal
2929
Ide.Plugin.Cabal.Diagnostics
30-
Ide.Plugin.Cabal.Completions
31-
Ide.Plugin.Cabal.FilepathCompletions
30+
Ide.Plugin.Cabal.Completion.Completions
31+
Ide.Plugin.Cabal.Completion.Types
32+
Ide.Plugin.Cabal.Completion.Data
33+
Ide.Plugin.Cabal.Completion.Completer.FilePath
34+
Ide.Plugin.Cabal.Completion.Completer.Module
35+
Ide.Plugin.Cabal.Completion.Completer.Simple
36+
Ide.Plugin.Cabal.Completion.Completer.Types
37+
Ide.Plugin.Cabal.Completion.Completer.Snippet
3238
Ide.Plugin.Cabal.LicenseSuggest
3339
Ide.Plugin.Cabal.Parse
34-
Ide.Plugin.Cabal.Types
3540

3641

3742
build-depends:

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,33 @@ import Control.Concurrent.Strict
1616
import Control.DeepSeq
1717
import Control.Monad.Extra
1818
import Control.Monad.IO.Class
19-
import Control.Monad.Trans.Maybe (runMaybeT)
20-
import qualified Data.ByteString as BS
19+
import Control.Monad.Trans.Maybe (runMaybeT)
20+
import qualified Data.ByteString as BS
2121
import Data.Hashable
22-
import Data.HashMap.Strict (HashMap)
23-
import qualified Data.HashMap.Strict as HashMap
24-
import qualified Data.List.NonEmpty as NE
25-
import qualified Data.Text.Encoding as Encoding
22+
import Data.HashMap.Strict (HashMap)
23+
import qualified Data.HashMap.Strict as HashMap
24+
import qualified Data.List.NonEmpty as NE
25+
import qualified Data.Text.Encoding as Encoding
2626
import Data.Typeable
27-
import Development.IDE as D
28-
import Development.IDE.Core.Shake (restartShakeSession)
29-
import qualified Development.IDE.Core.Shake as Shake
30-
import Development.IDE.Graph (alwaysRerun)
31-
import Distribution.Compat.Lens ((^.))
27+
import Development.IDE as D
28+
import Development.IDE.Core.Shake (restartShakeSession)
29+
import qualified Development.IDE.Core.Shake as Shake
30+
import Development.IDE.Graph (alwaysRerun)
31+
import Distribution.Compat.Lens ((^.))
3232
import GHC.Generics
33-
import qualified Ide.Plugin.Cabal.Completions as Completions
34-
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
35-
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
36-
import qualified Ide.Plugin.Cabal.Parse as Parse
37-
import qualified Ide.Plugin.Cabal.Types as Types
33+
import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
34+
import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
35+
import qualified Ide.Plugin.Cabal.Completion.Types as Types
36+
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
37+
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
38+
import qualified Ide.Plugin.Cabal.Parse as Parse
3839
import Ide.Types
39-
import qualified Language.LSP.Protocol.Lens as JL
40-
import qualified Language.LSP.Protocol.Message as LSP
40+
import qualified Language.LSP.Protocol.Lens as JL
41+
import qualified Language.LSP.Protocol.Message as LSP
4142
import Language.LSP.Protocol.Types
42-
import Language.LSP.Server (LspM, getVirtualFile)
43-
import qualified Language.LSP.VFS as VFS
43+
import Language.LSP.Server (LspM,
44+
getVirtualFile)
45+
import qualified Language.LSP.VFS as VFS
4446

4547
data Log
4648
= LogModificationTime NormalizedFilePath FileVersion
@@ -292,13 +294,13 @@ completion recorder ide _ complParams = do
292294
Just ctx -> do
293295
logWith recorder Debug $ LogCompletionContext ctx pos
294296
let completer = Completions.contextToCompleter ctx
295-
let completerData = Types.CompleterData
297+
let completerData = CompleterTypes.CompleterData
296298
{ ideState = ide
297299
, cabalPrefixInfo = completionContext
298300
, stanzaName =
299301
case fst ctx of
300302
Types.Stanza _ name -> name
301-
_ -> Nothing
303+
_ -> Nothing
302304
}
303305
completions <- completer completerRecorder completerData
304306
pure completions

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/FilepathCompletions.hs renamed to plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Completer/FilePath.hs

Lines changed: 62 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,69 @@
22
{-# LANGUAGE OverloadedStrings #-}
33
{-# LANGUAGE ScopedTypeVariables #-}
44

5-
module Ide.Plugin.Cabal.FilepathCompletions where
6-
7-
import Control.Exception (evaluate, try)
8-
import Control.Monad (filterM)
9-
import Control.Monad.Extra (concatForM, forM)
10-
import Data.List (stripPrefix)
11-
import Data.Maybe (fromMaybe)
12-
import qualified Data.Text as T
5+
6+
module Ide.Plugin.Cabal.Completion.Completer.FilePath where
7+
8+
import Data.Maybe (fromMaybe)
9+
import qualified Data.Text as T
10+
import Ide.Plugin.Cabal.Completion.Completer.Types
11+
12+
import Control.Exception (evaluate, try)
13+
import Control.Monad (filterM)
14+
import Control.Monad.Extra (forM)
1315
import Development.IDE.Types.Logger
14-
import Ide.Plugin.Cabal.Types
15-
import System.Directory (doesDirectoryExist,
16-
doesFileExist, listDirectory)
17-
import qualified System.FilePath as FP
18-
import System.FilePath (dropExtension)
19-
import qualified System.FilePath.Posix as Posix
20-
import qualified Text.Fuzzy.Parallel as Fuzzy
16+
import Ide.Plugin.Cabal.Completion.Types
17+
import System.Directory (doesDirectoryExist,
18+
doesFileExist,
19+
listDirectory)
20+
import qualified System.FilePath as FP
21+
import qualified System.FilePath.Posix as Posix
22+
import qualified Text.Fuzzy.Parallel as Fuzzy
23+
import Ide.Plugin.Cabal.Completion.Completer.Simple
24+
25+
26+
{- | Completer to be used when a file path can be
27+
completed for a field, takes the file path of the directory to start from.
28+
Completes file paths as well as directories.
29+
-}
30+
filePathCompleter :: Completer
31+
filePathCompleter recorder cData = do
32+
let prefInfo = cabalPrefixInfo cData
33+
suffix = fromMaybe "" $ completionSuffix prefInfo
34+
complInfo = pathCompletionInfoFromCabalPrefixInfo prefInfo
35+
toMatch = fromMaybe (partialFileName complInfo) $ T.stripPrefix "./" $ partialFileName complInfo
36+
filePathCompletions <- listFileCompletions recorder complInfo
37+
let scored = Fuzzy.simpleFilter 1000 10 toMatch (map T.pack filePathCompletions)
38+
forM
39+
scored
40+
( \compl' -> do
41+
let compl = Fuzzy.original compl'
42+
fullFilePath <- mkFilePathCompletion suffix compl complInfo
43+
pure $ mkCompletionItem (completionRange prefInfo) fullFilePath fullFilePath
44+
)
45+
46+
{- | Completer to be used when a directory can be completed for the field,
47+
takes the file path of the directory to start from.
48+
Only completes directories.
49+
-}
50+
directoryCompleter :: Completer
51+
directoryCompleter recorder cData = do
52+
let prefInfo = cabalPrefixInfo cData
53+
complInfo = pathCompletionInfoFromCabalPrefixInfo prefInfo
54+
directoryCompletions <- listDirectoryCompletions recorder complInfo
55+
let scored =
56+
Fuzzy.simpleFilter
57+
1000
58+
10
59+
(partialFileName complInfo)
60+
(map T.pack directoryCompletions)
61+
forM
62+
scored
63+
( \compl' -> do
64+
let compl = Fuzzy.original compl'
65+
let fullDirPath = mkPathCompletion complInfo compl
66+
pure $ mkCompletionItem (completionRange prefInfo) fullDirPath fullDirPath
67+
)
2168

2269
{- Note [Using correct file path separators]
2370
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -97,55 +144,6 @@ pathCompletionInfoFromCabalPrefixInfo ctx =
97144
dirNamePrefix = T.pack $ Posix.takeFileName prefix
98145
dir = completionWorkingDir ctx
99146

100-
{- | Extracts the source dirs from the library stanza in the cabal file using the GPD
101-
and returns a list of path completions relative to any source dir which fit the passed prefix info.
102-
-}
103-
filePathsForExposedModules :: [FilePath] -> Recorder (WithPriority Log) -> CabalPrefixInfo -> IO [T.Text]
104-
filePathsForExposedModules srcDirs recorder prefInfo = do
105-
concatForM
106-
srcDirs
107-
(\dir -> do
108-
let pInfo =
109-
PathCompletionInfo
110-
{ partialFileName = T.pack $ Posix.takeFileName prefix
111-
, partialFileDir = Posix.addTrailingPathSeparator $ Posix.takeDirectory prefix
112-
, workingDir = completionWorkingDir prefInfo FP.</> dir
113-
}
114-
completions <- listFileCompletions recorder pInfo
115-
validExposedCompletions <- filterM (isValidExposedModulePath pInfo) completions
116-
let filePathCompletions = map (fpToExposedModulePath dir) validExposedCompletions
117-
toMatch = fromMaybe (partialFileName pInfo) $ T.stripPrefix "./" $ partialFileName pInfo
118-
scored = Fuzzy.simpleFilter 1000 10 toMatch (map T.pack filePathCompletions)
119-
forM
120-
scored
121-
( \compl' -> do
122-
let compl = Fuzzy.original compl'
123-
fullFilePath <- mkExposedModulePathCompletion compl pInfo
124-
pure fullFilePath
125-
)
126-
)
127-
where
128-
prefix =
129-
exposedModulePathToFp
130-
$ completionPrefix prefInfo
131-
isValidExposedModulePath :: PathCompletionInfo -> FilePath -> IO Bool
132-
isValidExposedModulePath pInfo path = do
133-
let dir = mkCompletionDirectory pInfo
134-
fileExists <- doesFileExist (dir FP.</> path)
135-
pure $ not fileExists || FP.isExtensionOf ".hs" path
136-
137-
{- | Takes a source dir path and a cabal file path and returns the complete source dir
138-
path in exposed module syntax where the separators are '.' and the file ending is removed.
139-
-}
140-
fpToExposedModulePath :: FilePath -> FilePath -> FilePath
141-
fpToExposedModulePath srcDir cabalDir = T.unpack $ T.intercalate "." $ fmap T.pack $ FP.splitDirectories fp
142-
where
143-
fp = fromMaybe cabalDir $ stripPrefix srcDir cabalDir
144-
145-
{- | Takes a path in the exposed module field and translates it to a filepath.
146-
-}
147-
exposedModulePathToFp :: T.Text -> FilePath
148-
exposedModulePathToFp fp = T.unpack $ T.replace "." (T.singleton FP.pathSeparator) fp
149147

150148
{- | Returns the directory, the currently handled cabal file is in.
151149
@@ -191,16 +189,4 @@ mkFilePathCompletion suffix completion complInfo = do
191189
let completedPath = if isFilePath then combinedPath ++ T.unpack suffix else combinedPath
192190
pure $ T.pack completedPath
193191

194-
{- Takes a completed path and a pathCompletionInfo and generates the whole completed
195-
filepath including the already written prefix using the cabal syntax for exposed modules.
196192

197-
i.e. Dir.Dir2.HaskellFile
198-
or Dir.Dir2.
199-
-}
200-
mkExposedModulePathCompletion :: T.Text -> PathCompletionInfo -> IO T.Text
201-
mkExposedModulePathCompletion completion complInfo = do
202-
let combinedPath = T.unpack $ mkPathCompletion complInfo completion
203-
isFilePath <- doesFileExist (workingDir complInfo FP.</> combinedPath)
204-
let completedPath = T.pack $ if isFilePath then dropExtension combinedPath else combinedPath ++ "."
205-
let exposedPath = fromMaybe completedPath $ T.stripPrefix "./" completedPath
206-
pure $ T.pack $ fpToExposedModulePath "" $ T.unpack exposedPath

0 commit comments

Comments
 (0)