1{-# LANGUAGE CPP        #-}
2{-# LANGUAGE GADTs      #-}
3{-# LANGUAGE MultiWayIf #-}
4
5
6-- Mostly taken from "haskell-ide-engine"
7module Development.IDE.Plugin.Completions.Logic (
8  CachedCompletions
9, cacheDataProducer
10, localCompletionsForParsedModule
11, getCompletions
12, fromIdentInfo
13) where
14
15import           Control.Applicative
16import           Data.Char                                (isUpper)
17import           Data.Generics
18import           Data.List.Extra                          as List hiding
19                                                                  (stripPrefix)
20import qualified Data.Map                                 as Map
21
22import           Data.Maybe                               (fromMaybe, isJust,
23                                                           isNothing,
24                                                           listToMaybe,
25                                                           mapMaybe)
26import qualified Data.Text                                as T
27import qualified Text.Fuzzy                               as Fuzzy
28
29import           HscTypes
30import           Name
31import           RdrName
32import           Type
33#if MIN_VERSION_ghc(8,10,0)
34import           Coercion
35import           Pair
36import           Predicate                                (isDictTy)
37#endif
38
39import           ConLike
40import           Control.Monad
41import           Data.Aeson                               (ToJSON (toJSON))
42import           Data.Either                              (fromRight)
43import           Data.Functor
44import qualified Data.HashMap.Strict                      as HM
45import qualified Data.Set                                 as Set
46import qualified Data.HashSet                             as HashSet
47import           Development.IDE.Core.Compile
48import           Development.IDE.Core.PositionMapping
49import           Development.IDE.GHC.Compat               as GHC
50import           Development.IDE.GHC.Error
51import           Development.IDE.GHC.Util
52import           Development.IDE.Plugin.Completions.Types
53import           Development.IDE.Spans.Common
54import           Development.IDE.Spans.Documentation
55import           Development.IDE.Spans.LocalBindings
56import           Development.IDE.Types.Exports
57import           Development.IDE.Types.HscEnvEq
58import           Development.IDE.Types.Options
59import           GhcPlugins                               (flLabel, unpackFS)
60import           Ide.PluginUtils                          (mkLspCommand)
61import           Ide.Types                                (CommandId (..),
62                                                           PluginId)
63import           Language.LSP.Types
64import           Language.LSP.Types.Capabilities
65import qualified Language.LSP.VFS                         as VFS
66import           Outputable                               (Outputable)
67import           TyCoRep
68
69-- From haskell-ide-engine/hie-plugin-api/Haskell/Ide/Engine/Context.hs
70
71-- | A context of a declaration in the program
72-- e.g. is the declaration a type declaration or a value declaration
73-- Used for determining which code completions to show
74-- TODO: expand this with more contexts like classes or instances for
75-- smarter code completion
76data Context = TypeContext
77             | ValueContext
78             | ModuleContext String -- ^ module context with module name
79             | ImportContext String -- ^ import context with module name
80             | ImportListContext String -- ^ import list context with module name
81             | ImportHidingContext String -- ^ import hiding context with module name
82             | ExportContext -- ^ List of exported identifiers from the current module
83  deriving (Show, Eq)
84
85-- | Generates a map of where the context is a type and where the context is a value
86-- i.e. where are the value decls and the type decls
87getCContext :: Position -> ParsedModule -> Maybe Context
88getCContext pos pm
89  | Just (L r modName) <- moduleHeader
90  , pos `isInsideSrcSpan` r
91  = Just (ModuleContext (moduleNameString modName))
92
93  | Just (L r _) <- exportList
94  , pos `isInsideSrcSpan` r
95  = Just ExportContext
96
97  | Just ctx <- something (Nothing `mkQ` go `extQ` goInline) decl
98  = Just ctx
99
100  | Just ctx <- something (Nothing `mkQ` importGo) imports
101  = Just ctx
102
103  | otherwise
104  = Nothing
105
106  where decl = hsmodDecls $ unLoc $ pm_parsed_source pm
107        moduleHeader = hsmodName $ unLoc $ pm_parsed_source pm
108        exportList = hsmodExports $ unLoc $ pm_parsed_source pm
109        imports = hsmodImports $ unLoc $ pm_parsed_source pm
110
111        go :: LHsDecl GhcPs -> Maybe Context
112        go (L r SigD {})
113          | pos `isInsideSrcSpan` r = Just TypeContext
114          | otherwise = Nothing
115        go (L r GHC.ValD {})
116          | pos `isInsideSrcSpan` r = Just ValueContext
117          | otherwise = Nothing
118        go _ = Nothing
119
120        goInline :: GHC.LHsType GhcPs -> Maybe Context
121        goInline (GHC.L r _)
122          | pos `isInsideSrcSpan` r = Just TypeContext
123        goInline _ = Nothing
124
125        importGo :: GHC.LImportDecl GhcPs -> Maybe Context
126        importGo (L r impDecl)
127          | pos `isInsideSrcSpan` r
128          = importInline importModuleName (ideclHiding impDecl)
129          <|> Just (ImportContext importModuleName)
130
131          | otherwise = Nothing
132          where importModuleName = moduleNameString $ unLoc $ ideclName impDecl
133
134        importInline :: String -> Maybe (Bool,  GHC.Located [LIE GhcPs]) -> Maybe Context
135        importInline modName (Just (True, L r _))
136          | pos `isInsideSrcSpan` r = Just $ ImportHidingContext modName
137          | otherwise = Nothing
138        importInline modName (Just (False, L r _))
139          | pos `isInsideSrcSpan` r = Just $ ImportListContext modName
140          | otherwise = Nothing
141        importInline _ _ = Nothing
142
143occNameToComKind :: Maybe T.Text -> OccName -> CompletionItemKind
144occNameToComKind ty oc
145  | isVarOcc  oc = case occNameString oc of
146                     i:_ | isUpper i -> CiConstructor
147                     _               -> CiFunction
148  | isTcOcc   oc = case ty of
149                     Just t
150                       | "Constraint" `T.isSuffixOf` t
151                       -> CiInterface
152                     _ -> CiStruct
153  | isDataOcc oc = CiConstructor
154  | otherwise    = CiVariable
155
156
157showModName :: ModuleName -> T.Text
158showModName = T.pack . moduleNameString
159
160-- mkCompl :: IdeOptions -> CompItem -> CompletionItem
161-- mkCompl IdeOptions{..} CI{compKind,insertText, importedFrom,typeText,label,docs} =
162--   CompletionItem label kind (List []) ((colon <>) <$> typeText)
163--     (Just $ CompletionDocMarkup $ MarkupContent MkMarkdown $ T.intercalate sectionSeparator docs')
164--     Nothing Nothing Nothing Nothing (Just insertText) (Just Snippet)
165--     Nothing Nothing Nothing Nothing Nothing
166
167mkCompl :: PluginId -> IdeOptions -> CompItem -> IO CompletionItem
168mkCompl
169  pId
170  IdeOptions {..}
171  CI
172    { compKind,
173      isInfix,
174      insertText,
175      importedFrom,
176      typeText,
177      label,
178      docs,
179      additionalTextEdits
180    } = do
181  mbCommand <- mkAdditionalEditsCommand pId `traverse` additionalTextEdits
182  let ci = CompletionItem
183                 {_label = label,
184                  _kind = kind,
185                  _tags = Nothing,
186                  _detail = (colon <>) <$> typeText,
187                  _documentation = documentation,
188                  _deprecated = Nothing,
189                  _preselect = Nothing,
190                  _sortText = Nothing,
191                  _filterText = Nothing,
192                  _insertText = Just insertText,
193                  _insertTextFormat = Just Snippet,
194                  _insertTextMode = Nothing,
195                  _textEdit = Nothing,
196                  _additionalTextEdits = Nothing,
197                  _commitCharacters = Nothing,
198                  _command = mbCommand,
199                  _xdata = Nothing}
200  return $ removeSnippetsWhen (isJust isInfix) ci
201
202  where kind = Just compKind
203        docs' = imported : spanDocToMarkdown docs
204        imported = case importedFrom of
205          Left pos  -> "*Defined at '" <> ppr pos <> "'*\n'"
206          Right mod -> "*Defined in '" <> mod <> "'*\n"
207        colon = if optNewColonConvention then ": " else ":: "
208        documentation = Just $ CompletionDocMarkup $
209                        MarkupContent MkMarkdown $
210                        T.intercalate sectionSeparator docs'
211
212mkAdditionalEditsCommand :: PluginId -> ExtendImport -> IO Command
213mkAdditionalEditsCommand pId edits = pure $
214  mkLspCommand pId (CommandId extendImportCommandId) "extend import" (Just [toJSON edits])
215
216mkNameCompItem :: Uri -> Maybe T.Text -> OccName -> ModuleName -> Maybe Type -> Maybe Backtick -> SpanDoc -> Maybe (LImportDecl GhcPs) -> CompItem
217mkNameCompItem doc thingParent origName origMod thingType isInfix docs !imp = CI {..}
218  where
219    compKind = occNameToComKind typeText origName
220    importedFrom = Right $ showModName origMod
221    isTypeCompl = isTcOcc origName
222    label = stripPrefix $ showGhc origName
223    insertText = case isInfix of
224            Nothing -> case getArgText <$> thingType of
225                            Nothing      -> label
226                            Just argText -> label <> " " <> argText
227            Just LeftSide -> label <> "`"
228
229            Just Surrounded -> label
230    typeText
231          | Just t <- thingType = Just . stripForall $ showGhc t
232          | otherwise = Nothing
233    additionalTextEdits =
234      imp <&> \x ->
235        ExtendImport
236          { doc,
237            thingParent,
238            importName = showModName $ unLoc $ ideclName $ unLoc x,
239            importQual = getImportQual x,
240            newThing = showNameWithoutUniques origName
241          }
242
243    stripForall :: T.Text -> T.Text
244    stripForall t
245      | T.isPrefixOf "forall" t =
246        -- We drop 2 to remove the '.' and the space after it
247        T.drop 2 (T.dropWhile (/= '.') t)
248      | otherwise               = t
249
250    getArgText :: Type -> T.Text
251    getArgText typ = argText
252      where
253        argTypes = getArgs typ
254        argText :: T.Text
255        argText = mconcat $ List.intersperse " " $ zipWithFrom snippet 1 argTypes
256        snippet :: Int -> Type -> T.Text
257        snippet i t = case t of
258            (TyVarTy _)     -> noParensSnippet
259            (LitTy _)       -> noParensSnippet
260            (TyConApp _ []) -> noParensSnippet
261            _               -> snippetText i ("(" <> showGhc t <> ")")
262            where
263                noParensSnippet = snippetText i (showGhc t)
264                snippetText i t = "${" <> T.pack (show i) <> ":" <> t <> "}"
265        getArgs :: Type -> [Type]
266        getArgs t
267          | isPredTy t = []
268          | isDictTy t = []
269          | isForAllTy t = getArgs $ snd (splitForAllTys t)
270          | isFunTy t =
271            let (args, ret) = splitFunTys t
272              in if isForAllTy ret
273                  then getArgs ret
274                  else Prelude.filter (not . isDictTy) $ map scaledThing args
275          | isPiTy t = getArgs $ snd (splitPiTys t)
276#if MIN_VERSION_ghc(8,10,0)
277          | Just (Pair _ t) <- coercionKind <$> isCoercionTy_maybe t
278          = getArgs t
279#else
280          | isCoercionTy t = maybe [] (getArgs . snd) (splitCoercionType_maybe t)
281#endif
282          | otherwise = []
283
284mkModCompl :: T.Text -> CompletionItem
285mkModCompl label =
286  CompletionItem label (Just CiModule) Nothing Nothing
287    Nothing Nothing Nothing Nothing Nothing Nothing Nothing
288    Nothing Nothing Nothing Nothing Nothing Nothing
289
290mkModuleFunctionImport :: T.Text -> T.Text -> CompletionItem
291mkModuleFunctionImport moduleName label =
292  CompletionItem label (Just CiFunction) Nothing (Just moduleName)
293    Nothing Nothing Nothing Nothing Nothing Nothing Nothing
294    Nothing Nothing Nothing Nothing Nothing Nothing
295
296mkImportCompl :: T.Text -> T.Text -> CompletionItem
297mkImportCompl enteredQual label =
298  CompletionItem m (Just CiModule) Nothing (Just label)
299    Nothing Nothing Nothing Nothing Nothing Nothing Nothing
300    Nothing Nothing Nothing Nothing Nothing Nothing
301  where
302    m = fromMaybe "" (T.stripPrefix enteredQual label)
303
304mkExtCompl :: T.Text -> CompletionItem
305mkExtCompl label =
306  CompletionItem label (Just CiKeyword) Nothing Nothing
307    Nothing Nothing Nothing Nothing Nothing Nothing Nothing
308    Nothing Nothing Nothing Nothing Nothing Nothing
309
310
311fromIdentInfo :: Uri -> IdentInfo -> Maybe T.Text -> CompItem
312fromIdentInfo doc IdentInfo{..} q = CI
313  { compKind= occNameToComKind Nothing name
314  , insertText=rendered
315  , importedFrom=Right moduleNameText
316  , typeText=Nothing
317  , label=rendered
318  , isInfix=Nothing
319  , docs=emptySpanDoc
320  , isTypeCompl= not isDatacon && isUpper (T.head rendered)
321  , additionalTextEdits= Just $
322        ExtendImport
323          { doc,
324            thingParent = parent,
325            importName = moduleNameText,
326            importQual = q,
327            newThing = rendered
328          }
329  }
330
331cacheDataProducer :: Uri -> HscEnvEq -> Module -> GlobalRdrEnv-> GlobalRdrEnv -> [LImportDecl GhcPs] -> IO CachedCompletions
332cacheDataProducer uri env curMod globalEnv inScopeEnv limports = do
333  let
334      packageState = hscEnv env
335      curModName = moduleName curMod
336
337      importMap = Map.fromList [ (l, imp) | imp@(L (OldRealSrcSpan l) _) <- limports ]
338
339      iDeclToModName :: ImportDecl name -> ModuleName
340      iDeclToModName = unLoc . ideclName
341
342      asNamespace :: ImportDecl name -> ModuleName
343      asNamespace imp = maybe (iDeclToModName imp) GHC.unLoc (ideclAs imp)
344      -- Full canonical names of imported modules
345      importDeclerations = map unLoc limports
346
347
348      -- The given namespaces for the imported modules (ie. full name, or alias if used)
349      allModNamesAsNS = map (showModName . asNamespace) importDeclerations
350
351      rdrElts = globalRdrEnvElts globalEnv
352
353      foldMapM :: (Foldable f, Monad m, Monoid b) => (a -> m b) -> f a -> m b
354      foldMapM f xs = foldr step return xs mempty where
355        step x r z = f x >>= \y -> r $! z `mappend` y
356
357      getCompls :: [GlobalRdrElt] -> IO ([CompItem],QualCompls)
358      getCompls = foldMapM getComplsForOne
359
360      getComplsForOne :: GlobalRdrElt -> IO ([CompItem],QualCompls)
361      getComplsForOne (GRE n par True _) =
362          (, mempty) <$> toCompItem par curMod curModName n Nothing
363      getComplsForOne (GRE n par False prov) =
364        flip foldMapM (map is_decl prov) $ \spec -> do
365          let originalImportDecl = do
366                -- we don't want to extend import if it's already in scope
367                guard . null $ lookupGRE_Name inScopeEnv n
368                -- or if it doesn't have a real location
369                loc <- realSpanis_dloc spec
370                Map.lookup loc importMap
371          compItem <- toCompItem par curMod (is_mod spec) n originalImportDecl
372          let unqual
373                | is_qual spec = []
374                | otherwise = compItem
375              qual
376                | is_qual spec = Map.singleton asMod compItem
377                | otherwise = Map.fromList [(asMod,compItem),(origMod,compItem)]
378              asMod = showModName (is_as spec)
379              origMod = showModName (is_mod spec)
380          return (unqual,QualCompls qual)
381
382      toCompItem :: Parent -> Module -> ModuleName -> Name -> Maybe (LImportDecl GhcPs) -> IO [CompItem]
383      toCompItem par m mn n imp' = do
384        docs <- getDocumentationTryGhc packageState curMod n
385        let (mbParent, originName) = case par of
386                            NoParent -> (Nothing, nameOccName n)
387                            ParentIs n' -> (Just . T.pack $ printName n', nameOccName n)
388                            FldParent n' lbl -> (Just . T.pack $ printName n', maybe (nameOccName n) mkVarOccFS lbl)
389        tys <- catchSrcErrors (hsc_dflags packageState) "completion" $ do
390                name' <- lookupName packageState m n
391                return ( name' >>= safeTyThingType
392                       , guard (isJust mbParent) >> name' >>= safeTyThingForRecord
393                       )
394        let (ty, record_ty) = fromRight (Nothing, Nothing) tys
395
396        let recordCompls = case record_ty of
397                Just (ctxStr, flds) | not (null flds) ->
398                    [mkRecordSnippetCompItem uri mbParent ctxStr flds (ppr mn) docs imp']
399                _ -> []
400
401        return $ mkNameCompItem uri mbParent originName mn ty Nothing docs imp'
402               : recordCompls
403
404  (unquals,quals) <- getCompls rdrElts
405
406  -- The list of all importable Modules from all packages
407  moduleNames <- maybe [] (map showModName) <$> envVisibleModuleNames env
408
409  return $ CC
410    { allModNamesAsNS = allModNamesAsNS
411    , unqualCompls = unquals
412    , qualCompls = quals
413    , anyQualCompls = []
414    , importableModules = moduleNames
415    }
416
417-- | Produces completions from the top level declarations of a module.
418localCompletionsForParsedModule :: Uri -> ParsedModule -> CachedCompletions
419localCompletionsForParsedModule uri pm@ParsedModule{pm_parsed_source = L _ HsModule{hsmodDecls, hsmodName}} =
420    CC { allModNamesAsNS = mempty
421       , unqualCompls = compls
422       , qualCompls = mempty
423       , anyQualCompls = []
424       , importableModules = mempty
425        }
426  where
427    typeSigIds = Set.fromList
428        [ id
429            | L _ (SigD _ (TypeSig _ ids _)) <- hsmodDecls
430            , L _ id <- ids
431            ]
432    hasTypeSig = (`Set.member` typeSigIds) . unLoc
433
434    compls = concat
435        [ case decl of
436            SigD _ (TypeSig _ ids typ) ->
437                [mkComp id CiFunction (Just $ ppr typ) | id <- ids]
438            ValD _ FunBind{fun_id} ->
439                [ mkComp fun_id CiFunction Nothing
440                | not (hasTypeSig fun_id)
441                ]
442            ValD _ PatBind{pat_lhs} ->
443                [mkComp id CiVariable Nothing
444                | VarPat _ id <- listify (\(_ :: Pat GhcPs) -> True) pat_lhs]
445            TyClD _ ClassDecl{tcdLName, tcdSigs} ->
446                mkComp tcdLName CiInterface Nothing :
447                [ mkComp id CiFunction (Just $ ppr typ)
448                | L _ (ClassOpSig _ _ ids typ) <- tcdSigs
449                , id <- ids]
450            TyClD _ x ->
451                let generalCompls = [mkComp id cl Nothing
452                        | id <- listify (\(_ :: Located(IdP GhcPs)) -> True) x
453                        , let cl = occNameToComKind Nothing (rdrNameOcc $ unLoc id)]
454                    -- here we only have to look at the outermost type
455                    recordCompls = findRecordCompl uri pm thisModName x
456                in
457                   -- the constructors and snippets will be duplicated here giving the user 2 choices.
458                   generalCompls ++ recordCompls
459            ForD _ ForeignImport{fd_name,fd_sig_ty} ->
460                [mkComp fd_name CiVariable (Just $ ppr fd_sig_ty)]
461            ForD _ ForeignExport{fd_name,fd_sig_ty} ->
462                [mkComp fd_name CiVariable (Just $ ppr fd_sig_ty)]
463            _ -> []
464            | L _ decl <- hsmodDecls
465        ]
466
467    mkComp n ctyp ty =
468        CI ctyp pn (Right thisModName) ty pn Nothing doc (ctyp `elem` [CiStruct, CiInterface]) Nothing
469      where
470        pn = ppr n
471        doc = SpanDocText (getDocumentation [pm] n) (SpanDocUris Nothing Nothing)
472
473    thisModName = ppr hsmodName
474
475findRecordCompl :: Uri -> ParsedModule -> T.Text -> TyClDecl GhcPs -> [CompItem]
476findRecordCompl uri pmod mn DataDecl {tcdLName, tcdDataDefn} = result
477    where
478        result = [mkRecordSnippetCompItem uri (Just $ showNameWithoutUniques $ unLoc tcdLName)
479                        (showGhc . unLoc $ con_name) field_labels mn doc Nothing
480                 | ConDeclH98{..} <- unLoc <$> dd_cons tcdDataDefn
481                 , Just  con_details <- [getFlds con_args]
482                 , let field_names = mapMaybe extract con_details
483                 , let field_labels = showGhc . unLoc <$> field_names
484                 , (not . List.null) field_labels
485                 ]
486        doc = SpanDocText (getDocumentation [pmod] tcdLName) (SpanDocUris Nothing Nothing)
487
488        getFlds :: HsConDetails arg (Located [LConDeclField GhcPs]) -> Maybe [ConDeclField GhcPs]
489        getFlds conArg = case conArg of
490                             RecCon rec  -> Just $ unLoc <$> unLoc rec
491                             PrefixCon _ -> Just []
492                             _           -> Nothing
493
494        extract ConDeclField{..}
495             -- TODO: Why is cd_fld_names a list?
496            | Just fld_name <- rdrNameFieldOcc . unLoc <$> listToMaybe cd_fld_names = Just fld_name
497            | otherwise = Nothing
498        -- XConDeclField
499        extract _ = Nothing
500findRecordCompl _ _ _ _ = []
501
502ppr :: Outputable a => a -> T.Text
503ppr = T.pack . prettyPrint
504
505toggleSnippets :: ClientCapabilities -> CompletionsConfig -> CompletionItem -> CompletionItem
506toggleSnippets ClientCapabilities {_textDocument} (CompletionsConfig with _) =
507  removeSnippetsWhen (not $ with && supported)
508  where
509    supported =
510      Just True == (_textDocument >>= _completion >>= _completionItem >>= _snippetSupport)
511
512toggleAutoExtend :: CompletionsConfig -> CompItem -> CompItem
513toggleAutoExtend (CompletionsConfig _ False) x = x {additionalTextEdits = Nothing}
514toggleAutoExtend _ x = x
515
516removeSnippetsWhen :: Bool -> CompletionItem -> CompletionItem
517removeSnippetsWhen condition x =
518  if condition
519    then
520      x
521        { _insertTextFormat = Just PlainText,
522          _insertText = Nothing
523        }
524    else x
525
526-- | Returns the cached completions for the given module and position.
527getCompletions
528    :: PluginId
529    -> IdeOptions
530    -> CachedCompletions
531    -> Maybe (ParsedModule, PositionMapping)
532    -> (Bindings, PositionMapping)
533    -> VFS.PosPrefixInfo
534    -> ClientCapabilities
535    -> CompletionsConfig
536    -> HM.HashMap T.Text (HashSet.HashSet IdentInfo)
537    -> IO [CompletionItem]
538getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
539               maybe_parsed (localBindings, bmapping) prefixInfo caps config moduleExportsMap = do
540  let VFS.PosPrefixInfo { fullLine, prefixModule, prefixText } = prefixInfo
541      enteredQual = if T.null prefixModule then "" else prefixModule <> "."
542      fullPrefix  = enteredQual <> prefixText
543
544      {- correct the position by moving 'foo :: Int -> String ->    '
545                                                                    ^
546          to                             'foo :: Int -> String ->    '
547                                                              ^
548      -}
549      pos = VFS.cursorPos prefixInfo
550
551      filtModNameCompls =
552        map mkModCompl
553          $ mapMaybe (T.stripPrefix enteredQual)
554          $ Fuzzy.simpleFilter fullPrefix allModNamesAsNS
555
556      filtCompls = map Fuzzy.original $ Fuzzy.filter prefixText ctxCompls "" "" label False
557        where
558
559          mcc = case maybe_parsed of
560            Nothing -> Nothing
561            Just (pm, pmapping) ->
562              let PositionMapping pDelta = pmapping
563                  position' = fromDelta pDelta pos
564                  lpos = lowerRange position'
565                  hpos = upperRange position'
566              in getCContext lpos pm <|> getCContext hpos pm
567
568          -- completions specific to the current context
569          ctxCompls' = case mcc of
570                        Nothing           -> compls
571                        Just TypeContext  -> filter isTypeCompl compls
572                        Just ValueContext -> filter (not . isTypeCompl) compls
573                        Just _            -> filter (not . isTypeCompl) compls
574          -- Add whether the text to insert has backticks
575          ctxCompls = map (\comp -> toggleAutoExtend config $ comp { isInfix = infixCompls }) ctxCompls'
576
577          infixCompls :: Maybe Backtick
578          infixCompls = isUsedAsInfix fullLine prefixModule prefixText pos
579
580          PositionMapping bDelta = bmapping
581          oldPos = fromDelta bDelta $ VFS.cursorPos prefixInfo
582          startLoc = lowerRange oldPos
583          endLoc = upperRange oldPos
584          localCompls = map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
585          localBindsToCompItem :: Name -> Maybe Type -> CompItem
586          localBindsToCompItem name typ = CI ctyp pn thisModName ty pn Nothing emptySpanDoc (not $ isValOcc occ) Nothing
587            where
588              occ = nameOccName name
589              ctyp = occNameToComKind Nothing occ
590              pn = ppr name
591              ty = ppr <$> typ
592              thisModName = case nameModule_maybe name of
593                Nothing -> Left $ nameSrcSpan name
594                Just m  -> Right $ ppr m
595
596          compls = if T.null prefixModule
597            then localCompls ++ unqualCompls ++ (($Nothing) <$> anyQualCompls)
598            else Map.findWithDefault [] prefixModule (getQualCompls qualCompls)
599                 ++ (($ Just prefixModule) <$> anyQualCompls)
600
601      filtListWith f list =
602        [ f label
603        | label <- Fuzzy.simpleFilter fullPrefix list
604        , enteredQual `T.isPrefixOf` label
605        ]
606
607      filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
608      filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
609      filtKeywordCompls
610          | T.null prefixModule = filtListWith mkExtCompl (optKeywords ideOpts)
611          | otherwise = []
612
613  if
614    -- TODO: handle multiline imports
615    | "import " `T.isPrefixOf` fullLine
616      && (List.length (words (T.unpack fullLine)) >= 2)
617      && "(" `isInfixOf` T.unpack fullLine
618    -> do
619      let moduleName = T.pack $ words (T.unpack fullLine) !! 1
620          funcs = HM.lookupDefault HashSet.empty moduleName moduleExportsMap
621          funs = map (show . name) $ HashSet.toList funcs
622      return $ filterModuleExports moduleName $ map T.pack funs
623    | "import " `T.isPrefixOf` fullLine
624    -> return filtImportCompls
625    -- we leave this condition here to avoid duplications and return empty list
626    -- since HLS implements these completions (#haskell-language-server/pull/662)
627    | "{-# " `T.isPrefixOf` fullLine
628    -> return []
629    | otherwise -> do
630        -- assumes that nubOrdBy is stable
631        let uniqueFiltCompls = nubOrdBy uniqueCompl filtCompls
632        compls <- mapM (mkCompl plId ideOpts) uniqueFiltCompls
633        return $ filtModNameCompls
634              ++ filtKeywordCompls
635              ++ map (toggleSnippets caps config) compls
636
637uniqueCompl :: CompItem -> CompItem -> Ordering
638uniqueCompl x y =
639  case compare (label x, importedFrom x, compKind x)
640               (label y, importedFrom y, compKind y) of
641    EQ ->
642      -- preserve completions for duplicate record fields where the only difference is in the type
643      -- remove redundant completions with less type info
644      if typeText x == typeText y
645        || isNothing (typeText x)
646        || isNothing (typeText y)
647        then EQ
648        else compare (insertText x) (insertText y)
649    other -> other
650
651-- ---------------------------------------------------------------------
652-- helper functions for infix backticks
653-- ---------------------------------------------------------------------
654
655hasTrailingBacktick :: T.Text -> Position -> Bool
656hasTrailingBacktick line Position { _character }
657    | T.length line > _character = (line `T.index` _character) == '`'
658    | otherwise = False
659
660isUsedAsInfix :: T.Text -> T.Text -> T.Text -> Position -> Maybe Backtick
661isUsedAsInfix line prefixMod prefixText pos
662    | hasClosingBacktick && hasOpeningBacktick = Just Surrounded
663    | hasOpeningBacktick = Just LeftSide
664    | otherwise = Nothing
665  where
666    hasOpeningBacktick = openingBacktick line prefixMod prefixText pos
667    hasClosingBacktick = hasTrailingBacktick line pos
668
669openingBacktick :: T.Text -> T.Text -> T.Text -> Position -> Bool
670openingBacktick line prefixModule prefixText Position { _character }
671  | backtickIndex < 0 || backtickIndex > T.length line = False
672  | otherwise = (line `T.index` backtickIndex) == '`'
673    where
674    backtickIndex :: Int
675    backtickIndex =
676      let
677          prefixLength = T.length prefixText
678          moduleLength = if prefixModule == ""
679                    then 0
680                    else T.length prefixModule + 1 {- Because of "." -}
681      in
682        -- Points to the first letter of either the module or prefix text
683        _character - (prefixLength + moduleLength) - 1
684
685
686-- ---------------------------------------------------------------------
687
688-- | Under certain circumstance GHC generates some extra stuff that we
689-- don't want in the autocompleted symbols
690    {- When e.g. DuplicateRecordFields is enabled, compiler generates
691    names like "$sel:accessor:One" and "$sel:accessor:Two" to disambiguate record selectors
692    https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields/DuplicateRecordFields#Implementation
693    -}
694-- TODO: Turn this into an alex lexer that discards prefixes as if they were whitespace.
695stripPrefix :: T.Text -> T.Text
696stripPrefix name = T.takeWhile (/=':') $ go prefixes
697  where
698    go [] = name
699    go (p:ps)
700      | T.isPrefixOf p name = T.drop (T.length p) name
701      | otherwise = go ps
702
703-- | Prefixes that can occur in a GHC OccName
704prefixes :: [T.Text]
705prefixes =
706  [
707    -- long ones
708    "$con2tag_"
709  , "$tag2con_"
710  , "$maxtag_"
711
712  -- four chars
713  , "$sel:"
714  , "$tc'"
715
716  -- three chars
717  , "$dm"
718  , "$co"
719  , "$tc"
720  , "$cp"
721  , "$fx"
722
723  -- two chars
724  , "$W"
725  , "$w"
726  , "$m"
727  , "$b"
728  , "$c"
729  , "$d"
730  , "$i"
731  , "$s"
732  , "$f"
733  , "$r"
734  , "C:"
735  , "N:"
736  , "D:"
737  , "$p"
738  , "$L"
739  , "$f"
740  , "$t"
741  , "$c"
742  , "$m"
743  ]
744
745
746safeTyThingForRecord :: TyThing -> Maybe (T.Text, [T.Text])
747safeTyThingForRecord (AnId _) = Nothing
748safeTyThingForRecord (AConLike dc) =
749    let ctxStr =   showGhc . occName . conLikeName $ dc
750        field_names = T.pack . unpackFS . flLabel <$> conLikeFieldLabels dc
751    in
752        Just (ctxStr, field_names)
753safeTyThingForRecord _ = Nothing
754
755mkRecordSnippetCompItem :: Uri -> Maybe T.Text -> T.Text -> [T.Text] -> T.Text -> SpanDoc -> Maybe (LImportDecl GhcPs) -> CompItem
756mkRecordSnippetCompItem uri parent ctxStr compl mn docs imp = r
757  where
758      r  = CI {
759            compKind = CiSnippet
760          , insertText = buildSnippet
761          , importedFrom = importedFrom
762          , typeText = Nothing
763          , label = ctxStr
764          , isInfix = Nothing
765          , docs = docs
766          , isTypeCompl = False
767          , additionalTextEdits = imp <&> \x ->
768            ExtendImport
769                { doc = uri,
770                  thingParent = parent,
771                  importName = showModName $ unLoc $ ideclName $ unLoc x,
772                  importQual = getImportQual x,
773                  newThing = ctxStr
774                }
775          }
776
777      placeholder_pairs = zip compl ([1..]::[Int])
778      snippet_parts = map (\(x, i) -> x <> "=${" <> T.pack (show i) <> ":_" <> x <> "}") placeholder_pairs
779      snippet = T.intercalate (T.pack ", ") snippet_parts
780      buildSnippet = ctxStr <> " {" <> snippet <> "}"
781      importedFrom = Right mn
782
783getImportQual :: LImportDecl GhcPs -> Maybe T.Text
784getImportQual (L _ imp)
785    | isQualifiedImport imp = Just $ T.pack $ moduleNameString $ maybe (unLoc $ ideclName imp) unLoc (ideclAs imp)
786    | otherwise = Nothing
787