-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE BlockArguments #-} module Development.IDE.Types.Diagnostics ( LSP.Diagnostic(..), FileDiagnostics, FileDiagnostic, Location(..), Range(..), LSP.DiagnosticSeverity(..), Position(..), DiagnosticStore, DiagnosticRelatedInformation(..), List(..), StoreItem(..), Uri(..), noLocation, noRange, noFilePath, ideErrorText, ideErrorPretty, errorDiag, ideTryIOException, showDiagnostics, showDiagnosticsColored, prettyDiagnosticStore, defDiagnostic, addDiagnostics, filterSeriousErrors, filePathToUri, getDiagnosticsFromStore ) where import Control.Exception import Data.Either.Combinators import Data.Maybe as Maybe import Data.Foldable import qualified Data.Map as Map import qualified Data.Text as T import Data.Text.Prettyprint.Doc.Syntax import qualified Text.PrettyPrint.Annotated.HughesPJClass as Pretty import Language.Haskell.LSP.Types as LSP ( DiagnosticSeverity(..) , Diagnostic(..) , filePathToUri , uriToFilePath , List(..) , DiagnosticRelatedInformation(..) , Uri(..) ) import Language.Haskell.LSP.Diagnostics import Development.IDE.Types.Location ideErrorText :: FilePath -> T.Text -> FileDiagnostic ideErrorText fp = errorDiag fp "Ide Error" ideErrorPretty :: Pretty.Pretty e => FilePath -> e -> FileDiagnostic ideErrorPretty fp = ideErrorText fp . T.pack . Pretty.prettyShow errorDiag :: FilePath -> T.Text -> T.Text -> FileDiagnostic errorDiag fp src msg = (fp, diagnostic noRange LSP.DsError src msg) -- | This is for compatibility with our old diagnostic type diagnostic :: Range -> LSP.DiagnosticSeverity -> T.Text -- ^ source -> T.Text -- ^ message -> LSP.Diagnostic diagnostic rng sev src msg = LSP.Diagnostic { _range = rng, _severity = Just sev, _code = Nothing, _source = Just src, _message = msg, _relatedInformation = Nothing } -- | Any optional field is instantiated to Nothing defDiagnostic :: Range -> T.Text -> -- ^ error message LSP.Diagnostic defDiagnostic _range _message = LSP.Diagnostic { _range , _message , _severity = Nothing , _code = Nothing , _source = Nothing , _relatedInformation = Nothing } filterSeriousErrors :: FilePath -> [LSP.Diagnostic] -> [LSP.Diagnostic] filterSeriousErrors fp = filter (maybe False hasSeriousErrors . LSP._relatedInformation) where hasSeriousErrors :: List DiagnosticRelatedInformation -> Bool hasSeriousErrors (List a) = any ((/=) uri . _uri . _location) a uri = LSP.filePathToUri fp addDiagnostics :: FilePath -> [LSP.Diagnostic] -> DiagnosticStore -> DiagnosticStore addDiagnostics fp diags ds = updateDiagnostics ds (LSP.filePathToUri fp) Nothing $ partitionBySource diags ideTryIOException :: FilePath -> IO a -> IO (Either FileDiagnostic a) ideTryIOException fp act = mapLeft (\(e :: IOException) -> ideErrorText fp $ T.pack $ show e) <$> try act -- | Human readable diagnostics for a specific file. -- -- This type packages a pretty printed, human readable error message -- along with the related source location so that we can display the error -- on either the console or in the IDE at the right source location. -- type FileDiagnostics = (FilePath, [Diagnostic]) type FileDiagnostic = (FilePath, Diagnostic) prettyRange :: Range -> Doc SyntaxClass prettyRange Range{..} = f _start <> "-" <> f _end where f Position{..} = pretty (_line+1) <> colon <> pretty _character stringParagraphs :: T.Text -> Doc a stringParagraphs = vcat . map (fillSep . map pretty . T.words) . T.lines showDiagnostics :: [FileDiagnostic] -> T.Text showDiagnostics = srenderPlain . prettyDiagnostics showDiagnosticsColored :: [FileDiagnostic] -> T.Text showDiagnosticsColored = srenderColored . prettyDiagnostics prettyDiagnostics :: [FileDiagnostic] -> Doc SyntaxClass prettyDiagnostics = vcat . map prettyDiagnostic prettyDiagnostic :: FileDiagnostic -> Doc SyntaxClass prettyDiagnostic (fp, LSP.Diagnostic{..}) = vcat [ slabel_ "File: " $ pretty fp , slabel_ "Range: " $ prettyRange _range , slabel_ "Source: " $ pretty _source , slabel_ "Severity:" $ pretty $ show sev , slabel_ "Message: " $ case sev of LSP.DsError -> annotate ErrorSC LSP.DsWarning -> annotate WarningSC LSP.DsInfo -> annotate InfoSC LSP.DsHint -> annotate HintSC $ stringParagraphs _message , slabel_ "Code:" $ pretty _code ] where sev = fromMaybe LSP.DsError _severity prettyDiagnosticStore :: DiagnosticStore -> Doc SyntaxClass prettyDiagnosticStore ds = vcat $ map (\(uri, diags) -> prettyFileDiagnostics (fromMaybe noFilePath $ uriToFilePath uri, diags)) $ Map.assocs $ Map.map getDiagnosticsFromStore ds prettyFileDiagnostics :: FileDiagnostics -> Doc SyntaxClass prettyFileDiagnostics (filePath, diags) = slabel_ "Compiler error in" $ vcat [ slabel_ "File:" $ pretty filePath , slabel_ "Errors:" $ vcat $ map (prettyDiagnostic . (filePath,)) diags ] getDiagnosticsFromStore :: StoreItem -> [Diagnostic] getDiagnosticsFromStore (StoreItem _ diags) = toList =<< Map.elems diags