Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation for build-depends on hover #4385

Merged
merged 19 commits into from
Sep 8, 2024
Merged
1 change: 1 addition & 0 deletions ghcide/src/Development/IDE/LSP/HoverDefinition.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Development.IDE.LSP.HoverDefinition
( Log(..)
-- * For haskell-language-server
, hover
, foundHover
, gotoDefinition
, gotoTypeDefinition
, documentHighlight
Expand Down
52 changes: 52 additions & 0 deletions plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
import qualified Language.LSP.Protocol.Message as LSP
import Language.LSP.Protocol.Types
import qualified Language.LSP.VFS as VFS
import Distribution.PackageDescription.Configuration (flattenPackageDescription)
import Distribution.Package (Dependency())
import Distribution.PackageDescription (depPkgName, unPackageName, allBuildDepends)
import Development.IDE.LSP.HoverDefinition (foundHover)
import Text.Regex.TDFA
import Debug.Trace

Check warning on line 60 in plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

View workflow job for this annotation

GitHub Actions / test (9.6, macOS-latest, false)

The import of ‘Debug.Trace’ is redundant

Check warning on line 60 in plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

View workflow job for this annotation

GitHub Actions / flags (9.6, ubuntu-latest)

The import of ‘Debug.Trace’ is redundant

Check warning on line 60 in plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

View workflow job for this annotation

GitHub Actions / test (9.6, ubuntu-latest, true)

The import of ‘Debug.Trace’ is redundant

Check warning on line 60 in plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

View workflow job for this annotation

GitHub Actions / test (9.6, windows-latest, true)

The import of ‘Debug.Trace’ is redundant


data Log
= LogModificationTime NormalizedFilePath FileVersion
Expand Down Expand Up @@ -98,6 +105,7 @@
, mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline
, mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
, mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition
, mkPluginHandler LSP.SMethod_TextDocumentHover hover
]
, pluginNotificationHandlers =
mconcat
Expand Down Expand Up @@ -309,6 +317,50 @@
isSectionArgName name (Syntax.Section _ sectionArgName _) = name == CabalFields.onelineSectionArgs sectionArgName
isSectionArgName _ _ = False

-- | CodeActions for hover messages.
--
-- Provides a CodeAction for displaying message on hover.
-- If found that the filtered hover message is a dependency,
-- adds a Documentation link.
fendor marked this conversation as resolved.
Show resolved Hide resolved
hover :: PluginMethodHandler IdeState LSP.Method_TextDocumentHover
hover ide _ msgParam = do
nfp <- getNormalizedFilePathE uri
(cabalFields, _) <- runActionE "cabal.cabal-hover" ide $ useWithStaleE ParseCabalFields nfp
let mCursorText = CabalFields.findTextWord cursor cabalFields
case mCursorText of
Nothing ->
pure $ InR Null
Just cursorText -> do
(gpd, _) <- runActionE "cabal.GPD" ide $ useWithStaleE ParseCabalFile nfp
let depsNames = map dependencyName $ allBuildDepends $ flattenPackageDescription gpd
fendor marked this conversation as resolved.
Show resolved Hide resolved
mText = filterVersion cursorText
case mText of
Nothing -> pure $ foundHover (Nothing, [cursorText])
Just txt ->
if txt `elem` depsNames
then pure $ foundHover (Nothing, [txt <> "\n", documentationText txt])
else pure $ foundHover (Nothing, [txt])
fendor marked this conversation as resolved.
Show resolved Hide resolved
fendor marked this conversation as resolved.
Show resolved Hide resolved
where
cursor = Types.lspPositionToCabalPosition (msgParam ^. JL.position)
uri = msgParam ^. JL.textDocument . JL.uri

dependencyName :: Dependency -> T.Text
dependencyName dep = T.pack $ unPackageName $ depPkgName dep

filterVersion :: T.Text -> Maybe T.Text
filterVersion msg = getMatch (msg =~ regex)
where
regex :: T.Text
regex = "([a-zA-Z0-9-]*[a-zA-Z0-9]).*"
fendor marked this conversation as resolved.
Show resolved Hide resolved

