Skip to content

Commit 8a3a5e4

Browse files
committed
Use Hackage for Documentation and Source links
controlled by `linkToHackage` configuration field
1 parent ff7a3c3 commit 8a3a5e4

File tree

7 files changed

+55
-18
lines changed

7 files changed

+55
-18
lines changed

ghcide/src/Development/IDE/Core/Rules.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,8 +570,8 @@ getDocMapRule recorder =
570570
(tmrTypechecked -> tc, _) <- useWithStale_ TypeCheck file
571571
(hscEnv -> hsc, _) <- useWithStale_ GhcSessionDeps file
572572
(HAR{refMap=rf}, _) <- useWithStale_ GetHieAst file
573-
574-
dkMap <- liftIO $ mkDocMap hsc rf tc
573+
linkToHackage <- optLinkToHackage <$> getIdeOptions
574+
dkMap <- liftIO $ mkDocMap hsc rf tc linkToHackage
575575
return ([],Just dkMap)
576576

577577
-- | Persistent rule to ensure that hover doesn't block on startup

ghcide/src/Development/IDE/Core/Shake.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,8 @@ getIdeOptions = do
441441
Just env -> do
442442
config <- liftIO $ LSP.runLspT env HLS.getClientConfig
443443
return x{optCheckProject = pure $ checkProject config,
444-
optCheckParents = pure $ checkParents config
444+
optCheckParents = pure $ checkParents config,
445+
optLinkToHackage = linkToHackage config
445446
}
446447

447448
getIdeOptionsIO :: ShakeExtras -> IO IdeOptions

ghcide/src/Development/IDE/Plugin/Completions.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import Development.IDE.Core.Rules (usePropertyAction)
5656

5757
import qualified Ide.Plugin.Config as Config
5858

59+
import Development.IDE.Types.Options (IdeOptions (optLinkToHackage))
5960
import qualified GHC.LanguageExtensions as LangExt
6061

6162
data Log = LogShake Shake.Log deriving Show
@@ -136,7 +137,9 @@ resolveCompletion ide _pid comp@CompletionItem{_detail,_documentation,_data_} ur
136137
Nothing -> (mempty, mempty)
137138
doc <- case lookupNameEnv dm name of
138139
Just doc -> pure $ spanDocToMarkdown doc
139-
Nothing -> liftIO $ spanDocToMarkdown . fst <$> getDocumentationTryGhc (hscEnv sess) name
140+
Nothing -> liftIO $ do
141+
lc <- optLinkToHackage <$> getIdeOptionsIO (shakeExtras ide)
142+
spanDocToMarkdown . fst <$> getDocumentationTryGhc (hscEnv sess) lc name
140143
typ <- case lookupNameEnv km name of
141144
_ | not needType -> pure Nothing
142145
Just ty -> pure (safeTyThingType ty)

