packages feed

futhark-0.28.2: src/Futhark/LSP/Diagnostic.hs

-- | Handling of diagnostics in the language server - things like
-- warnings and errors.
--
-- Every compilation produces the diagnostics for the entire program,
-- so publishing them replaces everything previously published.  We
-- do not use the incremental diagnostic store of the lsp library, as
-- its notion of replacement depends on document versions recorded
-- at earlier publications.
module Futhark.LSP.Diagnostic
  ( Diagnostics,
    warningDiagnostics,
    errorDiagnostics,
    showUnlocatedErrors,
    publishDiagnostics,
  )
where

import Colog.Core (Severity (Debug), (<&))
import Control.Lens ((^.))
import Data.Foldable (for_)
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as M
import Data.Set qualified as S
import Data.Text qualified as T
import Futhark.Compiler.Program (ProgError (..))
import Futhark.LSP.Tool (logWithSeverity, posToUri, rangeFromLoc)
import Futhark.Util (showText)
import Futhark.Util.Loc (Loc (..))
import Futhark.Util.Pretty (Doc, docText)
import Language.LSP.Protocol.Message (SMethod (SMethod_TextDocumentPublishDiagnostics, SMethod_WindowShowMessage))
import Language.LSP.Protocol.Types
import Language.LSP.Server (LspT, sendNotification)
import Language.LSP.VFS (VFS, VirtualFileEntry (..), vfsMap, virtualFileVersion)

-- | The diagnostics of every file that has any.
type Diagnostics = M.Map NormalizedUri [Diagnostic]

mkDiagnostic :: Range -> DiagnosticSeverity -> T.Text -> Diagnostic
mkDiagnostic range severity msg =
  Diagnostic
    range
    (Just severity)
    Nothing
    Nothing
    diagnosticSource
    msg
    Nothing
    Nothing
    Nothing

-- | A diagnostic for the file containing the given location, if it
-- has one.
diagnosticAt :: Loc -> DiagnosticSeverity -> Doc a -> Diagnostics
diagnosticAt NoLoc _ _ = mempty
diagnosticAt loc@(Loc pos _) severity msg =
  M.singleton
    (toNormalizedUri $ posToUri pos)
    [mkDiagnostic (rangeFromLoc loc) severity (docText msg)]

-- | Diagnostics for the warnings of a successful compilation.
warningDiagnostics :: [(Loc, Doc a)] -> Diagnostics
warningDiagnostics = M.unionsWith (++) . map onWarn
  where
    onWarn (loc, msg) = diagnosticAt loc DiagnosticSeverity_Warning msg

-- | Diagnostics for the problems of a failed compilation.
errorDiagnostics :: NE.NonEmpty ProgError -> Diagnostics
errorDiagnostics = M.unionsWith (++) . map onDiag . NE.toList
  where
    onDiag (ProgError loc msg) = diagnosticAt loc DiagnosticSeverity_Error msg
    onDiag (ProgWarning loc msg) = diagnosticAt loc DiagnosticSeverity_Warning msg

-- | Errors without a location cannot be attached to a file, so they
-- are shown as messages.
showUnlocatedErrors :: NE.NonEmpty ProgError -> LspT () IO ()
showUnlocatedErrors = mapM_ showUnlocated
  where
    showUnlocated (ProgError NoLoc msg) = showMessage MessageType_Error msg
    showUnlocated (ProgWarning NoLoc msg) = showMessage MessageType_Warning msg
    showUnlocated _ = pure ()

    showMessage message_type msg =
      sendNotification SMethod_WindowShowMessage $
        ShowMessageParams message_type (docText msg)

-- | Publish the diagnostics of a compilation of the given VFS, for
-- every open file and every file in the given set of files that were
-- previously published for.  Returns the files that now have
-- diagnostics.  A client may discard a publication for a document it
-- has since changed, so open files are published for even when they
-- have no diagnostics.
publishDiagnostics :: VFS -> S.Set NormalizedUri -> Diagnostics -> LspT () IO (S.Set NormalizedUri)
publishDiagnostics vfs previous diagnostics = do
  let versions = M.mapMaybe openVersion $ vfs ^. vfsMap
      targets = previous <> M.keysSet versions
  for_ (M.toList $ diagnostics <> M.fromSet (const []) targets) $ \(uri, diags) -> do
    let version = M.lookup uri versions
    logWithSeverity Debug
      <& ("Publishing diagnostics for " <> showText uri <> " Version: " <> showText version)
    sendNotification SMethod_TextDocumentPublishDiagnostics $
      PublishDiagnosticsParams (fromNormalizedUri uri) version (take maxDiagnostic diags)
  pure $ M.keysSet diagnostics
  where
    openVersion (Open file) = Just $ virtualFileVersion file
    openVersion (Closed _) = Nothing

-- | The maximum number of diagnostics to report for a file.
maxDiagnostic :: Int
maxDiagnostic = 100

-- | The source of the diagnostics.  (That is, the Futhark compiler,
-- but apparently the client must be told such things...)
diagnosticSource :: Maybe T.Text
diagnosticSource = Just "futhark"