getMatch :: (T.Text, T.Text, T.Text, [T.Text]) -> Maybe T.Text
getMatch (_, _, _, []) = Nothing
getMatch (_, _, _, [dependency]) = Just dependency
getMatch (_, _, _, _) = Nothing -- impossible case

documentationText :: T.Text -> T.Text
documentationText package = "[Documentation](https://hackage.haskell.org/package/" <> package <> ")"

-- ----------------------------------------------------------------
-- Cabal file of Interest rules and global variable
-- ----------------------------------------------------------------
Expand Down
51 changes: 51 additions & 0 deletions plugins/hls-cabal-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ main = do
, outlineTests
, codeActionTests
, gotoDefinitionTests
, hoverTests
]

-- ------------------------------------------------------------------------
Expand Down Expand Up @@ -282,3 +283,53 @@ gotoDefinitionTests = testGroup "Goto Definition"
doc <- openDoc "simple-with-common.cabal" "cabal"
empty <- getDefinitions doc cursorPos
liftIO $ empty @?= (InR $ InR LSP.Null)

-- ----------------------------------------------------------------------------
-- Hover Tests
-- ----------------------------------------------------------------------------

hoverTests :: TestTree
hoverTests = testGroup "Hover"
[ hoverOnDependencyTests
]

hoverOnDependencyTests :: TestTree
hoverOnDependencyTests = testGroup "Hover Dependency"
[ hoverContainsTest "base with separated version" "hover-deps.cabal" (Position 6 25) "[Documentation](https://hackage.haskell.org/package/base)"
, hoverContainsTest "aeson with not separated version " "hover-deps.cabal" (Position 7 25) "[Documentation](https://hackage.haskell.org/package/aeson)"
, hoverContainsTest "lens no version" "hover-deps.cabal" (Position 7 42) "[Documentation](https://hackage.haskell.org/package/lens)"

, hoverNotContainsTest "name has no documentation" "hover-deps.cabal" (Position 1 25) "[Documentation]"
, hoverNotContainsTest "exposed-modules has no documentation" "hover-deps.cabal" (Position 5 25) "[Documentation]"
, hoverNotContainsTest "hs-source-dirs has no documentation" "hover-deps.cabal" (Position 8 25) "[Documentation]"
]
where
hoverContainsTest :: TestName -> FilePath -> Position -> T.Text -> TestTree
hoverContainsTest testName cabalFile pos containedText =
runCabalTestCaseSession testName "hover" $ do
doc <- openDoc cabalFile "cabal"
h <- getHover doc pos
case h of
Nothing -> liftIO $ assertFailure "No hover"
Just (Hover contents _) -> case contents of
InL (MarkupContent _ txt) -> do
liftIO
$ assertBool ("Failed to find `" <> T.unpack containedText <> "` in hover message: " <> T.unpack txt)
$ containedText `T.isInfixOf` txt
_ -> liftIO $ assertFailure "Unexpected content type"
closeDoc doc

hoverNotContainsTest :: TestName -> FilePath -> Position -> T.Text -> TestTree
hoverNotContainsTest testName cabalFile pos containedText =
runCabalTestCaseSession testName "hover" $ do
doc <- openDoc cabalFile "cabal"
h <- getHover doc pos
case h of
Nothing -> liftIO $ assertFailure "No hover"
Just (Hover contents _) -> case contents of
InL (MarkupContent _ txt) -> do
liftIO
$ assertBool ("Found `" <> T.unpack containedText <> "` in hover message: " <> T.unpack txt)
$ not (containedText `T.isInfixOf` txt)
_ -> liftIO $ assertFailure "Unexpected content type"
closeDoc doc
10 changes: 10 additions & 0 deletions plugins/hls-cabal-plugin/test/testdata/hover/hover-deps.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
cabal-version: 3.0
name: hover-deps
version: 0.1.0.0

library
exposed-modules: Module
build-depends: base ^>=4.14.3.0
, aeson==1.0.0.0 , lens
hs-source-dirs: src
default-language: Haskell2010
Loading