ghcide/src/Development/IDE/Spans/Documentation.hs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import qualified Data.Map as M
2222
import Data.Maybe
2323
import qualified Data.Set as S
2424
import qualified Data.Text as T
25+
import Data.Version (showVersion)
2526
import Development.IDE.Core.Compile
2627
import Development.IDE.Core.RuleTypes
2728
import Development.IDE.GHC.Compat
@@ -30,7 +31,9 @@ import Development.IDE.GHC.Error
3031
import Development.IDE.GHC.Util (printOutputable)
3132
import Development.IDE.Spans.Common
3233
import GHC.Iface.Ext.Utils (RefMap)
33-
import Language.LSP.Protocol.Types (filePathToUri, getUri)
34+
import GHC.Plugins (GenericUnitInfo (unitPackageName))
35+
import Language.LSP.Protocol.Types (Uri (..), filePathToUri,
36+
getUri)
3437
import Prelude hiding (mod)
3538
import System.Directory
3639
import System.FilePath
@@ -40,8 +43,9 @@ mkDocMap
4043
:: HscEnv
4144
-> RefMap a
4245
-> TcGblEnv
46+
-> Bool
4347
-> IO DocAndTyThingMap
44-
mkDocMap env rm this_mod =
48+
mkDocMap env rm this_mod linkToHackage =
4549
do
4650
(Just Docs{docs_decls = UniqMap this_docs, docs_args = UniqMap this_arg_docs}) <- extractDocs (hsc_dflags env) this_mod
4751
d <- foldrM getDocs (fmap (\(_, x) -> (map hsDocString x) `SpanDocString` SpanDocUris Nothing Nothing) this_docs) names
@@ -52,7 +56,7 @@ mkDocMap env rm this_mod =
5256
getDocs n nameMap
5357
| maybe True (mod ==) $ nameModule_maybe n = pure nameMap -- we already have the docs in this_docs, or they do not exist
5458
| otherwise = do
55-
(doc, _argDoc) <- getDocumentationTryGhc env n
59+
(doc, _argDoc) <- getDocumentationTryGhc env linkToHackage n
5660
pure $ extendNameEnv nameMap n doc
5761
getType n nameMap
5862
| Nothing <- lookupNameEnv nameMap n
@@ -62,7 +66,7 @@ mkDocMap env rm this_mod =
6266
getArgDocs n nameMap
6367
| maybe True (mod ==) $ nameModule_maybe n = pure nameMap
6468
| otherwise = do
65-
(_doc, argDoc) <- getDocumentationTryGhc env n
69+
(_doc, argDoc) <- getDocumentationTryGhc env linkToHackage n
6670
pure $ extendNameEnv nameMap n argDoc
6771
names = rights $ S.toList idents
6872
idents = M.keysSet rm
@@ -72,13 +76,13 @@ lookupKind :: HscEnv -> Name -> IO (Maybe TyThing)
7276
lookupKind env =
7377
fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env
7478

75-
getDocumentationTryGhc :: HscEnv -> Name -> IO (SpanDoc, IntMap SpanDoc)
76-
getDocumentationTryGhc env n =
77-
(fromMaybe (emptySpanDoc, mempty) . listToMaybe <$> getDocumentationsTryGhc env [n])
79+
getDocumentationTryGhc :: HscEnv -> Bool -> Name -> IO (SpanDoc, IntMap SpanDoc)
80+
getDocumentationTryGhc env l2h n =
81+
(fromMaybe (emptySpanDoc, mempty) . listToMaybe <$> getDocumentationsTryGhc env l2h [n])
7882
`catch` (\(_ :: IOEnvFailure) -> pure (emptySpanDoc, mempty))
7983

80-
getDocumentationsTryGhc :: HscEnv -> [Name] -> IO [(SpanDoc, IntMap SpanDoc)]
81-
getDocumentationsTryGhc env names = do
84+
getDocumentationsTryGhc :: HscEnv -> Bool -> [Name] -> IO [(SpanDoc, IntMap SpanDoc)]
85+
getDocumentationsTryGhc env linkToHackage names = do
8286
resOr <- catchSrcErrors (hsc_dflags env) "docs" $ getDocsBatch env names
8387
case resOr of
8488
Left _ -> return []
@@ -95,18 +99,37 @@ getDocumentationsTryGhc env names = do
9599
(docFu, srcFu) <-
96100
case nameModule_maybe name of
97101
Just mod -> liftIO $ do
98-
doc <- toFileUriText $ lookupDocHtmlForModule env mod
99-
src <- toFileUriText $ lookupSrcHtmlForModule env mod
100-
return (doc, src)
102+
doc <- lookupDocHtmlForModule env mod
103+
src <- lookupSrcHtmlForModule env mod
104+
-- If found, the local files are used as hints for the hackage links, this helps with symbols defined in an internal module but re-exported by another.
105+
if linkToHackage
106+
then return ( toHackageDocUriText env mod (takeFileName <$> doc)
107+
, toHackageSrcUriText env mod (takeFileName <$> src))
108+
else pure (toFileUriText doc, toFileUriText src)
101109
Nothing -> pure (Nothing, Nothing)
110+
102111
let docUri = (<> "#" <> selector <> printOutputable name) <$> docFu
103112
srcUri = (<> "#" <> printOutputable name) <$> srcFu
104113
selector
105114
| isValName name = "v:"
106115
| otherwise = "t:"
107116
return $ SpanDocUris docUri srcUri
108117

