Skip to content

fix new import position #2981

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

Merged
merged 9 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 109 additions & 34 deletions ghcide/src/Development/IDE/Plugin/CodeAction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ module Development.IDE.Plugin.CodeAction

import Control.Applicative ((<|>))
import Control.Arrow (second,
(>>>),
(&&&))
(&&&),
(>>>))
import Control.Concurrent.STM.Stats (atomically)
import Control.Monad (guard, join,
msum)
import Control.Monad (guard, join)
import Control.Monad.IO.Class
import Data.Char
import qualified Data.DList as DL
Expand All @@ -34,7 +33,7 @@ import qualified Data.HashSet as Set
import Data.List.Extra
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as M
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Ord (comparing)
import qualified Data.Rope.UTF16 as Rope
Expand All @@ -47,7 +46,6 @@ import Development.IDE.Core.Service
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Compat.Util
import Development.IDE.GHC.Error
import Development.IDE.GHC.ExactPrint
import Development.IDE.GHC.Util (printOutputable,
printRdrName,
traceAst)
Expand Down Expand Up @@ -80,6 +78,25 @@ import Language.LSP.Types (CodeAction (
import Language.LSP.VFS
import Text.Regex.TDFA (mrAfter,
(=~), (=~~))
#if MIN_VERSION_ghc(9,2,0)
import GHC (AddEpAnn (AddEpAnn),
Anchor (anchor_op),
AnchorOperation (..),
AnnsModule (am_main),
DeltaPos (..),
EpAnn (..),
EpaLocation (..),
LEpaComment,
LocatedA)

import Control.Monad (msum)
#else
import Language.Haskell.GHC.ExactPrint.Types (Annotation (annsDP),
DeltaPos,
KeywordId (G),
deltaRow,
mkAnnKey)
#endif

-------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -227,10 +244,8 @@ findInstanceHead df instanceHead decls =

#if MIN_VERSION_ghc(9,2,0)
findDeclContainingLoc :: Foldable t => Position -> t (GenLocated (SrcSpanAnn' a) e) -> Maybe (GenLocated (SrcSpanAnn' a) e)
#elif MIN_VERSION_ghc(8,10,0)
findDeclContainingLoc :: Foldable t => Position -> t (GenLocated SrcSpan e) -> Maybe (GenLocated SrcSpan e)
#else
-- TODO populate this type signature for GHC versions <8.10
findDeclContainingLoc :: Foldable t => Position -> t (GenLocated SrcSpan e) -> Maybe (GenLocated SrcSpan e)
#endif
findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` locA l)

Expand All @@ -243,8 +258,8 @@ findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` locA l)
-- imported from ‘Data.ByteString’ at B.hs:6:1-22
-- imported from ‘Data.ByteString.Lazy’ at B.hs:8:1-27
-- imported from ‘Data.Text’ at B.hs:7:1-16
suggestHideShadow :: ParsedSource -> T.Text -> Maybe TcModuleResult -> Maybe HieAstResult -> Diagnostic -> [(T.Text, [Either TextEdit Rewrite])]
suggestHideShadow ps@(L _ HsModule {hsmodImports}) fileContents mTcM mHar Diagnostic {_message, _range}
suggestHideShadow :: Annotated ParsedSource -> T.Text -> Maybe TcModuleResult -> Maybe HieAstResult -> Diagnostic -> [(T.Text, [Either TextEdit Rewrite])]
suggestHideShadow ps fileContents mTcM mHar Diagnostic {_message, _range}
| Just [identifier, modName, s] <-
matchRegexUnifySpaces
_message
Expand All @@ -261,6 +276,8 @@ suggestHideShadow ps@(L _ HsModule {hsmodImports}) fileContents mTcM mHar Diagno
result <> [hideAll]
| otherwise = []
where
L _ HsModule {hsmodImports} = astA ps

suggests identifier modName s
| Just tcM <- mTcM,
Just har <- mHar,
Expand Down Expand Up @@ -940,11 +957,11 @@ isPreludeImplicit = xopt Lang.ImplicitPrelude
suggestImportDisambiguation ::
DynFlags ->
Maybe T.Text ->
ParsedSource ->
Annotated ParsedSource ->
T.Text ->
Diagnostic ->
[(T.Text, [Either TextEdit Rewrite])]
suggestImportDisambiguation df (Just txt) ps@(L _ HsModule {hsmodImports}) fileContents diag@Diagnostic {..}
suggestImportDisambiguation df (Just txt) ps fileContents diag@Diagnostic {..}
| Just [ambiguous] <-
matchRegexUnifySpaces
_message
Expand All @@ -956,6 +973,8 @@ suggestImportDisambiguation df (Just txt) ps@(L _ HsModule {hsmodImports}) fileC
suggestions ambiguous modules (isJust local)
| otherwise = []
where
L _ HsModule {hsmodImports} = astA ps

locDic =
fmap (NE.fromList . DL.toList) $
Map.fromListWith (<>) $
Expand Down Expand Up @@ -1048,21 +1067,21 @@ targetModuleName (ExistingImp _) =
error "Cannot happen!"

disambiguateSymbol ::
ParsedSource ->
Annotated ParsedSource ->
T.Text ->
Diagnostic ->
T.Text ->
HidingMode ->
[Either TextEdit Rewrite]
disambiguateSymbol pm fileContents Diagnostic {..} (T.unpack -> symbol) = \case
disambiguateSymbol ps fileContents Diagnostic {..} (T.unpack -> symbol) = \case
(HideOthers hiddens0) ->
[ Right $ hideSymbol symbol idecl
| ExistingImp idecls <- hiddens0
, idecl <- NE.toList idecls
]
++ mconcat
[ if null imps
then maybeToList $ Left . snd <$> newImportToEdit (hideImplicitPreludeSymbol $ T.pack symbol) pm fileContents
then maybeToList $ Left . snd <$> newImportToEdit (hideImplicitPreludeSymbol $ T.pack symbol) ps fileContents
else Right . hideSymbol symbol <$> imps
| ImplicitPrelude imps <- hiddens0
]
Expand Down Expand Up @@ -1292,7 +1311,7 @@ removeRedundantConstraints df (L _ HsModule {hsmodDecls}) Diagnostic{..}

-------------------------------------------------------------------------------------------------

suggestNewOrExtendImportForClassMethod :: ExportsMap -> ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, [Either TextEdit Rewrite])]
suggestNewOrExtendImportForClassMethod :: ExportsMap -> Annotated ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, [Either TextEdit Rewrite])]
suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnostic {_message}
| Just [methodName, className] <-
matchRegexUnifySpaces
Expand All @@ -1306,7 +1325,7 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
where
suggest identInfo@IdentInfo {moduleNameText}
| importStyle <- NE.toList $ importStyles identInfo,
mImportDecl <- findImportDeclByModuleName (hsmodImports $ unLoc ps) (T.unpack moduleNameText) =
mImportDecl <- findImportDeclByModuleName (hsmodImports . unLoc . astA $ ps) (T.unpack moduleNameText) =
case mImportDecl of
-- extend
Just decl ->
Expand All @@ -1328,8 +1347,8 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
<> [(quickFixImportKind "new.all", newImportAll moduleNameText)]
| otherwise -> []

