content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  module Glean.Glass.SymbolSig
    (
      ToSymbolSignature(..),
      toSymbolSignatureText,
      -- testing
      renderTest
    ) where
  
  import Data.Text (Text, replicate, length)
  import Compat.Prettyprinter
  
  import qualified Glean.Haxl.Repos as Glean
  import qualified Glean.Schema.Code.Types as Code
  import qualified Glean.Schema.CodeLsif.Types as Lsif
  import qualified Glean.Schema.CodeScip.Types as Scip
  
  import Glean.Glass.Pretty.Angle as Angle ( prettyAngleSignature )
  import Glean.Glass.Pretty.Cxx as Cxx ( prettyCxxSignature, Qualified(..) )
  import Glean.Glass.Pretty.Flow as Flow ( prettyFlowSignature )
  import Glean.Glass.Pretty.Hack as Hack ( prettyHackSignature )
  import Glean.Glass.Pretty.Haskell as Haskell ( prettyHaskellSignature )
  import Glean.Glass.Pretty.LSIF as LSIF ( prettyLsifSignature )
  import Glean.Glass.Pretty.SCIP as SCIP ( prettyScipSignature )
  import Glean.Glass.Pretty.Python as Python ( prettyPythonSignature )
  import Glean.Glass.Pretty.Java as Java ( prettyJavaSignature )
  import Glean.Glass.Pretty.Fbthrift as Fbthrift ( prettyFbthriftSignature )
  import Glean.Glass.Types
      ( RepoName, SymbolId(..), TypeSymSpan(..), ByteSpan(..) )
  import qualified Data.Text.Lazy.Builder as TLB
  import qualified Data.Text.Lazy as TL
  
  -- This is equivalent to renderStrict but extract the symbol spans
  -- from the DocStream. Offsets are relative to the returned text.
  render :: SimpleDocStream (Maybe SymbolId) -> (Text, [TypeSymSpan])
  render stream =
      let (txt, xrefs) = render' stream 0 Nothing (mempty, []) in
      (TL.toStrict $ TLB.toLazyText txt, reverse xrefs)
    where
      -- curAnnot is the starting offset of the last symbol seen
      -- we know its length and generate the span when the annotation
      -- is popped
      render' stream curLen curAnnot res =
        let (txt, xrefs) = res in
        case stream of
          SFail -> error "render: the impossible has happened"
          SEmpty -> res
          SChar c rest ->
            render' rest (curLen + 1) curAnnot (txt <> TLB.singleton c, xrefs)
          SText _l t rest ->
            let res' = (txt <> TLB.fromText t, xrefs) in
            render' rest (curLen + Data.Text.length t) curAnnot res'
          SLine i rest ->
            let res' = (txt <>  TLB.singleton '\n' <>
                        TLB.fromText (Data.Text.replicate i " "), xrefs) in
            render' rest (curLen + 1 + i) curAnnot res'
          SAnnPush ann rest ->
            case ann of
              Just sym ->
                render' rest curLen (Just (sym, curLen)) res
              Nothing ->
                render' rest curLen Nothing res
          SAnnPop rest ->
            case curAnnot of
              Just (sym, xrefStart) ->
                let span = ByteSpan
                      (fromIntegral xrefStart)
                      (fromIntegral $ curLen - xrefStart) in
                let xref = TypeSymSpan sym span in
                render' rest curLen Nothing (txt, xref : xrefs)
              Nothing ->
                render' rest curLen Nothing res
  
  
  renderTest :: SimpleDocStream ann  -> Text
  renderTest stream =
    fst $ render $ reAnnotateS (const Nothing) stream
  
  sIGNATURE_LINEWRAP :: Int
  sIGNATURE_LINEWRAP = 120
  
  toSymbolSignatureText
    :: ToSymbolSignature a
    => a
    -> RepoName
    -> SymbolId
    -> Cxx.Qualified
    -> Glean.RepoHaxl u w (Maybe Text, [TypeSymSpan])
  toSymbolSignatureText entity repo sym qualified = do
    maybeDoc <- toSymbolSignature (LayoutOptions
        (AvailablePerLine sIGNATURE_LINEWRAP 1)) entity repo sym qualified
    case maybeDoc of
      Nothing -> return (Nothing, [])
      Just doc ->
        let (txt, xrefs) = render doc in
        return (Just txt, xrefs)
  
  -- signature of symbols
  -- The signature SimpleDocStream is annotated with SymbolId.
  -- The renderer can extract them with their span so as to
  -- generate type xrefs
  --
  class ToSymbolSignature a where
    toSymbolSignature
      :: LayoutOptions
      -> a
      -> RepoName -- ^ repo name of this entity
      -> SymbolId -- ^ symbol id of this entity if you need to link to it
      -> Cxx.Qualified
      -> Glean.RepoHaxl u w (Maybe (SimpleDocStream (Maybe SymbolId)))
  
  -- signature of symbols
  -- for supported languages (c++), qualified indicates whether to
  -- fully qualify the name of the code entity in question. Other
  -- symbols in the signature will always remain fully qualified.
  instance ToSymbolSignature Code.Entity where
    toSymbolSignature opts e repo sym qualified = case e of
      Code.Entity_angle x -> Angle.prettyAngleSignature opts x
      -- cxx pretty signatures
      Code.Entity_cxx x -> pure $ Cxx.prettyCxxSignature opts x qualified
      Code.Entity_pp{} -> pure Nothing
      -- hack pretty signatures
      Code.Entity_hack x -> Hack.prettyHackSignature opts repo sym x
      -- haskell pretty signatures
      Code.Entity_hs x -> Haskell.prettyHaskellSignature opts x
      -- Flow signatures direct from the DB
      Code.Entity_flow x -> Flow.prettyFlowSignature opts x
      -- python pretty signatures
      Code.Entity_python x -> Python.prettyPythonSignature opts repo sym x
      -- thrift pretty signatures
      Code.Entity_fbthrift x -> Fbthrift.prettyFbthriftSignature opts repo sym x
      -- python pretty signatures
      Code.Entity_java x -> Java.prettyJavaSignature opts repo sym x
  
      -- scip languages, just enumerate completely to stay total
      Code.Entity_scip e -> case e of
        Scip.Entity_rust x -> SCIP.prettyScipSignature opts x
        Scip.Entity_go x -> SCIP.prettyScipSignature opts x
        Scip.Entity_typescript x -> SCIP.prettyScipSignature opts x
        Scip.Entity_java x -> SCIP.prettyScipSignature opts x
        Scip.Entity_kotlin x -> SCIP.prettyScipSignature opts x
        Scip.Entity_swift x -> SCIP.prettyScipSignature opts x
        Scip.Entity_EMPTY -> pure Nothing
      -- lsif languages, just enumerate completely to stay total
      Code.Entity_lsif e -> case e of
        Lsif.Entity_erlang x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_fsharp x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_go x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_haskell x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_java x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_kotlin x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_ocaml x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_python x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_rust x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_scala x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_swift x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_typescript x -> LSIF.prettyLsifSignature opts x
        Lsif.Entity_EMPTY -> pure Nothing
      -- otherwise
      _ -> return Nothing
  