109-
toFileUriText = (fmap . fmap) (getUri . filePathToUri)
118+
toFileUriText = fmap (getUri . filePathToUri)
119+
toHackageUriText subdir sep env mod hint = do
120+
ui <- lookupUnit env (moduleUnit mod)
121+
let htmlFile = case hint of
122+
Nothing -> T.intercalate sep (map T.pack $ moduleNameChunks mod) <> ".html"
123+
Just foundFile -> T.replace "-" sep $ T.pack foundFile
124+
pure $!
125+
mconcat $
126+
[ "http://hackage.haskell.org/package/"
127+
, printOutputable (unitPackageName ui), "-", T.pack $ showVersion (unitPackageVersion ui), "/"
128+
, subdir , "/"
129+
, htmlFile
130+
]
131+
toHackageDocUriText mod = toHackageUriText "docs" "-" mod
132+
toHackageSrcUriText mod = toHackageUriText "docs/src" "." mod
110133

111134
getDocumentation
112135
:: HasSrcSpan name
@@ -146,10 +169,13 @@ lookupHtmlForModule mkDocPath hscEnv m = do
146169
-- first Language.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html
147170
-- then Language.LSP.Types.html and Language-Haskell-LSP-Types.html etc.
148171
mns = do
149-
chunks <- (reverse . drop1 . inits . splitOn ".") $ (moduleNameString . moduleName) m
172+
chunks <- (reverse . drop1 . inits) $ moduleNameChunks m
150173
-- The file might use "." or "-" as separator
151174
map (`intercalate` chunks) [".", "-"]
152175

176+
moduleNameChunks :: Module -> [String]
177+
moduleNameChunks m = splitOn "." $ (moduleNameString . moduleName) m
178+
153179
lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath]
154180
lookupHtmls df ui =
155181
-- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path

ghcide/src/Development/IDE/Types/Options.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ data IdeOptions = IdeOptions
8585
-- ^ Experimental feature to re-run only the subset of the Shake graph that has changed
8686
, optVerifyCoreFile :: Bool
8787
-- ^ Verify core files after serialization
88+
, optLinkToHackage :: Bool
89+
-- ^ `Documentation` and `Source` link to Hackage, rather than local docs.
8890
}
8991

9092
data OptHaddockParse = HaddockParse | NoHaddockParse
@@ -138,6 +140,7 @@ defaultIdeOptions session = IdeOptions
138140
,optRunSubset = True
139141
,optVerifyCoreFile = False
140142
,optMaxDirtyAge = 100
143+
,optLinkToHackage = False
141144
}
142145

143146
defaultSkipProgress :: Typeable a => a -> Bool

hls-plugin-api/src/Ide/Plugin/Config.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ parseConfig idePlugins defValue = A.withObject "settings" $ \o ->
4343
<*> o .:? "cabalFormattingProvider" .!= cabalFormattingProvider defValue
4444
<*> o .:? "maxCompletions" .!= maxCompletions defValue
4545
<*> o .:? "sessionLoading" .!= sessionLoading defValue
46+
<*> o .:? "linkToHackage" .!= linkToHackage defValue
4647
<*> A.explicitParseFieldMaybe (parsePlugins idePlugins) o "plugin" .!= plugins defValue
4748

4849
-- | Parse the 'PluginConfig'.

hls-plugin-api/src/Ide/Types.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ data Config =
178178
, cabalFormattingProvider :: !T.Text
179179
, maxCompletions :: !Int
180180
, sessionLoading :: !SessionLoadingPreferenceConfig
181+
, linkToHackage :: !Bool
181182
, plugins :: !(Map.Map PluginId PluginConfig)
182183
} deriving (Show,Eq)
183184

@@ -189,6 +190,7 @@ instance ToJSON Config where
189190
, "cabalFormattingProvider" .= cabalFormattingProvider
190191
, "maxCompletions" .= maxCompletions
191192
, "sessionLoading" .= sessionLoading
193+
, "linkToHackage" .= linkToHackage
192194
, "plugin" .= Map.mapKeysMonotonic (\(PluginId p) -> p) plugins
193195
]
194196

@@ -204,6 +206,7 @@ instance Default Config where
204206
-- this string value needs to kept in sync with the value provided in HlsPlugins
205207
, maxCompletions = 40
206208
, sessionLoading = PreferSingleComponentLoading
209+
, linkToHackage = False
207210
, plugins = mempty
208211
}
209212

0 commit comments

Comments
 (0)