@@ -64,14 +64,29 @@ packageDescriptor plId = PluginDescriptor
64
64
}
65
65
66
66
data AddParams = AddParams
67
- { rootDirParam :: FilePath -- ^ The root directory.
68
- , fileParam :: FilePath -- ^ A path to a module inside the
69
- -- library/executable/test-suite you want to
70
- -- add the package to.
71
- , packageParam :: T. Text -- ^ The name of the package to add.
67
+ { rootDirParam :: FilePath -- ^ The root directory.
68
+ , fileParam :: ModulePath -- ^ A path to a module inside the
69
+ -- library/executable/test-suite you want to
70
+ -- add the package to. May be a realtive oir
71
+ -- absolute path, thus, must be normalised.
72
+ , packageParam :: Package -- ^ The name of the package to add.
72
73
}
73
74
deriving (Eq , Show , Read , Generic , ToJSON , FromJSON )
74
75
76
+ -- | FilePath to a cabal package description file.
77
+ type CabalFilePath = FilePath
78
+ -- | FilePath to a package.yaml package description file.
79
+ type PackageYamlFilePath = FilePath
80
+ -- | FilePath to a module within the project.
81
+ -- May be used to establish what component the dependency shall be added to.
82
+ type ModulePath = FilePath
83
+ -- | Name of the Package to add.
84
+ type Package = T. Text
85
+
86
+ -- | Add a package to the project's dependencies.
87
+ -- May fail if no project dependency specification can be found.
88
+ -- Supported are `*.cabal` and `package.yaml` specifications.
89
+ -- Moreover, may fail with an IOException in case of a filesystem problem.
75
90
addCmd :: CommandFunc AddParams J. WorkspaceEdit
76
91
addCmd = CmdSync $ \ (AddParams rootDir modulePath pkg) -> do
77
92
@@ -83,30 +98,49 @@ addCmd = CmdSync $ \(AddParams rootDir modulePath pkg) -> do
83
98
absFp <- liftIO $ canonicalizePath relFp
84
99
let relModulePath = makeRelative (takeDirectory absFp) modulePath
85
100
86
- liftToGhc $ editCabalPackage absFp relModulePath ( T. unpack pkg) fileMap
101
+ liftToGhc $ editCabalPackage absFp relModulePath pkg fileMap
87
102
HpackPackage relFp -> do
88
103
absFp <- liftIO $ canonicalizePath relFp
89
104
let relModulePath = makeRelative (takeDirectory absFp) modulePath
90
105
liftToGhc $ editHpackPackage absFp relModulePath pkg
91
106
NoPackage -> return $ IdeResultFail (IdeError PluginError " No package.yaml or .cabal found" Null )
92
107
93
- data PackageType = CabalPackage FilePath
94
- | HpackPackage FilePath
95
- | NoPackage
96
-
108
+ data PackageType = CabalPackage FilePath -- ^ Location of Cabal File.
109
+ | HpackPackage FilePath -- ^ Location of `package.yaml`
110
+ | NoPackage -- ^ No package format has been found.
111
+
112
+ -- | Find the package type the project with the given root uses.
113
+ -- Might have weird results if there is more than one cabal package specification
114
+ -- in the root directory.
115
+ -- The `package.yaml` is preferred in case both files are present.
116
+ -- May fail with various IOException's, for example if the given
117
+ -- directory does not exist, a Hardware Failure happens or
118
+ -- the permissions deny it.
119
+ -- However, normally this command should succeeed without any exceptions.
97
120
findPackageType :: FilePath -> IO PackageType
98
121
findPackageType rootDir = do
99
122
files <- getDirectoryContents rootDir
123
+ -- Search for all files that have '.cabal' as a file ending,
124
+ -- since that is the format of the cabal file. May be more to one or zero.
100
125
let mCabal = listToMaybe $ filter (isExtensionOf " cabal" ) files
101
126
102
127
mHpack <- findFile [rootDir] " package.yaml"
103
128
104
129
return $ fromMaybe NoPackage $ asum [HpackPackage <$> mHpack, CabalPackage <$> mCabal]
105
130
131
+ -- | Edit a hpack package to add the given package to the package.yaml.
132
+ --
106
133
-- Currently does not preserve format.
107
134
-- Keep an eye out on this other GSOC project!
108
135
-- https://github.com/wisn/format-preserving-yaml
109
- editHpackPackage :: FilePath -> FilePath -> T. Text -> IdeM (IdeResult WorkspaceEdit )
136
+ editHpackPackage :: PackageYamlFilePath -- ^ Path to the package.yaml file
137
+ -- containing the package description.
138
+ -> ModulePath -- ^ Path to the module where the command has
139
+ -- been issued in.
140
+ -- Used to find out what component the
141
+ -- dependency shall be added to.
142
+ -> Package -- ^ Name of the package to add as a dependency.
143
+ -> IdeM (IdeResult WorkspaceEdit )
110
144
editHpackPackage fp modulePath pkgName = do
111
145
contents <- liftIO $ B. readFile fp
112
146
@@ -186,9 +220,16 @@ editHpackPackage fp modulePath pkgName = do
186
220
addDep (Array deps) = Array $ fromList (String pkgName: GHC.Exts. toList deps)
187
221
addDep _ = error " Not an array in addDep"
188
222
189
-
190
- -- | Takes a cabal file and a path to a module in the dependency you want to edit.
191
- editCabalPackage :: FilePath -> FilePath -> String -> (FilePath -> FilePath ) -> IdeM (IdeResult J. WorkspaceEdit )
223
+ -- | Takes a cabal file and a path to a module in the project and a package name to add
224
+ -- to the cabal file. Reverse file map is needed to find the correct file in the project.
225
+ -- May fail with an IOException if the Cabal file is invalid.
226
+ editCabalPackage :: CabalFilePath -- ^ Path to the cabal file to add the dependency to.
227
+ -> ModulePath -- ^ Path to the module that wants to add the package.
228
+ -- Used to find out what component the
229
+ -- dependency shall be added to.
230
+ -> Package -- ^ Name of the package added as a dependency.
231
+ -> (FilePath -> FilePath ) -- ^ Reverse File for computing file diffs.
232
+ -> IdeM (IdeResult J. WorkspaceEdit )
192
233
editCabalPackage file modulePath pkgName fileMap = do
193
234
194
235
package <- liftIO $ readGenericPackageDescription normal file
@@ -204,7 +245,6 @@ editCabalPackage file modulePath pkgName fileMap = do
204
245
205
246
IdeResultOk <$> makeAdditiveDiffResult file newContents fileMap
206
247
207
-
208
248
where
209
249
210
250
applyLens :: L. HasBuildInfo a => Lens' GenericPackageDescription [(b , CondTree v c a )]
@@ -218,7 +258,7 @@ editCabalPackage file modulePath pkgName fileMap = do
218
258
updateTree = mapIfHasModule modulePath (addDep pkgName)
219
259
220
260
221
- mapIfHasModule :: L. HasBuildInfo a => FilePath -> (a -> a ) -> CondTree v c a -> CondTree v c a
261
+ mapIfHasModule :: L. HasBuildInfo a => ModulePath -> (a -> a ) -> CondTree v c a -> CondTree v c a
222
262
mapIfHasModule modFp f = mapTreeData g
223
263
where g x
224
264
| null (sourceDirs x) = f x
@@ -227,18 +267,26 @@ editCabalPackage file modulePath pkgName fileMap = do
227
267
hasThisModule = any (`isPrefixOf` modFp) . sourceDirs
228
268
sourceDirs x = x ^. L. buildInfo . L. hsSourceDirs
229
269
230
- addDep :: L. HasBuildInfo a => String -> a -> a
270
+ -- | Add the given package name to the cabal file.
271
+ -- Package is appended to the dependency list.
272
+ addDep :: L. HasBuildInfo a => Package -> a -> a
231
273
addDep dep x = L. buildInfo . L. targetBuildDepends .~ newDeps $ x
232
274
where oldDeps = x ^. L. buildInfo . L. targetBuildDepends
233
275
-- Add it to the bottom of the dependencies list
234
- newDeps = oldDeps ++ [Dependency (mkPackageName dep) anyVersion]
276
+ -- TODO: we could sort the depencies and then insert it,
277
+ -- or insert it in order iff the list is already sorted.
278
+ newDeps = oldDeps ++ [Dependency (mkPackageName (T. unpack dep)) anyVersion]
235
279
280
+ -- | Provide a code action to add a package to the local package.yaml or cabal file.
281
+ -- Reads from diagnostics the unknown import module path and searches for it on Hoogle.
282
+ -- If found, offer a code action to add the package to the local package description.
236
283
codeActionProvider :: CodeActionProvider
237
284
codeActionProvider plId docId _ context = do
238
285
mRootDir <- getRootPath
239
286
let J. List diags = context ^. J. diagnostics
240
287
pkgs = mapMaybe getAddablePackages diags
241
288
289
+ -- Search for packages that define the given module.
242
290
res <- mapM (bimapM return Hoogle. searchPackages) pkgs
243
291
actions <- catMaybes <$> mapM (uncurry (mkAddPackageAction mRootDir)) (concatPkgs res)
244
292
@@ -247,7 +295,8 @@ codeActionProvider plId docId _ context = do
247
295
where
248
296
concatPkgs = concatMap (\ (d, ts) -> map (d,) ts)
249
297
250
- mkAddPackageAction :: Maybe FilePath -> J. Diagnostic -> T. Text -> IdeM (Maybe J. CodeAction )
298
+ -- | Create the Add Package Action with the given diagnostics and the found package name.
299
+ mkAddPackageAction :: Maybe FilePath -> J. Diagnostic -> Package -> IdeM (Maybe J. CodeAction )
251
300
mkAddPackageAction mRootDir diag pkgName = case (mRootDir, J. uriToFilePath (docId ^. J. uri)) of
252
301
(Just rootDir, Just docFp) -> do
253
302
let title = " Add " <> pkgName <> " as a dependency"
@@ -256,18 +305,20 @@ codeActionProvider plId docId _ context = do
256
305
return $ Just (J. CodeAction title (Just J. CodeActionQuickFix ) (Just (J. List [diag])) Nothing (Just cmd))
257
306
_ -> return Nothing
258
307
259
- getAddablePackages :: J. Diagnostic -> Maybe (J. Diagnostic , T. Text )
308
+ getAddablePackages :: J. Diagnostic -> Maybe (J. Diagnostic , Package )
260
309
getAddablePackages diag@ (J. Diagnostic _ _ _ (Just " ghcmod" ) msg _) = (diag,) <$> extractModuleName msg
261
310
getAddablePackages _ = Nothing
262
311
263
- extractModuleName :: T. Text -> Maybe T. Text
312
+ -- | Extract a module name from an error message.
313
+ extractModuleName :: T. Text -> Maybe Package
264
314
extractModuleName msg
265
315
| T. isPrefixOf " Could not find module " msg = Just $ T. tail $ T. init nameAndQuotes
266
316
| T. isPrefixOf " Could not load module " msg = Just $ T. tail $ T. init nameAndQuotes
267
317
| otherwise = Nothing
268
318
where line = head $ T. lines msg
269
319
nameAndQuotes = T. dropWhileEnd (/= ' ’' ) $ T. dropWhile (/= ' ‘' ) line
270
320
321
+ -- Example error messages
271
322
{- GHC 8.6.2 error message is
272
323
273
324
"Could not load module \8216Data.Text\8217\n" ++
0 commit comments