{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TupleSections         #-}
module Ide.Plugin.Cabal.Diagnostics
( errorDiagnostic
, warningDiagnostic
, positionFromCabalPosition
  -- * Re-exports
, FileDiagnostic
, Diagnostic(..)
)
where

import qualified Data.Text              as T
import           Development.IDE        (FileDiagnostic,
                                         ShowDiagnostic (ShowDiag))
import           Distribution.Fields    (showPError, showPWarning)
import qualified Ide.Plugin.Cabal.Parse as Lib
import           Ide.PluginUtils        (extendNextLine)
import           Language.LSP.Types     (Diagnostic (..),
                                         DiagnosticSeverity (..),
                                         DiagnosticSource, NormalizedFilePath,
                                         Position (Position), Range (Range),
                                         fromNormalizedFilePath)

-- | Produce a diagnostic from a Cabal parser error
errorDiagnostic :: NormalizedFilePath -> Lib.PError -> FileDiagnostic
errorDiagnostic :: NormalizedFilePath -> PError -> FileDiagnostic
errorDiagnostic NormalizedFilePath
fp err :: PError
err@(Lib.PError Position
pos String
_) =
  NormalizedFilePath
-> DiagnosticSource
-> DiagnosticSeverity
-> Range
-> DiagnosticSource
-> FileDiagnostic
mkDiag NormalizedFilePath
fp DiagnosticSource
"cabal" DiagnosticSeverity
DsError (Position -> Range
toBeginningOfNextLine Position
pos) DiagnosticSource
msg
  where
    msg :: DiagnosticSource
msg = String -> DiagnosticSource
T.pack forall a b. (a -> b) -> a -> b
$ String -> PError -> String
showPError (NormalizedFilePath -> String
fromNormalizedFilePath NormalizedFilePath
fp) PError
err

-- | Produce a diagnostic from a Cabal parser warning
warningDiagnostic :: NormalizedFilePath -> Lib.PWarning -> FileDiagnostic
warningDiagnostic :: NormalizedFilePath -> PWarning -> FileDiagnostic
warningDiagnostic NormalizedFilePath
fp warning :: PWarning
warning@(Lib.PWarning PWarnType
_ Position
pos String
_) =
  NormalizedFilePath
-> DiagnosticSource
-> DiagnosticSeverity
-> Range
-> DiagnosticSource
-> FileDiagnostic
mkDiag NormalizedFilePath
fp DiagnosticSource
"cabal" DiagnosticSeverity
DsWarning (Position -> Range
toBeginningOfNextLine Position
pos) DiagnosticSource
msg
  where
    msg :: DiagnosticSource
msg = String -> DiagnosticSource
T.pack forall a b. (a -> b) -> a -> b
$ String -> PWarning -> String
showPWarning (NormalizedFilePath -> String
fromNormalizedFilePath NormalizedFilePath
fp) PWarning
warning

-- | The Cabal parser does not output a _range_ for a warning/error,
-- only a single source code 'Lib.Position'.
-- We define the range to be _from_ this position
-- _to_ the first column of the next line.
toBeginningOfNextLine :: Lib.Position -> Range
toBeginningOfNextLine :: Position -> Range
toBeginningOfNextLine Position
cabalPos = Range -> Range
extendNextLine forall a b. (a -> b) -> a -> b
$ Position -> Position -> Range
Range Position
pos Position
pos
   where
    pos :: Position
pos = Position -> Position
positionFromCabalPosition Position
cabalPos

-- | Convert a 'Lib.Position' from Cabal to a 'Range' that LSP understands.
--
-- Prefer this function over hand-rolled unpacking/packing, since LSP is zero-based,
-- while Cabal is one-based.
--
-- >>> positionFromCabalPosition $ Lib.Position 1 1
-- Position 0 0
positionFromCabalPosition :: Lib.Position -> Position
positionFromCabalPosition :: Position -> Position
positionFromCabalPosition (Lib.Position Int
line Int
column) = UInt -> UInt -> Position
Position (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
line') (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
col')
  where
    -- LSP is zero-based, Cabal is one-based
    line' :: Int
line' = Int
lineforall a. Num a => a -> a -> a
-Int
1
    col' :: Int
col' = Int
columnforall a. Num a => a -> a -> a
-Int
1

-- | Create a 'FileDiagnostic'
mkDiag
  :: NormalizedFilePath
  -- ^ Cabal file path
  -> DiagnosticSource
  -- ^ Where does the diagnostic come from?
  -> DiagnosticSeverity
  -- ^ Severity
  -> Range
  -- ^ Which source code range should the editor highlight?
  -> T.Text
  -- ^ The message displayed by the editor
  -> FileDiagnostic
mkDiag :: NormalizedFilePath
-> DiagnosticSource
-> DiagnosticSeverity
-> Range
-> DiagnosticSource
-> FileDiagnostic
mkDiag NormalizedFilePath
file DiagnosticSource
diagSource DiagnosticSeverity
sev Range
loc DiagnosticSource
msg = (NormalizedFilePath
file, ShowDiagnostic
ShowDiag,)
    Diagnostic
    { $sel:_range:Diagnostic :: Range
_range    = Range
loc
    , $sel:_severity:Diagnostic :: Maybe DiagnosticSeverity
_severity = forall a. a -> Maybe a
Just DiagnosticSeverity
sev
    , $sel:_source:Diagnostic :: Maybe DiagnosticSource
_source   = forall a. a -> Maybe a
Just DiagnosticSource
diagSource
    , $sel:_message:Diagnostic :: DiagnosticSource
_message  = DiagnosticSource
msg
    , $sel:_code:Diagnostic :: Maybe (Int32 |? DiagnosticSource)
_code     = forall a. Maybe a
Nothing
    , $sel:_tags:Diagnostic :: Maybe (List DiagnosticTag)
_tags     = forall a. Maybe a
Nothing
    , $sel:_relatedInformation:Diagnostic :: Maybe (List DiagnosticRelatedInformation)
_relatedInformation = forall a. Maybe a
Nothing
    }