suggestNewImport :: ExportsMap -> ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, TextEdit)]
suggestNewImport packageExportsMap ps@(L _ HsModule {..}) fileContents Diagnostic{_message}
suggestNewImport :: ExportsMap -> Annotated ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, TextEdit)]
suggestNewImport packageExportsMap ps fileContents Diagnostic{_message}
| msg <- unifySpaces _message
, Just thingMissing <- extractNotInScopeName msg
, qual <- extractQualifiedModuleName msg
Expand All @@ -1344,6 +1363,8 @@ suggestNewImport packageExportsMap ps@(L _ HsModule {..}) fileContents Diagnosti
= sortOn fst3 [(imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))
| (kind, unNewImport -> imp) <- constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions
]
where
L _ HsModule {..} = astA ps
suggestNewImport _ _ _ _ = []

constructNewImportSuggestions
Expand Down Expand Up @@ -1371,7 +1392,7 @@ constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules =
newtype NewImport = NewImport {unNewImport :: T.Text}
deriving (Show, Eq, Ord)

newImportToEdit :: NewImport -> ParsedSource -> T.Text -> Maybe (T.Text, TextEdit)
newImportToEdit :: NewImport -> Annotated ParsedSource -> T.Text -> Maybe (T.Text, TextEdit)
newImportToEdit (unNewImport -> imp) ps fileContents
| Just (range, indent) <- newImportInsertRange ps fileContents
= Just (imp, TextEdit range (imp <> "\n" <> T.replicate indent " "))
Expand All @@ -1385,35 +1406,89 @@ newImportToEdit (unNewImport -> imp) ps fileContents
-- * If the file has neither existing imports nor a module declaration,
-- the import will be inserted at line zero if there are no pragmas,
-- * otherwise inserted one line after the last file-header pragma
newImportInsertRange :: ParsedSource -> T.Text -> Maybe (Range, Int)
newImportInsertRange (L _ HsModule {..}) fileContents
newImportInsertRange :: Annotated ParsedSource -> T.Text -> Maybe (Range, Int)
newImportInsertRange ps fileContents
| Just ((l, c), col) <- case hsmodImports of
[] -> findPositionNoImports (fmap reLoc hsmodName) (fmap reLoc hsmodExports) fileContents
_ -> findPositionFromImportsOrModuleDecl (map reLoc hsmodImports) last True
[] -> (\line -> ((line, 0), 0)) <$> findPositionNoImports ps fileContents
_ -> findPositionFromImportsOrModuleDecl (map reLoc hsmodImports) last
, let insertPos = Position (fromIntegral l) (fromIntegral c)
= Just (Range insertPos insertPos, col)
| otherwise = Nothing
where
L _ HsModule {..} = astA ps

