@@ -3,137 +3,234 @@
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE DuplicateRecordFields #-}
7+{-# LANGUAGE BlockArguments #-}
6module Development.IDE.Types.Diagnostics (
8module Development.IDE.Types.Diagnostics (
7− Diagnostic(..),
9+ LSP.Diagnostic(..),
8− FileDiagnostics(..),
10+ FileDiagnostics,
9 Location(..),
11 Location(..),
10 Range(..),
12 Range(..),
11− Severity(..),
13+ LSP.DiagnosticSeverity(..),
12 Position(..),
14 Position(..),
16+ DiagnosticRelatedInformation(..),
13 noLocation,
20 noLocation,
15 ideErrorText,
23 ideErrorText,
16 ideErrorPretty,
24 ideErrorPretty,
17 errorDiag,
25 errorDiag,
18 ideTryIOException,
26 ideTryIOException,
19 prettyFileDiagnostics,
27 prettyFileDiagnostics,
20− prettyDiagnostic
28+ prettyDiagnostic,
29+ prettyDiagnosticStore,
36+ getDiagnosticsFromStore
23−import Control.DeepSeq
24import Control.Exception
39import Control.Exception
25−import Data.Aeson (FromJSON, ToJSON)
40+import Control.Lens (Lens', lens, set, view)
26import Data.Either.Combinators
41import Data.Either.Combinators
27−import Data.List.Extra
42+import Data.Maybe as Maybe
44+import qualified Data.Map as Map
28import qualified Data.Text as T
45import qualified Data.Text as T
29import Data.Text.Prettyprint.Doc.Syntax
46import Data.Text.Prettyprint.Doc.Syntax
30−import GHC.Generics
47+import Data.String (IsString(..))
31−import qualified Network.URI.Encode
32import qualified Text.PrettyPrint.Annotated.HughesPJClass as Pretty
48import qualified Text.PrettyPrint.Annotated.HughesPJClass as Pretty
49+import Language.Haskell.LSP.Types as LSP (
50+ DiagnosticSeverity(..)
55+ , DiagnosticRelatedInformation(..)
58+import Language.Haskell.LSP.Diagnostics
34import Development.IDE.Types.Location
60import Development.IDE.Types.Location
36−ideErrorText :: FilePath -> T.Text -> Diagnostic
62+ideErrorText :: FilePath -> T.Text -> LSP.Diagnostic
37−ideErrorText absFile = errorDiag absFile "Ide Error"
63+ideErrorText fp = errorDiag fp "Ide Error"
39−ideErrorPretty :: Pretty.Pretty e => FilePath -> e -> Diagnostic
65+ideErrorPretty :: Pretty.Pretty e => FilePath -> e -> LSP.Diagnostic
40−ideErrorPretty absFile = ideErrorText absFile . T.pack . Pretty.prettyShow
66+ideErrorPretty fp = ideErrorText fp . T.pack . Pretty.prettyShow
42−errorDiag :: FilePath -> T.Text -> T.Text -> Diagnostic
68+errorDiag :: FilePath -> T.Text -> T.Text -> LSP.Diagnostic
43−errorDiag fp src msg =
69+errorDiag fp src =
44− Diagnostic
70+ set dFilePath (Just fp) . diagnostic noRange LSP.DsError src
46− , dRange = noRange
72+-- | This is for compatibility with our old diagnostic type
47− , dSeverity = Error
73+diagnostic :: Range
48− , dSource = src
74+ -> LSP.DiagnosticSeverity
49− , dMessage = msg
75+ -> T.Text -- ^ source
76+ -> T.Text -- ^ message
78+diagnostic rng sev src msg
81+ _severity = Just sev,
85+ _relatedInformation = Nothing
88+-- | Any optional field is instantiated to Nothing
91+ T.Text -> -- ^ error message
93+defDiagnostic _range _message = LSP.Diagnostic {
96+ , _severity = Nothing
99+ , _relatedInformation = Nothing
102+-- | setLocation but with no range information
104+ Lens' LSP.Diagnostic (Maybe FilePath)
105+dFilePath = lens g s where
106+ g :: LSP.Diagnostic -> Maybe FilePath
107+ g d = (uriToFilePath . _uri) =<< view dLocation d
108+ s :: LSP.Diagnostic -> Maybe FilePath -> LSP.Diagnostic
109+ s d@Diagnostic{..} fp = set dLocation
110+ (Location <$> (filePathToUri <$> fp) <*> pure _range) d
112+-- | This adds location information to the diagnostics but this is only used in
113+-- the case of serious errors to give some context to what went wrong
115+ Lens' LSP.Diagnostic (Maybe Location)
116+dLocation = lens g s where
117+ s :: LSP.Diagnostic -> Maybe Location -> LSP.Diagnostic
120+ d {LSP._range=(_range :: Location -> Range) loc
121+ , LSP._relatedInformation = Just $ LSP.List [DiagnosticRelatedInformation loc "dLocation: Unknown error"]}
122+ Nothing -> d {LSP._range = noRange, LSP._relatedInformation = Nothing}
123+ g :: LSP.Diagnostic -> Maybe Location
124+ g Diagnostic{..} = case _relatedInformation of
125+ Just (List [DiagnosticRelatedInformation loc _]) -> Just loc
126+ Just (List xs) -> error $ "Diagnostic created, expected 1 related information but got" <> show xs
129+filterSeriousErrors ::
133+filterSeriousErrors fp =
134+ filter (maybe False hasSeriousErrors . LSP._relatedInformation)
136+ hasSeriousErrors :: List DiagnosticRelatedInformation -> Bool
137+ hasSeriousErrors (List a) = any ((/=) uri . _uri . _location) a
138+ uri = LSP.filePathToUri fp
143+ DiagnosticStore -> DiagnosticStore
144+addDiagnostics fp diags ds =
147+ (LSP.filePathToUri fp)
149+ partitionBySource diags
52−ideTryIOException :: FilePath -> IO a -> IO (Either Diagnostic a)
151+ideTryIOException :: FilePath -> IO a -> IO (Either LSP.Diagnostic a)
53ideTryIOException fp act =
152ideTryIOException fp act =
54 mapLeft (\(e :: IOException) -> ideErrorText fp $ T.pack $ show e) <$> try act
153 mapLeft (\(e :: IOException) -> ideErrorText fp $ T.pack $ show e) <$> try act
56−data Diagnostic = Diagnostic
57− { dFilePath :: !FilePath
58− -- ^ Specific file that the diagnostic refers to.
60− -- ^ The range to which the diagnostic applies.
61− , dSeverity :: !Severity
62− -- ^ The severity of the diagnostic, such as 'SError' or 'SWarning'.
64− -- ^ Human-readable description for the source of the diagnostic,
65− -- for example 'parser'.
66− , dMessage :: !T.Text
67− -- ^ The diagnostic's message.
69− deriving (Eq, Ord, Show, Generic)
71−instance NFData Diagnostic
73−-- | The diagnostic severity.
76− deriving (Eq, Ord, Show, Generic)
78−instance NFData Severity
80-- | Human readable diagnostics for a specific file.
155-- | Human readable diagnostics for a specific file.
82-- This type packages a pretty printed, human readable error message
157-- This type packages a pretty printed, human readable error message
83-- along with the related source location so that we can display the error
158-- along with the related source location so that we can display the error
84-- on either the console or in the IDE at the right source location.
159-- on either the console or in the IDE at the right source location.
86−data FileDiagnostics = FileDiagnostics
161+type FileDiagnostics = (Uri, [Diagnostic])
87− { fdFilePath :: !FilePath
88− -- ^ Path of the module that we were trying to process.
89− -- In a multi-module program this is the file that we started
90− -- trying to compile, not necessarily the one in which we found the
91− -- reported errors or warnings.
92− , fdDiagnostics :: ![Diagnostic]
93− -- ^ Diagnostics for the desired module,
94− -- as well as any transitively imported modules.
96− deriving (Eq, Ord, Show, Generic)
98−instance FromJSON Diagnostic
99−instance ToJSON Diagnostic
101−instance FromJSON Severity
102−instance ToJSON Severity
104−instance FromJSON FileDiagnostics
105−instance ToJSON FileDiagnostics
107−prettyFileDiagnostics :: FileDiagnostics -> Doc SyntaxClass
163+prettyRange :: Range -> Doc SyntaxClass
108−prettyFileDiagnostics (FileDiagnostics filePath diagnostics) =
164+prettyRange Range{..} =
109− label_ "Compiler error in" $ vcat
165+ label_ "Range" $ vcat
110− [ label_ "File:" $ pretty filePath
166+ [ label_ "Start:" $ prettyPosition _start
111− , label_ "Errors:" $ vcat $ map prettyDiagnostic $ nubOrd diagnostics
167+ , label_ "End: " $ prettyPosition _end
170+prettyPosition :: Position -> Doc SyntaxClass
171+prettyPosition Position{..} = label_ "Position" $ vcat
172+ [ label_ "Line:" $ pretty _line
173+ , label_ "Character:" $ pretty _character
114stringParagraphs :: T.Text -> Doc a
176stringParagraphs :: T.Text -> Doc a
115stringParagraphs = vcat . map (fillSep . map pretty . T.words) . T.lines
177stringParagraphs = vcat . map (fillSep . map pretty . T.words) . T.lines
117−prettyDiagnostic :: Diagnostic -> Doc SyntaxClass
179+prettyDiagnostic :: LSP.Diagnostic -> Doc SyntaxClass
118−prettyDiagnostic (Diagnostic filePath range severity source msg) =
180+prettyDiagnostic LSP.Diagnostic{..} =
120− [ label_ "File: " $ pretty filePath
121− , label_ "Range: "
182+ [label_ "Range: "
122− $ annotate (LinkSC uri title)
183+ $ prettyRange _range
124− , label_ "Source: " $ pretty source
184+ , label_ "Source: " $ pretty _source
125− , label_ "Severity:" $ pretty $ show severity
185+ , label_ "Severity:" $ pretty $ show sev
126 , label_ "Message: "
186 , label_ "Message: "
127− $ case severity of
187+ $ case sev of
128− Error -> annotate ErrorSC
188+ LSP.DsError -> annotate ErrorSC
129− Warning -> annotate WarningSC
189+ LSP.DsWarning -> annotate WarningSC
190+ LSP.DsInfo -> annotate InfoSC
191+ LSP.DsHint -> annotate HintSC
130− $ stringParagraphs msg
192+ $ stringParagraphs _message
193+ , label_ "Code:" $ pretty _code
133− -- FIXME(JM): Move uri construction to DA.Pretty?
196+ sev = fromMaybe LSP.DsError _severity
134− Position sline _ = rangeStart range
197+
135− Position eline _ = rangeEnd range
198+prettyDiagnosticStore :: DiagnosticStore -> Doc SyntaxClass
136− uri = "command:daml.revealLocation?"
199+prettyDiagnosticStore ds =
137− <> Network.URI.Encode.encodeText ("[\"file://" <> T.pack filePath <> "\","
200+ vcat $
138− <> T.pack (show sline) <> ", " <> T.pack (show eline) <> "]")
201+ map prettyFileDiagnostics $
139− title = T.pack filePath
202+ Map.assocs $
203+ Map.map getDiagnosticsFromStore ds
205+prettyFileDiagnostics :: FileDiagnostics -> Doc SyntaxClass
206+prettyFileDiagnostics (uri, diags) =
207+ label_ "Compiler error in" $ vcat
208+ [ label_ "File:" $ pretty filePath
209+ , label_ "Errors:" $ vcat $ map prettyDiagnostic diags
212+ -- prettyFileDiags :: (FilePath, [(T.Text, [LSP.Diagnostic])]) -> Doc SyntaxClass
213+ -- prettyFileDiags (fp,stages) =
214+ -- label_ ("File: "<>fp) $ vcat $ map prettyStage stages
216+ -- prettyStage :: (T.Text, [LSP.Diagnostic]) -> Doc SyntaxClass
217+ -- prettyStage (stage,diags) =
218+ -- label_ ("Stage: "<>T.unpack stage) $ vcat $ map prettyDiagnostic diags
220+ filePath :: FilePath
221+ filePath = fromMaybe dontKnow $ uriToFilePath uri
224+ -- (FilePath, [(T.Text, [LSP.Diagnostic])])
225+ -- -- ^ Source File, Stage Source, Diags
226+ -- storeContents = (fromMaybe dontKnow $ uriToFilePath uri, getDiags diags)
228+ dontKnow :: IsString s => s
229+ dontKnow = "<unknown>"
231+ -- getDiags :: DiagnosticsBySource -> [(T.Text, [LSP.Diagnostic])]
232+ -- getDiags = map (\(ds, diag) -> (fromMaybe dontKnow ds, toList diag)) . Map.assocs
234+getDiagnosticsFromStore :: StoreItem -> [Diagnostic]
235+getDiagnosticsFromStore (StoreItem _ diags) =
236+ toList =<< Map.elems diags