packages feed

hls-tactics-plugin-0.5.1.0: src/Ide/TreeTransform.hs

{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Ide.TreeTransform
  ( Graft, graft, transform, useAnnotatedSource
  ) where

import           BasicTypes (appPrec)
import           Control.Monad
import           Control.Monad.Trans.Class
import qualified Data.Text as T
import           Development.IDE.Core.RuleTypes
import           Development.IDE.Core.Rules
import           Development.IDE.Core.Shake
import           Development.IDE.GHC.Compat  hiding (parseExpr)
import           Development.IDE.Types.Location
import           Generics.SYB
import           Ide.PluginUtils
import           Language.Haskell.GHC.ExactPrint
import           Language.Haskell.GHC.ExactPrint.Parsers
import           Language.Haskell.LSP.Types
import           Language.Haskell.LSP.Types.Capabilities (ClientCapabilities)
import           Outputable
import           Retrie.ExactPrint hiding (parseExpr)


------------------------------------------------------------------------------
-- | Get the latest version of the annotated parse source.
useAnnotatedSource
    :: String
    -> IdeState
    -> NormalizedFilePath
    -> IO (Maybe (Annotated ParsedSource))
useAnnotatedSource herald state nfp = do
  pm <- runAction herald state $ use GetParsedModule nfp
  pure $ fmap fixAnns pm


------------------------------------------------------------------------------
-- | A transformation for grafting source trees together. Use the semigroup
-- instance to combine 'Graft's, and run them via 'transform'.
newtype Graft a = Graft
  { runGraft :: DynFlags -> a -> TransformT (Either String) a
  }

instance Semigroup (Graft a) where
  Graft a <> Graft b = Graft $ \dflags -> a dflags >=> b dflags

instance Monoid (Graft a) where
  mempty = Graft $ const pure


------------------------------------------------------------------------------
-- | Convert a 'Graft' into a 'WorkspaceEdit'.
transform
    :: DynFlags
    -> ClientCapabilities
    -> Uri
    -> Graft ParsedSource
    -> Annotated ParsedSource
    -> Either String WorkspaceEdit
transform dflags ccs uri f a = do
  let src = printA a
  a' <- transformA a $ runGraft f dflags
  let res = printA a'
  pure $ diffText ccs (uri, T.pack src) (T.pack res) IncludeDeletions


------------------------------------------------------------------------------
-- | Construct a 'Graft', replacing the node at the given 'SrcSpan' with the
-- given 'LHSExpr'. The node at that position must already be a 'LHsExpr', or
-- this is a no-op.
graft
    :: forall a
     . Data a
    => SrcSpan
    -> LHsExpr GhcPs
    -> Graft a
graft dst val = Graft $ \dflags a -> do
  (anns, val') <- annotate dflags $ parenthesize val
  modifyAnnsT $ mappend anns
  pure $ everywhere'
    ( mkT $
        \case
          (L src _ :: LHsExpr GhcPs) | src == dst -> val'
          l -> l
    ) a


------------------------------------------------------------------------------
-- | Dark magic I stole from retrie. No idea what it does.
fixAnns :: ParsedModule -> Annotated ParsedSource
fixAnns ParsedModule {..} =
  let ranns = relativiseApiAnns pm_parsed_source pm_annotations
   in unsafeMkA pm_parsed_source ranns 0


------------------------------------------------------------------------------
-- | Given an 'LHSExpr', compute its exactprint annotations.
annotate :: DynFlags -> LHsExpr GhcPs -> TransformT (Either String) (Anns, LHsExpr GhcPs)
annotate dflags expr = do
  uniq <- show <$> uniqueSrcSpanT
  let rendered = render dflags expr
  (anns, expr') <- lift $ either (Left . show) Right $ parseExpr dflags uniq rendered
  let anns' = setPrecedingLines expr' 0 1 anns
  pure (anns', expr')


------------------------------------------------------------------------------
-- | Print out something 'Outputable'.
render :: Outputable a => DynFlags -> a -> String
render dflags = showSDoc dflags . ppr


------------------------------------------------------------------------------
-- | Put parentheses around an expression if required.
parenthesize :: LHsExpr GhcPs -> LHsExpr GhcPs
parenthesize = parenthesizeHsExpr appPrec