-
-
Notifications
You must be signed in to change notification settings - Fork 389
/
Copy pathFillHole.hs
109 lines (101 loc) · 4.97 KB
/
FillHole.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
module Development.IDE.Plugin.Plugins.FillHole
( suggestFillHole
) where
import Control.Monad (guard)
import Data.Char
import qualified Data.Text as T
import Development.IDE.GHC.Util (textInRange)
import Development.IDE.Plugin.Plugins.Diagnostic
import Language.LSP.Protocol.Types (Diagnostic (..),
TextEdit (TextEdit))
import Text.Regex.TDFA (MatchResult (..),
(=~))
suggestFillHole :: Maybe T.Text -> Diagnostic -> [(T.Text, TextEdit)]
suggestFillHole contents Diagnostic{_range=_range,..}
| Just holeName <- extractHoleName _message
, (holeFits, refFits) <- processHoleSuggestions (T.lines _message) =
let isInfixHole = textInDiagnosticRange =~ addBackticks holeName :: Bool in
map (proposeHoleFit holeName False isInfixHole) holeFits
++ map (proposeHoleFit holeName True isInfixHole) refFits
| otherwise = []
where
extractHoleName = fmap (headOrThrow "impossible") . flip matchRegexUnifySpaces "Found hole: ([^ ]*)"
textInDiagnosticRange = case contents of
Nothing -> ""
Just text -> textInRange _range text
addBackticks text = "`" <> text <> "`"
addParens text = "(" <> text <> ")"
proposeHoleFit holeName parenthise isInfixHole name =
case T.uncons name of
Nothing -> error "impossible: empty name provided by ghc"
Just (firstChr, _) ->
let isInfixOperator = firstChr == '('
name' = getOperatorNotation isInfixHole isInfixOperator name in
( "replace " <> holeName <> " with " <> name
, TextEdit _range (if parenthise then addParens name' else name')
)
getOperatorNotation True False name = addBackticks name
getOperatorNotation True True name = T.drop 1 (T.dropEnd 1 name)
getOperatorNotation _isInfixHole _isInfixOperator name = name
headOrThrow msg = \case
[] -> error msg
(x:_) -> x
processHoleSuggestions :: [T.Text] -> ([T.Text], [T.Text])
processHoleSuggestions mm = (holeSuggestions, refSuggestions)
{-
• Found hole: _ :: LSP.Handlers
Valid hole fits include def
Valid refinement hole fits include
fromMaybe (_ :: LSP.Handlers) (_ :: Maybe LSP.Handlers)
fromJust (_ :: Maybe LSP.Handlers)
haskell-lsp-types-0.22.0.0:Language.LSP.Types.Window.$sel:_value:ProgressParams (_ :: ProgressParams
LSP.Handlers)
T.foldl (_ :: LSP.Handlers -> Char -> LSP.Handlers)
(_ :: LSP.Handlers)
(_ :: T.Text)
T.foldl' (_ :: LSP.Handlers -> Char -> LSP.Handlers)
(_ :: LSP.Handlers)
(_ :: T.Text)
-}
where
t = id @T.Text
holeSuggestions = do
-- get the text indented under Valid hole fits
validHolesSection <-
getIndentedGroupsBy (=~ t " *Valid (hole fits|substitutions) include") mm
-- the Valid hole fits line can contain a hole fit
holeFitLine <-
mapHead
(mrAfter . (=~ t " *Valid (hole fits|substitutions) include"))
validHolesSection
let holeFit = T.strip $ T.takeWhile (/= ':') holeFitLine
guard $ not $ holeFit =~ t "Some hole fits suppressed"
guard $ not $ T.null holeFit
return holeFit
refSuggestions = do -- @[]
-- get the text indented under Valid refinement hole fits
refinementSection <-
getIndentedGroupsBy (=~ t " *Valid refinement hole fits include") mm
case refinementSection of
[] -> error "GHC provided invalid hole fit options"
(_:refinementSection) -> do
-- get the text for each hole fit
holeFitLines <- getIndentedGroups refinementSection
let holeFit = T.strip $ T.unwords holeFitLines
guard $ not $ holeFit =~ t "Some refinement hole fits suppressed"
return holeFit
mapHead f (a:aa) = f a : aa
mapHead _ [] = []
-- > getIndentedGroups [" H1", " l1", " l2", " H2", " l3"] = [[" H1,", " l1", " l2"], [" H2", " l3"]]
getIndentedGroups :: [T.Text] -> [[T.Text]]
getIndentedGroups [] = []
getIndentedGroups ll@(l:_) = getIndentedGroupsBy ((== indentation l) . indentation) ll
-- |
-- > getIndentedGroupsBy (" H" `isPrefixOf`) [" H1", " l1", " l2", " H2", " l3"] = [[" H1", " l1", " l2"], [" H2", " l3"]]
getIndentedGroupsBy :: (T.Text -> Bool) -> [T.Text] -> [[T.Text]]
getIndentedGroupsBy pred inp = case dropWhile (not.pred) inp of
(l:ll) -> case span (\l' -> indentation l < indentation l') ll of
(indented, rest) -> (l:indented) : getIndentedGroupsBy pred rest
_ -> []
indentation :: T.Text -> Int
indentation = T.length . T.takeWhile isSpace