Skip to content

Commit 8f1a59c

Browse files
July541pepeiborra
andauthored
Fix Completion document format (#2848)
* Fix doc display * Fix doc display * Add tests * Fix broken tests * Remove extra docs * Rerun tests * Add spanDocToMarkdown doc * Fix tests * Adjust test for broken target * Fix test * Fix broken tests * Unify tests * Update spanDocToMarkdown doc Co-authored-by: Pepe Iborra <[email protected]>
1 parent b5a37f7 commit 8f1a59c

File tree

3 files changed

+171
-28
lines changed

3 files changed

+171
-28
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ mkCompl
204204
where kind = Just compKind
205205
docs' = imported : spanDocToMarkdown docs
206206
imported = case provenance of
207-
Local pos -> "*Defined at " <> pprLineCol (srcSpanStart pos) <> " in this module*\n'"
207+
Local pos -> "*Defined at " <> pprLineCol (srcSpanStart pos) <> " in this module*\n"
208208
ImportedFrom mod -> "*Imported from '" <> mod <> "'*\n"
209209
DefinedIn mod -> "*Defined in '" <> mod <> "'*\n"
210210
colon = if optNewColonConvention then ": " else ":: "

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ safeTyThingId _ = Nothing
6262
-- Possible documentation for an element in the code
6363
data SpanDoc
6464
= SpanDocString HsDocString SpanDocUris
65+
-- ^ Extern module doc
6566
| SpanDocText [T.Text] SpanDocUris
67+
-- ^ Local module doc
6668
deriving stock (Eq, Show, Generic)
6769
deriving anyclass NFData
6870

@@ -76,13 +78,33 @@ data SpanDocUris =
7678
emptySpanDoc :: SpanDoc
7779
emptySpanDoc = SpanDocText [] (SpanDocUris Nothing Nothing)
7880

81+
-- | Convert `SpanDoc` to Markdown format.
82+
--
83+
-- Return a list `Text` includes haddock, document uri and source code uri,
84+
-- each item can be empty and must end with '\\n' if exist. This is to prevent
85+
-- subsequent render problem caused by the missing newline.
86+
--
87+
-- Example:
88+
--
89+
-- For return value ["xxxx","yyyy"], if we concat the list with inserting
90+
-- a separate line(note by "---\n"),
91+
-- it will result "xxxx---\nyyyy" and can't be rendered as a normal doc.
92+
-- Therefore we check every item in the value to make sure they all end with '\\n',
93+
-- this makes "xxxx\n---\nyyy\n" and can be rendered correctly.
7994
spanDocToMarkdown :: SpanDoc -> [T.Text]
80-
spanDocToMarkdown (SpanDocString docs uris)
81-
= [T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs]
82-
<> ["\n"] <> spanDocUrisToMarkdown uris
83-
-- Append the extra newlines since this is markdown --- to get a visible newline,
84-
-- you need to have two newlines
85-
spanDocToMarkdown (SpanDocText txt uris) = txt <> ["\n"] <> spanDocUrisToMarkdown uris
95+
spanDocToMarkdown = \case
96+
(SpanDocString docs uris) ->
97+
let doc = T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs
98+
in go [doc] uris
99+
(SpanDocText txt uris) -> go txt uris
100+
where
101+
go [] uris = render <$> spanDocUrisToMarkdown uris
102+
go txt uris = init txt <> [render (last txt)] <> (render <$> spanDocUrisToMarkdown uris)
103+
-- If the doc is not end with an '\n', we append it.
104+
render txt
105+
| T.null txt = txt
106+
| T.last txt == '\n' = txt
107+
| otherwise = txt <> T.pack "\n"
86108

87109
spanDocUrisToMarkdown :: SpanDocUris -> [T.Text]
88110
spanDocUrisToMarkdown (SpanDocUris mdoc msrc) = catMaybes

ghcide/test/exe/Main.hs

Lines changed: 142 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ import Ide.PluginUtils (pluginDescToIdePlugin
124124
import Ide.Types
125125
import qualified Language.LSP.Types as LSP
126126
import qualified Language.LSP.Types.Lens as L
127+
import Language.LSP.Types.Lens (workspace, didChangeWatchedFiles)
127128
import qualified Progress
128129
import System.Time.Extra
129130
import Test.Tasty
@@ -133,7 +134,6 @@ import Test.Tasty.Ingredients.Rerun
133134
import Test.Tasty.QuickCheck
134135
import Text.Printf (printf)
135136
import Text.Regex.TDFA ((=~))
136-
import Language.LSP.Types.Lens (workspace, didChangeWatchedFiles)
137137

138138
data Log
139139
= LogGhcIde Ghcide.Log
@@ -4615,6 +4615,7 @@ completionTests
46154615
, testGroup "package" packageCompletionTests
46164616
, testGroup "project" projectCompletionTests
46174617
, testGroup "other" otherCompletionTests
4618+
, testGroup "doc" completionDocTests
46184619
]
46194620

46204621
completionTest :: String -> [T.Text] -> Position -> [(T.Text, CompletionItemKind, T.Text, Bool, Bool, Maybe (List TextEdit))] -> TestTree
@@ -5067,7 +5068,7 @@ packageCompletionTests =
50675068
_ <- waitForDiagnostics
50685069
compls <- getCompletions doc (Position 2 12)
50695070
let compls' =
5070-
[T.drop 1 $ T.dropEnd 10 d
5071+
[T.drop 1 $ T.dropEnd 3 d
50715072
| CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown d)), _label}
50725073
<- compls
50735074
, _label == "fromList"
@@ -5087,7 +5088,7 @@ packageCompletionTests =
50875088
_ <- waitForDiagnostics
50885089
compls <- getCompletions doc (Position 2 7)
50895090
let compls' =
5090-
[T.drop 1 $ T.dropEnd 10 d
5091+
[T.drop 1 $ T.dropEnd 3 d
50915092
| CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown d)), _label}
50925093
<- compls
50935094
, _label == "Map"
@@ -5171,7 +5172,7 @@ projectCompletionTests =
51715172
]
51725173
compls <- getCompletions doc (Position 1 10)
51735174
let compls' =
5174-
[T.drop 1 $ T.dropEnd 10 d
5175+
[T.drop 1 $ T.dropEnd 3 d
51755176
| CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown d)), _label}
51765177
<- compls
51775178
, _label == "anidentifier"
@@ -5230,6 +5231,97 @@ projectCompletionTests =
52305231
item ^. L.label @?= "anidentifier"
52315232
]
52325233