-- | Insert the import under the Module declaration exports if they exist, otherwise just under the module declaration.
-- If no module declaration exists, then no exports will exist either, in that case
-- insert the import after any file-header pragmas or at position zero if there are no pragmas
findPositionNoImports :: Maybe (Located ModuleName) -> Maybe (Located [LIE name]) -> T.Text -> Maybe ((Int, Int), Int)
findPositionNoImports Nothing _ fileContents = findNextPragmaPosition fileContents
findPositionNoImports _ (Just hsmodExports) _ = findPositionFromImportsOrModuleDecl hsmodExports id False
findPositionNoImports (Just hsmodName) _ _ = findPositionFromImportsOrModuleDecl hsmodName id False
findPositionNoImports :: Annotated ParsedSource -> T.Text -> Maybe Int
findPositionNoImports ps fileContents =
maybe (Just (findNextPragmaPosition fileContents)) (findPositionAfterModuleName ps) hsmodName
where
L _ HsModule {..} = astA ps

findPositionAfterModuleName :: Annotated ParsedSource
#if MIN_VERSION_ghc(9,2,0)
-> LocatedA ModuleName
#else
-> Located ModuleName
#endif
-> Maybe Int
findPositionAfterModuleName ps hsmodName' = do
lineOffset <- whereKeywordLineOffset
case prevSrcSpan of
UnhelpfulSpan _ -> Nothing
(RealSrcSpan prevSrcSpan' _) ->
Just $ srcLocLine (realSrcSpanEnd prevSrcSpan') + lineOffset
where
L _ HsModule {..} = astA ps

prevSrcSpan = maybe (getLoc hsmodName') getLoc hsmodExports

whereKeywordLineOffset :: Maybe Int
#if MIN_VERSION_ghc(9,2,0)
whereKeywordLineOffset = case hsmodAnn of
EpAnn _ annsModule _ -> do
whereLocation <- fmap NE.head . NE.nonEmpty . mapMaybe filterWhere . am_main $ annsModule
epaLocationToLine whereLocation
EpAnnNotUsed -> Nothing
filterWhere (AddEpAnn AnnWhere loc) = Just loc
filterWhere _ = Nothing

epaLocationToLine :: EpaLocation -> Maybe Int
epaLocationToLine (EpaSpan sp) = Just . srcLocLine . realSrcSpanEnd $ sp
epaLocationToLine (EpaDelta (SameLine _) priorComments) = Just $ sumCommentsOffset priorComments
epaLocationToLine (EpaDelta (DifferentLine line _) priorComments) = Just (line + sumCommentsOffset priorComments)

sumCommentsOffset :: [LEpaComment] -> Int
sumCommentsOffset = sum . fmap (\(L anchor _) -> anchorOpLine (anchor_op anchor))

anchorOpLine :: AnchorOperation -> Int
anchorOpLine UnchangedAnchor = 0
anchorOpLine (MovedAnchor (SameLine _)) = 0
anchorOpLine (MovedAnchor (DifferentLine line _)) = line
#else
whereKeywordLineOffset = do
ann <- annsA ps M.!? mkAnnKey (astA ps)
deltaPos <- fmap NE.head . NE.nonEmpty .mapMaybe filterWhere $ annsDP ann
pure $ deltaRow deltaPos

filterWhere :: (KeywordId, DeltaPos) -> Maybe DeltaPos
filterWhere (keywordId, deltaPos) =
if keywordId == G AnnWhere then Just deltaPos else Nothing
#endif

findPositionFromImportsOrModuleDecl :: HasSrcSpan a => t -> (t -> a) -> Bool -> Maybe ((Int, Int), Int)
findPositionFromImportsOrModuleDecl hsField f hasImports = case getLoc (f hsField) of
findPositionFromImportsOrModuleDecl :: HasSrcSpan a => t -> (t -> a) -> Maybe ((Int, Int), Int)
findPositionFromImportsOrModuleDecl hsField f = case getLoc (f hsField) of
RealSrcSpan s _ ->
let col = calcCol s
in Just ((srcLocLine (realSrcSpanEnd s), col), col)
_ -> Nothing
where calcCol s = if hasImports then srcLocCol (realSrcSpanStart s) - 1 else 0
where calcCol s = srcLocCol (realSrcSpanStart s) - 1

-- | Find the position one after the last file-header pragma
-- Defaults to zero if there are no pragmas in file
findNextPragmaPosition :: T.Text -> Maybe ((Int, Int), Int)
findNextPragmaPosition contents = Just ((lineNumber, 0), 0)
findNextPragmaPosition :: T.Text -> Int
findNextPragmaPosition contents = lineNumber
where
lineNumber = afterLangPragma . afterOptsGhc $ afterShebang
afterLangPragma = afterPragma "LANGUAGE" contents'
Expand Down
5 changes: 1 addition & 4 deletions ghcide/src/Development/IDE/Plugin/Completions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,7 @@ extendImportHandler' ideState ExtendImport {..}
it = case thingParent of
Nothing -> newThing
Just p -> p <> "(" <> newThing <> ")"
t <- liftMaybe $ snd <$> newImportToEdit
n
(astA ps)
(fromMaybe "" contents)
t <- liftMaybe $ snd <$> newImportToEdit n ps (fromMaybe "" contents)
return (nfp, WorkspaceEdit {_changes=Just (fromList [(doc,List [t])]), _documentChanges=Nothing, _changeAnnotations=Nothing})
| otherwise =
mzero
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Asdf
(f
, where')
-- hello
-- world

where
import Data.Int



f :: Int64 -> Int64
f = id'
where id' = id

g :: Int -> Int
g = id

where' :: Int -> Int
where' = id
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Asdf
(f
, where')
-- hello
-- world

where



f :: Int64 -> Int64
f = id'
where id' = id

g :: Int -> Int
g = id

where' :: Int -> Int
where' = id
Loading