5234+
completionDocTests :: [TestTree]
5235+
completionDocTests =
5236+
[ testSession "local define" $ do
5237+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5238+
[ "module A where"
5239+
, "foo = ()"
5240+
, "bar = fo"
5241+
]
5242+
let expected = "*Defined at line 2, column 1 in this module*\n"
5243+
test doc (Position 2 8) "foo" Nothing [expected]
5244+
, testSession "local empty doc" $ do
5245+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5246+
[ "module A where"
5247+
, "foo = ()"
5248+
, "bar = fo"
5249+
]
5250+
test doc (Position 2 8) "foo" Nothing ["*Defined at line 2, column 1 in this module*\n"]
5251+
, brokenForGhc9 $ testSession "local single line doc without '\\n'" $ do
5252+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5253+
[ "module A where"
5254+
, "-- |docdoc"
5255+
, "foo = ()"
5256+
, "bar = fo"
5257+
]
5258+
test doc (Position 3 8) "foo" Nothing ["*Defined at line 3, column 1 in this module*\n* * *\ndocdoc\n"]
5259+
, brokenForGhc9 $ testSession "local multi line doc with '\\n'" $ do
5260+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5261+
[ "module A where"
5262+
, "-- | abcabc"
5263+
, "--"
5264+
, "foo = ()"
5265+
, "bar = fo"
5266+
]
5267+
test doc (Position 4 8) "foo" Nothing ["*Defined at line 4, column 1 in this module*\n* * *\n abcabc\n"]
5268+
, brokenForGhc9 $ testSession "local multi line doc without '\\n'" $ do
5269+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5270+
[ "module A where"
5271+
, "-- | abcabc"
5272+
, "--"
5273+
, "--def"
5274+
, "foo = ()"
5275+
, "bar = fo"
5276+
]
5277+
test doc (Position 5 8) "foo" Nothing ["*Defined at line 5, column 1 in this module*\n* * *\n abcabc\n\ndef\n"]
5278+
, testSession "extern empty doc" $ do
5279+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5280+
[ "module A where"
5281+
, "foo = od"
5282+
]
5283+
let expected = "*Imported from 'Prelude'*\n"
5284+
test doc (Position 1 8) "odd" (Just $ T.length expected) [expected]
5285+
, brokenForMacGhc9 $ brokenForWinGhc9 $ testSession "extern single line doc without '\\n'" $ do
5286+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5287+
[ "module A where"
5288+
, "foo = no"
5289+
]
5290+
let expected = "*Imported from 'Prelude'*\n* * *\n\n\nBoolean \"not\"\n"
5291+
test doc (Position 1 8) "not" (Just $ T.length expected) [expected]
5292+
, brokenForMacGhc9 $ brokenForWinGhc9 $ testSession "extern mulit line doc" $ do
5293+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5294+
[ "module A where"
5295+
, "foo = i"
5296+
]
5297+
let expected = "*Imported from 'Prelude'*\n* * *\n\n\nIdentity function. \n```haskell\nid x = x\n```\n"
5298+
test doc (Position 1 7) "id" (Just $ T.length expected) [expected]
5299+
, testSession "extern defined doc" $ do
5300+
doc <- createDoc "A.hs" "haskell" $ T.unlines
5301+
[ "module A where"
5302+
, "foo = i"
5303+
]
5304+
let expected = "*Imported from 'Prelude'*\n"
5305+
test doc (Position 1 7) "id" (Just $ T.length expected) [expected]
5306+
]
5307+
where
5308+
brokenForGhc9 = knownBrokenFor (BrokenForGHC [GHC90, GHC92]) "Completion doc doesn't support ghc9"
5309+
brokenForWinGhc9 = knownBrokenFor (BrokenSpecific Windows [GHC90, GHC92]) "Extern doc doesn't support Windows for ghc9.2"
5310+
-- https://gitlab.haskell.org/ghc/ghc/-/issues/20903
5311+
brokenForMacGhc9 = knownBrokenFor (BrokenSpecific MacOS [GHC90, GHC92]) "Extern doc doesn't support MacOS for ghc9"
5312+
test doc pos label mn expected = do
5313+
_ <- waitForDiagnostics
5314+
compls <- getCompletions doc pos
5315+
let compls' = [
5316+
-- We ignore doc uris since it points to the local path which determined by specific machines
5317+
case mn of
5318+
Nothing -> txt
5319+
Just n -> T.take n txt
5320+
| CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown txt)), ..} <- compls
5321+
, _label == label
5322+
]
5323+
liftIO $ compls' @?= expected
5324+
52335325
highlightTests :: TestTree
52345326
highlightTests = testGroup "highlight"
52355327
[ testSessionWait "value" $ do
@@ -5483,32 +5575,61 @@ xfail :: TestTree -> String -> TestTree
54835575
xfail = flip expectFailBecause
54845576

54855577
ignoreInWindowsBecause :: String -> TestTree -> TestTree
5486-
ignoreInWindowsBecause
5487-
| isWindows = ignoreTestBecause
5488-
| otherwise = \_ x -> x
5578+
ignoreInWindowsBecause = ignoreFor (BrokenForOS Windows)
54895579

54905580
ignoreInWindowsForGHC88And810 :: TestTree -> TestTree
5491-
ignoreInWindowsForGHC88And810
5492-
| ghcVersion `elem` [GHC88, GHC810] =
5493-
ignoreInWindowsBecause "tests are unreliable in windows for ghc 8.8 and 8.10"
5494-
| otherwise = id
5581+
ignoreInWindowsForGHC88And810 =
5582+
ignoreFor (BrokenSpecific Windows [GHC88, GHC810]) "tests are unreliable in windows for ghc 8.8 and 8.10"
54955583

54965584
ignoreForGHC92 :: String -> TestTree -> TestTree
5497-
ignoreForGHC92 msg
5498-
| ghcVersion == GHC92 = ignoreTestBecause msg
5499-
| otherwise = id
5585+
ignoreForGHC92 = ignoreFor (BrokenForGHC [GHC92])
55005586

55015587
ignoreInWindowsForGHC88 :: TestTree -> TestTree
5502-
ignoreInWindowsForGHC88
5503-
| ghcVersion == GHC88 =
5504-
ignoreInWindowsBecause "tests are unreliable in windows for ghc 8.8"
5505-
| otherwise = id
5588+
ignoreInWindowsForGHC88 =
5589+
ignoreFor (BrokenSpecific Windows [GHC88]) "tests are unreliable in windows for ghc 8.8"
55065590

55075591
knownBrokenForGhcVersions :: [GhcVersion] -> String -> TestTree -> TestTree
5508-
knownBrokenForGhcVersions ghcVers
5509-
| ghcVersion `elem` ghcVers = expectFailBecause
5510-
| otherwise = \_ x -> x
5592+
knownBrokenForGhcVersions ghcVers = knownBrokenFor (BrokenForGHC ghcVers)
5593+
5594+
data BrokenOS = Linux | MacOS | Windows deriving (Show)
5595+
5596+
data IssueSolution = Broken | Ignore deriving (Show)
5597+
5598+
data BrokenTarget =
5599+
BrokenSpecific BrokenOS [GhcVersion]
5600+
-- ^Broken for `BrokenOS` with `GhcVersion`
5601+
| BrokenForOS BrokenOS
5602+
-- ^Broken for `BrokenOS`
5603+
| BrokenForGHC [GhcVersion]
5604+
-- ^Broken for `GhcVersion`
5605+
deriving (Show)
5606+
5607+
-- | Ignore test for specific os and ghc with reason.
5608+
ignoreFor :: BrokenTarget -> String -> TestTree -> TestTree
5609+
ignoreFor = knownIssueFor Ignore
5610+
5611+
-- | Known broken for specific os and ghc with reason.
5612+
knownBrokenFor :: BrokenTarget -> String -> TestTree -> TestTree
5613+
knownBrokenFor = knownIssueFor Broken
5614+
5615+
-- | Deal with `IssueSolution` for specific OS and GHC.
5616+
knownIssueFor :: IssueSolution -> BrokenTarget -> String -> TestTree -> TestTree
5617+
knownIssueFor solution = go . \case
5618+
BrokenSpecific bos vers -> isTargetOS bos && isTargetGhc vers
5619+
BrokenForOS bos -> isTargetOS bos
5620+
BrokenForGHC vers -> isTargetGhc vers
5621+
where
5622+
isTargetOS = \case
5623+
Windows -> isWindows
5624+
MacOS -> isMac
5625+
Linux -> not isWindows && not isMac
5626+
5627+
isTargetGhc = elem ghcVersion
55115628

5629+
go True = case solution of
5630+
Broken -> expectFailBecause
5631+
Ignore -> ignoreTestBecause
5632+
go False = \_ -> id
55125633

55135634
data Expect
55145635
= ExpectRange Range -- Both gotoDef and hover should report this range

0 commit comments

Comments
 (0)