Add pre-commit hook for cleaning up mixed-line endings (#2679)

* Update pre-commit hook to include changing line endings

* Fix non-lf lines

* Check pre-commit excludes files

* Revert "Check pre-commit excludes files"

This reverts commit 7b9670f863.

* Actually add the exclude to contributing docs

* Fix merge failure with previous patch

* Inadvertently overwrote merge

* Add LF option for stylish-haskell and pre-commit file

Co-authored-by: Anton Latukha <anton.latukha@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Nick Suchecki 2022-02-04 09:50:18 -05:00 committed by GitHub
parent 9faa179017
commit 411db02883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 3232 additions and 3189 deletions

2
.gitignore vendored
View File

@ -32,7 +32,7 @@ test/testdata/**/hie.yaml
.shake/
# pre-commit-hook.nix
.pre-commit-config.yaml
#.pre-commit-config.yaml
# direnv
/.direnv/

32
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,32 @@
{
"repos": [
{
"hooks": [
{
"entry": "stylish-haskell --inplace",
"exclude": "(^Setup.hs$|test/testdata/.*$|test/data/.*$|test/manual/lhs/.*$|^hie-compat/.*$|^plugins/hls-tactics-plugin/.*$|^ghcide/src/Development/IDE/GHC/Compat.hs$|^ghcide/src/Development/IDE/Plugin/CodeAction/ExactPrint.hs$|^ghcide/src/Development/IDE/GHC/Compat/Core.hs$|^ghcide/src/Development/IDE/Spans/Pragmas.hs$|^ghcide/src/Development/IDE/LSP/Outline.hs$|^plugins/hls-splice-plugin/src/Ide/Plugin/Splice.hs$|^ghcide/test/exe/Main.hs$|ghcide/src/Development/IDE/Core/Rules.hs|^hls-test-utils/src/Test/Hls/Util.hs$)",
"files": "\\.l?hs$",
"id": "stylish-haskell",
"language": "system",
"name": "stylish-haskell",
"pass_filenames": true,
"types": [
"file"
]
}
],
"repo": "local"
},
{
"repo": "https://github.com/pre-commit/pre-commit-hooks",
"rev": "v4.1.0",
"hooks": [
{
"id": "mixed-line-ending",
"args": ["--fix", "lf"],
"exclude": "test/testdata/.*CRLF*.hs$"
}
]
}
]
}

View File

@ -53,7 +53,7 @@ steps:
columns: 80
newline: native
newline: lf
language_extensions:
- BangPatterns

View File

@ -199,6 +199,17 @@ If you don't want to use [nix](https://nixos.org/guides/install-nix.html), you c
}
],
"repo": "local"
},
{
"repo": "https://github.com/pre-commit/pre-commit-hooks",
"rev": "v4.1.0",
"hooks": [
{
"id": "mixed-line-ending",
"args": ["--fix", "lf"],
"exclude": "test/testdata/.*CRLF*.hs$"
}
]
}
]
}

View File

@ -1,162 +1,162 @@
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
module Development.IDE.Core.UseStale
( Age(..)
, Tracked
, unTrack
, PositionMap
, TrackedStale (..)
, untrackedStaleValue
, unsafeMkStale
, unsafeMkCurrent
, unsafeCopyAge
, MapAge (..)
, dualPositionMap
, useWithStale
, useWithStale_
) where
import Control.Arrow
import Control.Category (Category)
import qualified Control.Category as C
import Control.DeepSeq (NFData)
import Data.Aeson
import Data.Coerce (coerce)
import Data.Functor ((<&>))
import Data.Functor.Identity (Identity (Identity))
import Data.Kind (Type)
import Data.String (fromString)
import Development.IDE (Action, IdeRule,
NormalizedFilePath,
Range,
rangeToRealSrcSpan,
realSrcSpanToRange)
import qualified Development.IDE.Core.PositionMapping as P
import qualified Development.IDE.Core.Shake as IDE
import Development.IDE.GHC.Compat (RealSrcSpan, srcSpanFile)
import Development.IDE.GHC.Compat.Util (unpackFS)
------------------------------------------------------------------------------
-- | A data kind for 'Tracked'.
data Age = Current | Stale Type
------------------------------------------------------------------------------
-- | Some value, tagged with its age. All 'Current' ages are considered to be
-- the same thing, but 'Stale' values are protected by an untouchable variable
-- to ensure they can't be unified.
newtype Tracked (age :: Age) a = UnsafeTracked
{ unTrack :: a
}
deriving stock (Functor, Foldable, Traversable)
deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData)
deriving (Applicative, Monad) via Identity
------------------------------------------------------------------------------
-- | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values
-- change. Use the 'Category' instance to compose 'PositionMapping's in order
-- to transform between values of different stale ages.
newtype PositionMap (from :: Age) (to :: Age) = PositionMap
{ _getPositionMapping :: P.PositionMapping
}
instance Category PositionMap where
id = coerce P.zeroMapping
(.) = coerce P.composeDelta
------------------------------------------------------------------------------
-- | Get a 'PositionMap' that runs in the opposite direction.
dualPositionMap :: PositionMap from to -> PositionMap to from
dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) =
PositionMap $ P.PositionMapping $ P.PositionDelta to from
------------------------------------------------------------------------------
-- | A pair containing a @'Tracked' 'Stale'@ value, as well as
-- a 'PositionMapping' that will fast-forward values to the current age.
data TrackedStale a where
TrackedStale
:: Tracked (Stale s) a
-> PositionMap (Stale s) Current
-> TrackedStale a
instance Functor TrackedStale where
fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm
untrackedStaleValue :: TrackedStale a -> a
untrackedStaleValue (TrackedStale ta _) = coerce ta
------------------------------------------------------------------------------
-- | A class for which 'Tracked' values can be run across a 'PositionMapping'
-- to change their ages.
class MapAge a where
{-# MINIMAL mapAgeFrom | mapAgeTo #-}
mapAgeFrom :: PositionMap from to -> Tracked to a -> Maybe (Tracked from a)
mapAgeFrom = mapAgeTo . dualPositionMap
mapAgeTo :: PositionMap from to -> Tracked from a -> Maybe (Tracked to a)
mapAgeTo = mapAgeFrom . dualPositionMap
instance MapAge Range where
mapAgeFrom = coerce P.fromCurrentRange
mapAgeTo = coerce P.toCurrentRange
instance MapAge RealSrcSpan where
mapAgeFrom =
invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs))
(srcSpanFile &&& realSrcSpanToRange)
. mapAgeFrom
------------------------------------------------------------------------------
-- | Helper function for deriving 'MapAge' for values in terms of other
-- instances.
invMapAge
:: (c -> a -> b)
-> (b -> (c, a))
-> (Tracked from a -> Maybe (Tracked to a))
-> Tracked from b
-> Maybe (Tracked to b)
invMapAge to from f t =
let (c, t') = unTrack $ fmap from t
in fmap (fmap $ to c) $ f $ UnsafeTracked t'
unsafeMkCurrent :: age -> Tracked 'Current age
unsafeMkCurrent = coerce
unsafeMkStale :: age -> Tracked (Stale s) age
unsafeMkStale = coerce
unsafeCopyAge :: Tracked age a -> b -> Tracked age b
unsafeCopyAge _ = coerce
-- | Request a Rule result, it not available return the last computed result, if any, which may be stale
useWithStale :: IdeRule k v
=> k -> NormalizedFilePath -> Action (Maybe (TrackedStale v))
useWithStale key file = do
x <- IDE.useWithStale key file
pure $ x <&> \(v, pm) ->
TrackedStale (coerce v) (coerce pm)
-- | Request a Rule result, it not available return the last computed result which may be stale.
-- Errors out if none available.
useWithStale_ :: IdeRule k v
=> k -> NormalizedFilePath -> Action (TrackedStale v)
useWithStale_ key file = do
(v, pm) <- IDE.useWithStale_ key file
pure $ TrackedStale (coerce v) (coerce pm)
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
module Development.IDE.Core.UseStale
( Age(..)
, Tracked
, unTrack
, PositionMap
, TrackedStale (..)
, untrackedStaleValue
, unsafeMkStale
, unsafeMkCurrent
, unsafeCopyAge
, MapAge (..)
, dualPositionMap
, useWithStale
, useWithStale_
) where
import Control.Arrow
import Control.Category (Category)
import qualified Control.Category as C
import Control.DeepSeq (NFData)
import Data.Aeson
import Data.Coerce (coerce)
import Data.Functor ((<&>))
import Data.Functor.Identity (Identity (Identity))
import Data.Kind (Type)
import Data.String (fromString)
import Development.IDE (Action, IdeRule,
NormalizedFilePath,
Range,
rangeToRealSrcSpan,
realSrcSpanToRange)
import qualified Development.IDE.Core.PositionMapping as P
import qualified Development.IDE.Core.Shake as IDE
import Development.IDE.GHC.Compat (RealSrcSpan, srcSpanFile)
import Development.IDE.GHC.Compat.Util (unpackFS)
------------------------------------------------------------------------------
-- | A data kind for 'Tracked'.
data Age = Current | Stale Type
------------------------------------------------------------------------------
-- | Some value, tagged with its age. All 'Current' ages are considered to be
-- the same thing, but 'Stale' values are protected by an untouchable variable
-- to ensure they can't be unified.
newtype Tracked (age :: Age) a = UnsafeTracked
{ unTrack :: a
}
deriving stock (Functor, Foldable, Traversable)
deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData)
deriving (Applicative, Monad) via Identity
------------------------------------------------------------------------------
-- | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values
-- change. Use the 'Category' instance to compose 'PositionMapping's in order
-- to transform between values of different stale ages.
newtype PositionMap (from :: Age) (to :: Age) = PositionMap
{ _getPositionMapping :: P.PositionMapping
}
instance Category PositionMap where
id = coerce P.zeroMapping
(.) = coerce P.composeDelta
------------------------------------------------------------------------------
-- | Get a 'PositionMap' that runs in the opposite direction.
dualPositionMap :: PositionMap from to -> PositionMap to from
dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) =
PositionMap $ P.PositionMapping $ P.PositionDelta to from
------------------------------------------------------------------------------
-- | A pair containing a @'Tracked' 'Stale'@ value, as well as
-- a 'PositionMapping' that will fast-forward values to the current age.
data TrackedStale a where
TrackedStale
:: Tracked (Stale s) a
-> PositionMap (Stale s) Current
-> TrackedStale a
instance Functor TrackedStale where
fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm
untrackedStaleValue :: TrackedStale a -> a
untrackedStaleValue (TrackedStale ta _) = coerce ta
------------------------------------------------------------------------------
-- | A class for which 'Tracked' values can be run across a 'PositionMapping'
-- to change their ages.
class MapAge a where
{-# MINIMAL mapAgeFrom | mapAgeTo #-}
mapAgeFrom :: PositionMap from to -> Tracked to a -> Maybe (Tracked from a)
mapAgeFrom = mapAgeTo . dualPositionMap
mapAgeTo :: PositionMap from to -> Tracked from a -> Maybe (Tracked to a)
mapAgeTo = mapAgeFrom . dualPositionMap
instance MapAge Range where
mapAgeFrom = coerce P.fromCurrentRange
mapAgeTo = coerce P.toCurrentRange
instance MapAge RealSrcSpan where
mapAgeFrom =
invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs))
(srcSpanFile &&& realSrcSpanToRange)
. mapAgeFrom
------------------------------------------------------------------------------
-- | Helper function for deriving 'MapAge' for values in terms of other
-- instances.
invMapAge
:: (c -> a -> b)
-> (b -> (c, a))
-> (Tracked from a -> Maybe (Tracked to a))
-> Tracked from b
-> Maybe (Tracked to b)
invMapAge to from f t =
let (c, t') = unTrack $ fmap from t
in fmap (fmap $ to c) $ f $ UnsafeTracked t'
unsafeMkCurrent :: age -> Tracked 'Current age
unsafeMkCurrent = coerce
unsafeMkStale :: age -> Tracked (Stale s) age
unsafeMkStale = coerce
unsafeCopyAge :: Tracked age a -> b -> Tracked age b
unsafeCopyAge _ = coerce
-- | Request a Rule result, it not available return the last computed result, if any, which may be stale
useWithStale :: IdeRule k v
=> k -> NormalizedFilePath -> Action (Maybe (TrackedStale v))
useWithStale key file = do
x <- IDE.useWithStale key file
pure $ x <&> \(v, pm) ->
TrackedStale (coerce v) (coerce pm)
-- | Request a Rule result, it not available return the last computed result which may be stale.
-- Errors out if none available.
useWithStale_ :: IdeRule k v
=> k -> NormalizedFilePath -> Action (TrackedStale v)
useWithStale_ key file = do
(v, pm) <- IDE.useWithStale_ key file
pure $ TrackedStale (coerce v) (coerce pm)

View File

@ -1,63 +1,63 @@
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
{-# LANGUAGE NondecreasingIndentation #-}
-----------------------------------------------------------------------------
--
-- GHC Driver
--
-- (c) The University of Glasgow 2005
--
-----------------------------------------------------------------------------
module Development.IDE.GHC.CPP(doCpp, addOptP)
where
import Development.IDE.GHC.Compat as Compat
import GHC
#if !MIN_VERSION_ghc(8,10,0)
import qualified Development.IDE.GHC.Compat.CPP as CPP
#else
import Development.IDE.GHC.Compat.Util
#endif
#if MIN_VERSION_ghc(9,0,0)
import qualified GHC.Driver.Pipeline as Pipeline
import GHC.Settings
#else
#if MIN_VERSION_ghc (8,10,0)
import qualified DriverPipeline as Pipeline
import ToolSettings
#else
import DynFlags
#endif
#endif
addOptP :: String -> DynFlags -> DynFlags
#if MIN_VERSION_ghc (8,10,0)
addOptP f = alterToolSettings $ \s -> s
{ toolSettings_opt_P = f : toolSettings_opt_P s
, toolSettings_opt_P_fingerprint = fingerprintStrings (f : toolSettings_opt_P s)
}
where
fingerprintStrings ss = fingerprintFingerprints $ map fingerprintString ss
alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) }
#else
addOptP opt = onSettings (onOptP (opt:))
where
onSettings f x = x{settings = f $ settings x}
onOptP f x = x{sOpt_P = f $ sOpt_P x}
#endif
doCpp :: HscEnv -> Bool -> FilePath -> FilePath -> IO ()
doCpp env raw input_fn output_fn =
#if MIN_VERSION_ghc (9,2,0)
Pipeline.doCpp (hsc_logger env) (hsc_tmpfs env) (hsc_dflags env) (hsc_unit_env env) raw input_fn output_fn
#elif MIN_VERSION_ghc (8,10,0)
Pipeline.doCpp (hsc_dflags env) raw input_fn output_fn
#else
CPP.doCpp (hsc_dflags env) raw input_fn output_fn
#endif
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
{-# LANGUAGE NondecreasingIndentation #-}
-----------------------------------------------------------------------------
--
-- GHC Driver
--
-- (c) The University of Glasgow 2005
--
-----------------------------------------------------------------------------
module Development.IDE.GHC.CPP(doCpp, addOptP)
where
import Development.IDE.GHC.Compat as Compat
import GHC
#if !MIN_VERSION_ghc(8,10,0)
import qualified Development.IDE.GHC.Compat.CPP as CPP
#else
import Development.IDE.GHC.Compat.Util
#endif
#if MIN_VERSION_ghc(9,0,0)
import qualified GHC.Driver.Pipeline as Pipeline
import GHC.Settings
#else
#if MIN_VERSION_ghc (8,10,0)
import qualified DriverPipeline as Pipeline
import ToolSettings
#else
import DynFlags
#endif
#endif
addOptP :: String -> DynFlags -> DynFlags
#if MIN_VERSION_ghc (8,10,0)
addOptP f = alterToolSettings $ \s -> s
{ toolSettings_opt_P = f : toolSettings_opt_P s
, toolSettings_opt_P_fingerprint = fingerprintStrings (f : toolSettings_opt_P s)
}
where
fingerprintStrings ss = fingerprintFingerprints $ map fingerprintString ss
alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) }
#else
addOptP opt = onSettings (onOptP (opt:))
where
onSettings f x = x{settings = f $ settings x}
onOptP f x = x{sOpt_P = f $ sOpt_P x}
#endif
doCpp :: HscEnv -> Bool -> FilePath -> FilePath -> IO ()
doCpp env raw input_fn output_fn =
#if MIN_VERSION_ghc (9,2,0)
Pipeline.doCpp (hsc_logger env) (hsc_tmpfs env) (hsc_dflags env) (hsc_unit_env env) raw input_fn output_fn
#elif MIN_VERSION_ghc (8,10,0)
Pipeline.doCpp (hsc_dflags env) raw input_fn output_fn
#else
CPP.doCpp (hsc_dflags env) raw input_fn output_fn
#endif

View File

@ -1,204 +1,204 @@
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
-- Copied from https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs on 14 May 2019
-- Requested to be exposed at https://gitlab.haskell.org/ghc/ghc/merge_requests/944.
-- Update the above MR got merged to master on 31 May 2019. When it becomes avialable to ghc-lib, this file can be removed.
{- HLINT ignore -} -- since copied from upstream
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
-- | Re-export 'doCpp' for GHC < 8.10.
--
-- Later versions export what we need.
module Development.IDE.GHC.Compat.CPP (
doCpp
) where
import FileCleanup
import Packages
import Panic
import SysTools
#if MIN_VERSION_ghc(8,8,2)
import LlvmCodeGen (llvmVersionList)
#elif MIN_VERSION_ghc(8,8,0)
import LlvmCodeGen (LlvmVersion (..))
#endif
import Control.Monad
import Data.List (intercalate)
import Data.Maybe
import Data.Version
import DynFlags
import Module (rtsUnitId, toInstalledUnitId)
import System.Directory
import System.FilePath
import System.Info
import Development.IDE.GHC.Compat as Compat
doCpp :: DynFlags -> Bool -> FilePath -> FilePath -> IO ()
doCpp dflags raw input_fn output_fn = do
let hscpp_opts = picPOpts dflags
let cmdline_include_paths = includePaths dflags
pkg_include_dirs <- getPackageIncludePath dflags []
let include_paths_global = foldr (\ x xs -> ("-I" ++ x) : xs) []
(includePathsGlobal cmdline_include_paths ++ pkg_include_dirs)
let include_paths_quote = foldr (\ x xs -> ("-iquote" ++ x) : xs) []
(includePathsQuote cmdline_include_paths)
let include_paths = include_paths_quote ++ include_paths_global
let verbFlags = getVerbFlags dflags
let cpp_prog args | raw = SysTools.runCpp dflags args
#if MIN_VERSION_ghc(8,10,0)
| otherwise = SysTools.runCc Nothing
#else
| otherwise = SysTools.runCc
#endif
dflags (SysTools.Option "-E" : args)
let target_defs =
-- NEIL: Patched to use System.Info instead of constants from CPP
[ "-D" ++ os ++ "_BUILD_OS",
"-D" ++ arch ++ "_BUILD_ARCH",
"-D" ++ os ++ "_HOST_OS",
"-D" ++ arch ++ "_HOST_ARCH" ]
-- remember, in code we *compile*, the HOST is the same our TARGET,
-- and BUILD is the same as our HOST.
let sse_defs =
[ "-D__SSE__" | isSseEnabled dflags ] ++
[ "-D__SSE2__" | isSse2Enabled dflags ] ++
[ "-D__SSE4_2__" | isSse4_2Enabled dflags ]
let avx_defs =
[ "-D__AVX__" | isAvxEnabled dflags ] ++
[ "-D__AVX2__" | isAvx2Enabled dflags ] ++
[ "-D__AVX512CD__" | isAvx512cdEnabled dflags ] ++
[ "-D__AVX512ER__" | isAvx512erEnabled dflags ] ++
[ "-D__AVX512F__" | isAvx512fEnabled dflags ] ++
[ "-D__AVX512PF__" | isAvx512pfEnabled dflags ]
backend_defs <- getBackendDefs dflags
let th_defs = [ "-D__GLASGOW_HASKELL_TH__" ]
-- Default CPP defines in Haskell source
ghcVersionH <- getGhcVersionPathName dflags
let hsSourceCppOpts = [ "-include", ghcVersionH ]
-- MIN_VERSION macros
let uids = explicitPackages (pkgState dflags)
pkgs = catMaybes (map (lookupPackage dflags) uids)
mb_macro_include <-
if not (null pkgs) && gopt Opt_VersionMacros dflags
then do macro_stub <- newTempName dflags TFL_CurrentModule "h"
writeFile macro_stub (generatePackageVersionMacros pkgs)
-- Include version macros for every *exposed* package.
-- Without -hide-all-packages and with a package database
-- size of 1000 packages, it takes cpp an estimated 2
-- milliseconds to process this file. See #10970
-- comment 8.
return [SysTools.FileOption "-include" macro_stub]
else return []
cpp_prog ( map SysTools.Option verbFlags
++ map SysTools.Option include_paths
++ map SysTools.Option hsSourceCppOpts
++ map SysTools.Option target_defs
++ map SysTools.Option backend_defs
++ map SysTools.Option th_defs
++ map SysTools.Option hscpp_opts
++ map SysTools.Option sse_defs
++ map SysTools.Option avx_defs
++ mb_macro_include
-- Set the language mode to assembler-with-cpp when preprocessing. This
-- alleviates some of the C99 macro rules relating to whitespace and the hash
-- operator, which we tend to abuse. Clang in particular is not very happy
-- about this.
++ [ SysTools.Option "-x"
, SysTools.Option "assembler-with-cpp"
, SysTools.Option input_fn
-- We hackily use Option instead of FileOption here, so that the file
-- name is not back-slashed on Windows. cpp is capable of
-- dealing with / in filenames, so it works fine. Furthermore
-- if we put in backslashes, cpp outputs #line directives
-- with *double* backslashes. And that in turn means that
-- our error messages get double backslashes in them.
-- In due course we should arrange that the lexer deals
-- with these \\ escapes properly.
, SysTools.Option "-o"
, SysTools.FileOption "" output_fn
])
getBackendDefs :: DynFlags -> IO [String]
getBackendDefs dflags | hscTarget dflags == HscLlvm = do
llvmVer <- figureLlvmVersion dflags
return $ case llvmVer of
#if MIN_VERSION_ghc(8,8,2)
Just v
| [m] <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, 0) ]
| m:n:_ <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, n) ]
#elif MIN_VERSION_ghc(8,8,0)
Just (LlvmVersion n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (n,0) ]
Just (LlvmVersionOld m n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m,n) ]
#else
Just n -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format n ]
#endif
_ -> []
where
format (major, minor)
| minor >= 100 = error "getBackendDefs: Unsupported minor version"
| otherwise = show $ (100 * major + minor :: Int) -- Contract is Int
getBackendDefs _ =
return []
-- ---------------------------------------------------------------------------
-- Macros (cribbed from Cabal)
generatePackageVersionMacros :: [Compat.UnitInfo] -> String
generatePackageVersionMacros pkgs = concat
-- Do not add any C-style comments. See #3389.
[ generateMacros "" pkgname version
| pkg <- pkgs
, let version = packageVersion pkg
pkgname = map fixchar (packageNameString pkg)
]
fixchar :: Char -> Char
fixchar '-' = '_'
fixchar c = c
generateMacros :: String -> String -> Version -> String
generateMacros prefix name version =
concat
["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n"
,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
," (major1) < ",major1," || \\\n"
," (major1) == ",major1," && (major2) < ",major2," || \\\n"
," (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"
,"\n\n"
]
where
(major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)
-- | Find out path to @ghcversion.h@ file
getGhcVersionPathName :: DynFlags -> IO FilePath
getGhcVersionPathName dflags = do
candidates <- case ghcVersionFile dflags of
Just path -> return [path]
Nothing -> (map (</> "ghcversion.h")) <$>
(getPackageIncludePath dflags [toInstalledUnitId rtsUnit])
found <- filterM doesFileExist candidates
case found of
[] -> throwGhcExceptionIO (InstallationError
("ghcversion.h missing; tried: "
++ intercalate ", " candidates))
(x:_) -> return x
rtsUnit :: UnitId
rtsUnit = Module.rtsUnitId
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
-- Copied from https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs on 14 May 2019
-- Requested to be exposed at https://gitlab.haskell.org/ghc/ghc/merge_requests/944.
-- Update the above MR got merged to master on 31 May 2019. When it becomes avialable to ghc-lib, this file can be removed.
{- HLINT ignore -} -- since copied from upstream
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
-- | Re-export 'doCpp' for GHC < 8.10.
--
-- Later versions export what we need.
module Development.IDE.GHC.Compat.CPP (
doCpp
) where
import FileCleanup
import Packages
import Panic
import SysTools
#if MIN_VERSION_ghc(8,8,2)
import LlvmCodeGen (llvmVersionList)
#elif MIN_VERSION_ghc(8,8,0)
import LlvmCodeGen (LlvmVersion (..))
#endif
import Control.Monad
import Data.List (intercalate)
import Data.Maybe
import Data.Version
import DynFlags
import Module (rtsUnitId, toInstalledUnitId)
import System.Directory
import System.FilePath
import System.Info
import Development.IDE.GHC.Compat as Compat
doCpp :: DynFlags -> Bool -> FilePath -> FilePath -> IO ()
doCpp dflags raw input_fn output_fn = do
let hscpp_opts = picPOpts dflags
let cmdline_include_paths = includePaths dflags
pkg_include_dirs <- getPackageIncludePath dflags []
let include_paths_global = foldr (\ x xs -> ("-I" ++ x) : xs) []
(includePathsGlobal cmdline_include_paths ++ pkg_include_dirs)
let include_paths_quote = foldr (\ x xs -> ("-iquote" ++ x) : xs) []
(includePathsQuote cmdline_include_paths)
let include_paths = include_paths_quote ++ include_paths_global
let verbFlags = getVerbFlags dflags
let cpp_prog args | raw = SysTools.runCpp dflags args
#if MIN_VERSION_ghc(8,10,0)
| otherwise = SysTools.runCc Nothing
#else
| otherwise = SysTools.runCc
#endif
dflags (SysTools.Option "-E" : args)
let target_defs =
-- NEIL: Patched to use System.Info instead of constants from CPP
[ "-D" ++ os ++ "_BUILD_OS",
"-D" ++ arch ++ "_BUILD_ARCH",
"-D" ++ os ++ "_HOST_OS",
"-D" ++ arch ++ "_HOST_ARCH" ]
-- remember, in code we *compile*, the HOST is the same our TARGET,
-- and BUILD is the same as our HOST.
let sse_defs =
[ "-D__SSE__" | isSseEnabled dflags ] ++
[ "-D__SSE2__" | isSse2Enabled dflags ] ++
[ "-D__SSE4_2__" | isSse4_2Enabled dflags ]
let avx_defs =
[ "-D__AVX__" | isAvxEnabled dflags ] ++
[ "-D__AVX2__" | isAvx2Enabled dflags ] ++
[ "-D__AVX512CD__" | isAvx512cdEnabled dflags ] ++
[ "-D__AVX512ER__" | isAvx512erEnabled dflags ] ++
[ "-D__AVX512F__" | isAvx512fEnabled dflags ] ++
[ "-D__AVX512PF__" | isAvx512pfEnabled dflags ]
backend_defs <- getBackendDefs dflags
let th_defs = [ "-D__GLASGOW_HASKELL_TH__" ]
-- Default CPP defines in Haskell source
ghcVersionH <- getGhcVersionPathName dflags
let hsSourceCppOpts = [ "-include", ghcVersionH ]
-- MIN_VERSION macros
let uids = explicitPackages (pkgState dflags)
pkgs = catMaybes (map (lookupPackage dflags) uids)
mb_macro_include <-
if not (null pkgs) && gopt Opt_VersionMacros dflags
then do macro_stub <- newTempName dflags TFL_CurrentModule "h"
writeFile macro_stub (generatePackageVersionMacros pkgs)
-- Include version macros for every *exposed* package.
-- Without -hide-all-packages and with a package database
-- size of 1000 packages, it takes cpp an estimated 2
-- milliseconds to process this file. See #10970
-- comment 8.
return [SysTools.FileOption "-include" macro_stub]
else return []
cpp_prog ( map SysTools.Option verbFlags
++ map SysTools.Option include_paths
++ map SysTools.Option hsSourceCppOpts
++ map SysTools.Option target_defs
++ map SysTools.Option backend_defs
++ map SysTools.Option th_defs
++ map SysTools.Option hscpp_opts
++ map SysTools.Option sse_defs
++ map SysTools.Option avx_defs
++ mb_macro_include
-- Set the language mode to assembler-with-cpp when preprocessing. This
-- alleviates some of the C99 macro rules relating to whitespace and the hash
-- operator, which we tend to abuse. Clang in particular is not very happy
-- about this.
++ [ SysTools.Option "-x"
, SysTools.Option "assembler-with-cpp"
, SysTools.Option input_fn
-- We hackily use Option instead of FileOption here, so that the file
-- name is not back-slashed on Windows. cpp is capable of
-- dealing with / in filenames, so it works fine. Furthermore
-- if we put in backslashes, cpp outputs #line directives
-- with *double* backslashes. And that in turn means that
-- our error messages get double backslashes in them.
-- In due course we should arrange that the lexer deals
-- with these \\ escapes properly.
, SysTools.Option "-o"
, SysTools.FileOption "" output_fn
])
getBackendDefs :: DynFlags -> IO [String]
getBackendDefs dflags | hscTarget dflags == HscLlvm = do
llvmVer <- figureLlvmVersion dflags
return $ case llvmVer of
#if MIN_VERSION_ghc(8,8,2)
Just v
| [m] <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, 0) ]
| m:n:_ <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, n) ]
#elif MIN_VERSION_ghc(8,8,0)
Just (LlvmVersion n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (n,0) ]
Just (LlvmVersionOld m n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m,n) ]
#else
Just n -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format n ]
#endif
_ -> []
where
format (major, minor)
| minor >= 100 = error "getBackendDefs: Unsupported minor version"
| otherwise = show $ (100 * major + minor :: Int) -- Contract is Int
getBackendDefs _ =
return []
-- ---------------------------------------------------------------------------
-- Macros (cribbed from Cabal)
generatePackageVersionMacros :: [Compat.UnitInfo] -> String
generatePackageVersionMacros pkgs = concat
-- Do not add any C-style comments. See #3389.
[ generateMacros "" pkgname version
| pkg <- pkgs
, let version = packageVersion pkg
pkgname = map fixchar (packageNameString pkg)
]
fixchar :: Char -> Char
fixchar '-' = '_'
fixchar c = c
generateMacros :: String -> String -> Version -> String
generateMacros prefix name version =
concat
["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n"
,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
," (major1) < ",major1," || \\\n"
," (major1) == ",major1," && (major2) < ",major2," || \\\n"
," (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"
,"\n\n"
]
where
(major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)
-- | Find out path to @ghcversion.h@ file
getGhcVersionPathName :: DynFlags -> IO FilePath
getGhcVersionPathName dflags = do
candidates <- case ghcVersionFile dflags of
Just path -> return [path]
Nothing -> (map (</> "ghcversion.h")) <$>
(getPackageIncludePath dflags [toInstalledUnitId rtsUnit])
found <- filterM doesFileExist candidates
case found of
[] -> throwGhcExceptionIO (InstallationError
("ghcversion.h missing; tried: "
++ intercalate ", " candidates))
(x:_) -> return x
rtsUnit :: UnitId
rtsUnit = Module.rtsUnitId

View File

@ -1,53 +1,53 @@
{-# LANGUAGE NumericUnderscores #-}
-- | Logging utilities for reporting heap statistics
module Development.IDE.Main.HeapStats ( withHeapStats ) where
import Control.Concurrent
import Control.Concurrent.Async
import Control.Monad
import qualified Data.Text as T
import Data.Word
import Development.IDE.Types.Logger (Logger, logInfo)
import GHC.Stats
import Text.Printf (printf)
-- | Interval at which to report the latest heap statistics.
heapStatsInterval :: Int
heapStatsInterval = 60_000_000 -- 60s
-- | Report the live bytes and heap size at the last major collection.
logHeapStats :: Logger -> IO ()
logHeapStats l = do
stats <- getRTSStats
-- live_bytes is the total amount of live memory in a program
-- (corresponding to the amount on a heap profile)
let live_bytes = gcdetails_live_bytes (gc stats)
-- heap_size is the total amount of memory the RTS is using
-- this corresponds closer to OS memory usage
heap_size = gcdetails_mem_in_use_bytes (gc stats)
format :: Word64 -> T.Text
format m = T.pack (printf "%.2fMB" (fromIntegral @Word64 @Double m / 1e6))
message = "Live bytes: " <> format live_bytes <> " " <>
"Heap size: " <> format heap_size
logInfo l message
-- | An action which logs heap statistics at the 'heapStatsInterval'
heapStatsThread :: Logger -> IO r
heapStatsThread l = forever $ do
threadDelay heapStatsInterval
logHeapStats l
-- | A helper function which lauches the 'heapStatsThread' and kills it
-- appropiately when the inner action finishes. It also checks to see
-- if `-T` is enabled.
withHeapStats :: Logger -> IO r -> IO r
withHeapStats l k = do
enabled <- getRTSStatsEnabled
if enabled
then do
logInfo l ("Logging heap statistics every "
<> T.pack (printf "%.2fs" (fromIntegral @Int @Double heapStatsInterval / 1e6)))
withAsync (heapStatsThread l) (const k)
else do
logInfo l "Heap statistics are not enabled (RTS option -T is needed)"
k
{-# LANGUAGE NumericUnderscores #-}
-- | Logging utilities for reporting heap statistics
module Development.IDE.Main.HeapStats ( withHeapStats ) where
import Control.Concurrent
import Control.Concurrent.Async
import Control.Monad
import qualified Data.Text as T
import Data.Word
import Development.IDE.Types.Logger (Logger, logInfo)
import GHC.Stats
import Text.Printf (printf)
-- | Interval at which to report the latest heap statistics.
heapStatsInterval :: Int
heapStatsInterval = 60_000_000 -- 60s
-- | Report the live bytes and heap size at the last major collection.
logHeapStats :: Logger -> IO ()
logHeapStats l = do
stats <- getRTSStats
-- live_bytes is the total amount of live memory in a program
-- (corresponding to the amount on a heap profile)
let live_bytes = gcdetails_live_bytes (gc stats)
-- heap_size is the total amount of memory the RTS is using
-- this corresponds closer to OS memory usage
heap_size = gcdetails_mem_in_use_bytes (gc stats)
format :: Word64 -> T.Text
format m = T.pack (printf "%.2fMB" (fromIntegral @Word64 @Double m / 1e6))
message = "Live bytes: " <> format live_bytes <> " " <>
"Heap size: " <> format heap_size
logInfo l message
-- | An action which logs heap statistics at the 'heapStatsInterval'
heapStatsThread :: Logger -> IO r
heapStatsThread l = forever $ do
threadDelay heapStatsInterval
logHeapStats l
-- | A helper function which lauches the 'heapStatsThread' and kills it
-- appropiately when the inner action finishes. It also checks to see
-- if `-T` is enabled.
withHeapStats :: Logger -> IO r -> IO r
withHeapStats l k = do
enabled <- getRTSStatsEnabled
if enabled
then do
logInfo l ("Logging heap statistics every "
<> T.pack (printf "%.2fs" (fromIntegral @Int @Double heapStatsInterval / 1e6)))
withAsync (heapStatsThread l) (const k)
else do
logInfo l "Heap statistics are not enabled (RTS option -T is needed)"
k

View File

@ -31,7 +31,7 @@ import Data.Function (on)
import Data.Functor
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HashSet
import Data.Monoid (First(..))
import Data.Monoid (First (..))
import Data.Ord (Down (Down))
import qualified Data.Set as Set
import Development.IDE.Core.Compile

View File

@ -1,224 +1,224 @@
{-# LANGUAGE RankNTypes #-}
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
module Development.IDE.Spans.Documentation (
getDocumentation
, getDocumentationTryGhc
, getDocumentationsTryGhc
, DocMap
, mkDocMap
) where
import Control.Monad
import Control.Monad.Extra (findM)
import Control.Monad.IO.Class
import Data.Either
import Data.Foldable
import Data.List.Extra
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set as S
import qualified Data.Text as T
import Development.IDE.Core.Compile
import Development.IDE.Core.RuleTypes
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Compat.Util
import Development.IDE.GHC.Error
import Development.IDE.Spans.Common
import System.Directory
import System.FilePath
import Language.LSP.Types (filePathToUri, getUri)
mkDocMap
:: HscEnv
-> RefMap a
-> TcGblEnv
-> IO DocAndKindMap
mkDocMap env rm this_mod =
do
#if MIN_VERSION_ghc(9,2,0)
(_ , DeclDocMap this_docs, _) <- extractDocs this_mod
#else
let (_ , DeclDocMap this_docs, _) = extractDocs this_mod
#endif
d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names
k <- foldrM getType (tcg_type_env this_mod) names
pure $ DKMap d k
where
getDocs n map
| maybe True (mod ==) $ nameModule_maybe n = pure map -- we already have the docs in this_docs, or they do not exist
| otherwise = do
doc <- getDocumentationTryGhc env mod n
pure $ extendNameEnv map n doc
getType n map
| isTcOcc $ occName n = do
kind <- lookupKind env mod n
pure $ maybe map (extendNameEnv map n) kind
| otherwise = pure map
names = rights $ S.toList idents
idents = M.keysSet rm
mod = tcg_mod this_mod
lookupKind :: HscEnv -> Module -> Name -> IO (Maybe TyThing)
lookupKind env mod =
fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod
getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]
getDocumentationsTryGhc :: HscEnv -> Module -> [Name] -> IO [SpanDoc]
getDocumentationsTryGhc env mod names = do
res <- catchSrcErrors (hsc_dflags env) "docs" $ getDocsBatch env mod names
case res of
Left _ -> return []
Right res -> zipWithM unwrap res names
where
unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n
unwrap _ n = mkSpanDocText n
mkSpanDocText name =
SpanDocText [] <$> getUris name
-- Get the uris to the documentation and source html pages if they exist
getUris name = do
(docFu, srcFu) <-
case nameModule_maybe name of
Just mod -> liftIO $ do
doc <- toFileUriText $ lookupDocHtmlForModule env mod
src <- toFileUriText $ lookupSrcHtmlForModule env mod
return (doc, src)
Nothing -> pure (Nothing, Nothing)
let docUri = (<> "#" <> selector <> showNameWithoutUniques name) <$> docFu
srcUri = (<> "#" <> showNameWithoutUniques name) <$> srcFu
selector
| isValName name = "v:"
| otherwise = "t:"
return $ SpanDocUris docUri srcUri
toFileUriText = (fmap . fmap) (getUri . filePathToUri)
getDocumentation
:: HasSrcSpan name
=> [ParsedModule] -- ^ All of the possible modules it could be defined in.
-> name -- ^ The name you want documentation for.
-> [T.Text]
-- This finds any documentation between the name you want
-- documentation for and the one before it. This is only an
-- approximately correct algorithm and there are easily constructed
-- cases where it will be wrong (if so then usually slightly but there
-- may be edge cases where it is very wrong).
-- TODO : Build a version of GHC exactprint to extract this information
-- more accurately.
-- TODO : Implement this for GHC 9.2 with in-tree annotations
-- (alternatively, just remove it and rely soley on GHC's parsing)
getDocumentation sources targetName = fromMaybe [] $ do
#if MIN_VERSION_ghc(9,2,0)
Nothing
#else
-- Find the module the target is defined in.
targetNameSpan <- realSpan $ getLoc targetName
tc <-
find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)
$ reverse sources -- TODO : Is reversing the list here really neccessary?
-- Top level names bound by the module
let bs = [ n | let L _ HsModule{hsmodDecls} = pm_parsed_source tc
, L _ (ValD _ hsbind) <- hsmodDecls
, Just n <- [name_of_bind hsbind]
]
-- Sort the names' source spans.
let sortedSpans = sortedNameSpans bs
-- Now go ahead and extract the docs.
let docs = ann tc
nameInd <- elemIndex targetNameSpan sortedSpans
let prevNameSpan =
if nameInd >= 1
then sortedSpans !! (nameInd - 1)
else zeroSpan $ srcSpanFile targetNameSpan
-- Annoyingly "-- |" documentation isn't annotated with a location,
-- so you have to pull it out from the elements.
pure
$ docHeaders
$ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)
$ fold
docs
where
-- Get the name bound by a binding. We only concern ourselves with
-- @FunBind@ (which covers functions and variables).
name_of_bind :: HsBind GhcPs -> Maybe (Located RdrName)
name_of_bind FunBind {fun_id} = Just fun_id
name_of_bind _ = Nothing
-- Get source spans from names, discard unhelpful spans, remove
-- duplicates and sort.
sortedNameSpans :: [Located RdrName] -> [RealSrcSpan]
sortedNameSpans ls = nubSort (mapMaybe (realSpan . getLoc) ls)
isBetween target before after = before <= target && target <= after
#if MIN_VERSION_ghc(9,0,0)
ann = apiAnnComments . pm_annotations
#else
ann = fmap filterReal . snd . pm_annotations
filterReal :: [Located a] -> [RealLocated a]
filterReal = mapMaybe (\(L l v) -> (`L`v) <$> realSpan l)
#endif
annotationFileName :: ParsedModule -> Maybe FastString
annotationFileName = fmap srcSpanFile . listToMaybe . map getRealSrcSpan . fold . ann
-- | Shows this part of the documentation
docHeaders :: [RealLocated AnnotationComment]
-> [T.Text]
docHeaders = mapMaybe (\(L _ x) -> wrk x)
where
wrk = \case
-- When `Opt_Haddock` is enabled.
AnnDocCommentNext s -> Just $ T.pack s
-- When `Opt_KeepRawTokenStream` enabled.
AnnLineComment s -> if "-- |" `isPrefixOf` s
then Just $ T.pack s
else Nothing
_ -> Nothing
#endif
-- These are taken from haskell-ide-engine's Haddock plugin
-- | Given a module finds the local @doc/html/Foo-Bar-Baz.html@ page.
-- An example for a cabal installed module:
-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/Data-Vector-Primitive.html@
lookupDocHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)
lookupDocHtmlForModule =
lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> modDocName <.> "html")
-- | Given a module finds the hyperlinked source @doc/html/src/Foo.Bar.Baz.html@ page.
-- An example for a cabal installed module:
-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/src/Data.Vector.Primitive.html@
lookupSrcHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)
lookupSrcHtmlForModule =
lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> "src" </> modDocName <.> "html")
lookupHtmlForModule :: (FilePath -> FilePath -> FilePath) -> HscEnv -> Module -> IO (Maybe FilePath)
lookupHtmlForModule mkDocPath hscEnv m = do
-- try all directories
let mfs = fmap (concatMap go) (lookupHtmls hscEnv ui)
html <- findM doesFileExist (concat . maybeToList $ mfs)
-- canonicalize located html to remove /../ indirection which can break some clients
-- (vscode on Windows at least)
traverse canonicalizePath html
where
go pkgDocDir = map (mkDocPath pkgDocDir) mns
ui = moduleUnit m
-- try to locate html file from most to least specific name e.g.
-- first Language.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html
-- then Language.LSP.Types.html and Language-Haskell-LSP-Types.html etc.
mns = do
chunks <- (reverse . drop1 . inits . splitOn ".") $ (moduleNameString . moduleName) m
-- The file might use "." or "-" as separator
map (`intercalate` chunks) [".", "-"]
lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath]
lookupHtmls df ui =
-- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path
-- and therefore doesn't expand $topdir on Windows
map takeDirectory . unitHaddockInterfaces <$> lookupUnit df ui
{-# LANGUAGE RankNTypes #-}
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
module Development.IDE.Spans.Documentation (
getDocumentation
, getDocumentationTryGhc
, getDocumentationsTryGhc
, DocMap
, mkDocMap
) where
import Control.Monad
import Control.Monad.Extra (findM)
import Control.Monad.IO.Class
import Data.Either
import Data.Foldable
import Data.List.Extra
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set as S
import qualified Data.Text as T
import Development.IDE.Core.Compile
import Development.IDE.Core.RuleTypes
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Compat.Util
import Development.IDE.GHC.Error
import Development.IDE.Spans.Common
import System.Directory
import System.FilePath
import Language.LSP.Types (filePathToUri, getUri)
mkDocMap
:: HscEnv
-> RefMap a
-> TcGblEnv
-> IO DocAndKindMap
mkDocMap env rm this_mod =
do
#if MIN_VERSION_ghc(9,2,0)
(_ , DeclDocMap this_docs, _) <- extractDocs this_mod
#else
let (_ , DeclDocMap this_docs, _) = extractDocs this_mod
#endif
d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names
k <- foldrM getType (tcg_type_env this_mod) names
pure $ DKMap d k
where
getDocs n map
| maybe True (mod ==) $ nameModule_maybe n = pure map -- we already have the docs in this_docs, or they do not exist
| otherwise = do
doc <- getDocumentationTryGhc env mod n
pure $ extendNameEnv map n doc
getType n map
| isTcOcc $ occName n = do
kind <- lookupKind env mod n
pure $ maybe map (extendNameEnv map n) kind
| otherwise = pure map
names = rights $ S.toList idents
idents = M.keysSet rm
mod = tcg_mod this_mod
lookupKind :: HscEnv -> Module -> Name -> IO (Maybe TyThing)
lookupKind env mod =
fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod
getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]
getDocumentationsTryGhc :: HscEnv -> Module -> [Name] -> IO [SpanDoc]
getDocumentationsTryGhc env mod names = do
res <- catchSrcErrors (hsc_dflags env) "docs" $ getDocsBatch env mod names
case res of
Left _ -> return []
Right res -> zipWithM unwrap res names
where
unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n
unwrap _ n = mkSpanDocText n
mkSpanDocText name =
SpanDocText [] <$> getUris name
-- Get the uris to the documentation and source html pages if they exist
getUris name = do
(docFu, srcFu) <-
case nameModule_maybe name of
Just mod -> liftIO $ do
doc <- toFileUriText $ lookupDocHtmlForModule env mod
src <- toFileUriText $ lookupSrcHtmlForModule env mod
return (doc, src)
Nothing -> pure (Nothing, Nothing)
let docUri = (<> "#" <> selector <> showNameWithoutUniques name) <$> docFu
srcUri = (<> "#" <> showNameWithoutUniques name) <$> srcFu
selector
| isValName name = "v:"
| otherwise = "t:"
return $ SpanDocUris docUri srcUri
toFileUriText = (fmap . fmap) (getUri . filePathToUri)
getDocumentation
:: HasSrcSpan name
=> [ParsedModule] -- ^ All of the possible modules it could be defined in.
-> name -- ^ The name you want documentation for.
-> [T.Text]
-- This finds any documentation between the name you want
-- documentation for and the one before it. This is only an
-- approximately correct algorithm and there are easily constructed
-- cases where it will be wrong (if so then usually slightly but there
-- may be edge cases where it is very wrong).
-- TODO : Build a version of GHC exactprint to extract this information
-- more accurately.
-- TODO : Implement this for GHC 9.2 with in-tree annotations
-- (alternatively, just remove it and rely soley on GHC's parsing)
getDocumentation sources targetName = fromMaybe [] $ do
#if MIN_VERSION_ghc(9,2,0)
Nothing
#else
-- Find the module the target is defined in.
targetNameSpan <- realSpan $ getLoc targetName
tc <-
find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)
$ reverse sources -- TODO : Is reversing the list here really neccessary?
-- Top level names bound by the module
let bs = [ n | let L _ HsModule{hsmodDecls} = pm_parsed_source tc
, L _ (ValD _ hsbind) <- hsmodDecls
, Just n <- [name_of_bind hsbind]
]
-- Sort the names' source spans.
let sortedSpans = sortedNameSpans bs
-- Now go ahead and extract the docs.
let docs = ann tc
nameInd <- elemIndex targetNameSpan sortedSpans
let prevNameSpan =
if nameInd >= 1
then sortedSpans !! (nameInd - 1)
else zeroSpan $ srcSpanFile targetNameSpan
-- Annoyingly "-- |" documentation isn't annotated with a location,
-- so you have to pull it out from the elements.
pure
$ docHeaders
$ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)
$ fold
docs
where
-- Get the name bound by a binding. We only concern ourselves with
-- @FunBind@ (which covers functions and variables).
name_of_bind :: HsBind GhcPs -> Maybe (Located RdrName)
name_of_bind FunBind {fun_id} = Just fun_id
name_of_bind _ = Nothing
-- Get source spans from names, discard unhelpful spans, remove
-- duplicates and sort.
sortedNameSpans :: [Located RdrName] -> [RealSrcSpan]
sortedNameSpans ls = nubSort (mapMaybe (realSpan . getLoc) ls)
isBetween target before after = before <= target && target <= after
#if MIN_VERSION_ghc(9,0,0)
ann = apiAnnComments . pm_annotations
#else
ann = fmap filterReal . snd . pm_annotations
filterReal :: [Located a] -> [RealLocated a]
filterReal = mapMaybe (\(L l v) -> (`L`v) <$> realSpan l)
#endif
annotationFileName :: ParsedModule -> Maybe FastString
annotationFileName = fmap srcSpanFile . listToMaybe . map getRealSrcSpan . fold . ann
-- | Shows this part of the documentation
docHeaders :: [RealLocated AnnotationComment]
-> [T.Text]
docHeaders = mapMaybe (\(L _ x) -> wrk x)
where
wrk = \case
-- When `Opt_Haddock` is enabled.
AnnDocCommentNext s -> Just $ T.pack s
-- When `Opt_KeepRawTokenStream` enabled.
AnnLineComment s -> if "-- |" `isPrefixOf` s
then Just $ T.pack s
else Nothing
_ -> Nothing
#endif
-- These are taken from haskell-ide-engine's Haddock plugin
-- | Given a module finds the local @doc/html/Foo-Bar-Baz.html@ page.
-- An example for a cabal installed module:
-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/Data-Vector-Primitive.html@
lookupDocHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)
lookupDocHtmlForModule =
lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> modDocName <.> "html")
-- | Given a module finds the hyperlinked source @doc/html/src/Foo.Bar.Baz.html@ page.
-- An example for a cabal installed module:
-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/src/Data.Vector.Primitive.html@
lookupSrcHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)
lookupSrcHtmlForModule =
lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> "src" </> modDocName <.> "html")
lookupHtmlForModule :: (FilePath -> FilePath -> FilePath) -> HscEnv -> Module -> IO (Maybe FilePath)
lookupHtmlForModule mkDocPath hscEnv m = do
-- try all directories
let mfs = fmap (concatMap go) (lookupHtmls hscEnv ui)
html <- findM doesFileExist (concat . maybeToList $ mfs)
-- canonicalize located html to remove /../ indirection which can break some clients
-- (vscode on Windows at least)
traverse canonicalizePath html
where
go pkgDocDir = map (mkDocPath pkgDocDir) mns
ui = moduleUnit m
-- try to locate html file from most to least specific name e.g.
-- first Language.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html
-- then Language.LSP.Types.html and Language-Haskell-LSP-Types.html etc.
mns = do
chunks <- (reverse . drop1 . inits . splitOn ".") $ (moduleNameString . moduleName) m
-- The file might use "." or "-" as separator
map (`intercalate` chunks) [".", "-"]
lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath]
lookupHtmls df ui =
-- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path
-- and therefore doesn't expand $topdir on Windows
map takeDirectory . unitHaddockInterfaces <$> lookupUnit df ui

View File

@ -1,140 +1,140 @@
{-# LANGUAGE DerivingStrategies #-}
module Development.IDE.Spans.LocalBindings
( Bindings
, getLocalScope
, getFuzzyScope
, getDefiningBindings
, getFuzzyDefiningBindings
, bindings
) where
import Control.DeepSeq
import Control.Monad
import Data.Bifunctor
import Data.IntervalMap.FingerTree (Interval (..), IntervalMap)
import qualified Data.IntervalMap.FingerTree as IM
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import Development.IDE.GHC.Compat (Name, NameEnv, RealSrcSpan,
RefMap, Scope (..), Type,
getBindSiteFromContext,
getScopeFromContext, identInfo,
identType, isSystemName,
nameEnvElts, realSrcSpanEnd,
realSrcSpanStart, unitNameEnv)
import Development.IDE.GHC.Error
import Development.IDE.Types.Location
------------------------------------------------------------------------------
-- | Turn a 'RealSrcSpan' into an 'Interval'.
realSrcSpanToInterval :: RealSrcSpan -> Interval Position
realSrcSpanToInterval rss =
Interval
(realSrcLocToPosition $ realSrcSpanStart rss)
(realSrcLocToPosition $ realSrcSpanEnd rss)
bindings :: RefMap Type -> Bindings
bindings = uncurry Bindings . localBindings
------------------------------------------------------------------------------
-- | Compute which identifiers are in scope at every point in the AST. Use
-- 'getLocalScope' to find the results.
localBindings
:: RefMap Type
-> ( IntervalMap Position (NameEnv (Name, Maybe Type))
, IntervalMap Position (NameEnv (Name, Maybe Type))
)
localBindings refmap = bimap mk mk $ unzip $ do
(ident, refs) <- M.toList refmap
Right name <- pure ident
(_, ident_details) <- refs
let ty = identType ident_details
info <- S.toList $ identInfo ident_details
pure
( do
Just scopes <- pure $ getScopeFromContext info
scope <- scopes >>= \case
LocalScope scope -> pure $ realSrcSpanToInterval scope
_ -> []
pure ( scope
, unitNameEnv name (name,ty)
)
, do
Just scope <- pure $ getBindSiteFromContext info
pure ( realSrcSpanToInterval scope
, unitNameEnv name (name,ty)
)
)
where
mk = L.foldl' (flip (uncurry IM.insert)) mempty . join
------------------------------------------------------------------------------
-- | The available bindings at every point in a Haskell tree.
data Bindings = Bindings
{ getLocalBindings
:: IntervalMap Position (NameEnv (Name, Maybe Type))
, getBindingSites
:: IntervalMap Position (NameEnv (Name, Maybe Type))
}
instance Semigroup Bindings where
Bindings a1 b1 <> Bindings a2 b2
= Bindings (a1 <> a2) (b1 <> b2)
instance Monoid Bindings where
mempty = Bindings mempty mempty
instance NFData Bindings where
rnf = rwhnf
instance Show Bindings where
show _ = "<bindings>"
------------------------------------------------------------------------------
-- | Given a 'Bindings' get every identifier in scope at the given
-- 'RealSrcSpan',
getLocalScope :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]
getLocalScope bs rss
= nameEnvElts
$ foldMap snd
$ IM.dominators (realSrcSpanToInterval rss)
$ getLocalBindings bs
------------------------------------------------------------------------------
-- | Given a 'Bindings', get every binding currently active at a given
-- 'RealSrcSpan',
getDefiningBindings :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]
getDefiningBindings bs rss
= nameEnvElts
$ foldMap snd
$ IM.dominators (realSrcSpanToInterval rss)
$ getBindingSites bs
-- | Lookup all names in scope in any span that intersects the interval
-- defined by the two positions.
-- This is meant for use with the fuzzy `PositionRange` returned by `PositionMapping`
getFuzzyScope :: Bindings -> Position -> Position -> [(Name, Maybe Type)]
getFuzzyScope bs a b
= filter (not . isSystemName . fst)
$ nameEnvElts
$ foldMap snd
$ IM.intersections (Interval a b)
$ getLocalBindings bs
------------------------------------------------------------------------------
-- | Given a 'Bindings', get every binding that intersects the interval defined
-- by the two positions.
-- This is meant for use with the fuzzy `PositionRange` returned by
-- `PositionMapping`
getFuzzyDefiningBindings :: Bindings -> Position -> Position -> [(Name, Maybe Type)]
getFuzzyDefiningBindings bs a b
= nameEnvElts
$ foldMap snd
$ IM.intersections (Interval a b)
$ getBindingSites bs
{-# LANGUAGE DerivingStrategies #-}
module Development.IDE.Spans.LocalBindings
( Bindings
, getLocalScope
, getFuzzyScope
, getDefiningBindings
, getFuzzyDefiningBindings
, bindings
) where
import Control.DeepSeq
import Control.Monad
import Data.Bifunctor
import Data.IntervalMap.FingerTree (Interval (..), IntervalMap)
import qualified Data.IntervalMap.FingerTree as IM
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import Development.IDE.GHC.Compat (Name, NameEnv, RealSrcSpan,
RefMap, Scope (..), Type,
getBindSiteFromContext,
getScopeFromContext, identInfo,
identType, isSystemName,
nameEnvElts, realSrcSpanEnd,
realSrcSpanStart, unitNameEnv)
import Development.IDE.GHC.Error
import Development.IDE.Types.Location
------------------------------------------------------------------------------
-- | Turn a 'RealSrcSpan' into an 'Interval'.
realSrcSpanToInterval :: RealSrcSpan -> Interval Position
realSrcSpanToInterval rss =
Interval
(realSrcLocToPosition $ realSrcSpanStart rss)
(realSrcLocToPosition $ realSrcSpanEnd rss)
bindings :: RefMap Type -> Bindings
bindings = uncurry Bindings . localBindings
------------------------------------------------------------------------------
-- | Compute which identifiers are in scope at every point in the AST. Use
-- 'getLocalScope' to find the results.
localBindings
:: RefMap Type
-> ( IntervalMap Position (NameEnv (Name, Maybe Type))
, IntervalMap Position (NameEnv (Name, Maybe Type))
)
localBindings refmap = bimap mk mk $ unzip $ do
(ident, refs) <- M.toList refmap
Right name <- pure ident
(_, ident_details) <- refs
let ty = identType ident_details
info <- S.toList $ identInfo ident_details
pure
( do
Just scopes <- pure $ getScopeFromContext info
scope <- scopes >>= \case
LocalScope scope -> pure $ realSrcSpanToInterval scope
_ -> []
pure ( scope
, unitNameEnv name (name,ty)
)
, do
Just scope <- pure $ getBindSiteFromContext info
pure ( realSrcSpanToInterval scope
, unitNameEnv name (name,ty)
)
)
where
mk = L.foldl' (flip (uncurry IM.insert)) mempty . join
------------------------------------------------------------------------------
-- | The available bindings at every point in a Haskell tree.
data Bindings = Bindings
{ getLocalBindings
:: IntervalMap Position (NameEnv (Name, Maybe Type))
, getBindingSites
:: IntervalMap Position (NameEnv (Name, Maybe Type))
}
instance Semigroup Bindings where
Bindings a1 b1 <> Bindings a2 b2
= Bindings (a1 <> a2) (b1 <> b2)
instance Monoid Bindings where
mempty = Bindings mempty mempty
instance NFData Bindings where
rnf = rwhnf
instance Show Bindings where
show _ = "<bindings>"
------------------------------------------------------------------------------
-- | Given a 'Bindings' get every identifier in scope at the given
-- 'RealSrcSpan',
getLocalScope :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]
getLocalScope bs rss
= nameEnvElts
$ foldMap snd
$ IM.dominators (realSrcSpanToInterval rss)
$ getLocalBindings bs
------------------------------------------------------------------------------
-- | Given a 'Bindings', get every binding currently active at a given
-- 'RealSrcSpan',
getDefiningBindings :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]
getDefiningBindings bs rss
= nameEnvElts
$ foldMap snd
$ IM.dominators (realSrcSpanToInterval rss)
$ getBindingSites bs
-- | Lookup all names in scope in any span that intersects the interval
-- defined by the two positions.
-- This is meant for use with the fuzzy `PositionRange` returned by `PositionMapping`
getFuzzyScope :: Bindings -> Position -> Position -> [(Name, Maybe Type)]
getFuzzyScope bs a b
= filter (not . isSystemName . fst)
$ nameEnvElts
$ foldMap snd
$ IM.intersections (Interval a b)
$ getLocalBindings bs
------------------------------------------------------------------------------
-- | Given a 'Bindings', get every binding that intersects the interval defined
-- by the two positions.
-- This is meant for use with the fuzzy `PositionRange` returned by
-- `PositionMapping`
getFuzzyDefiningBindings :: Bindings -> Position -> Position -> [(Name, Maybe Type)]
getFuzzyDefiningBindings bs a b
= nameEnvElts
$ foldMap snd
$ IM.intersections (Interval a b)
$ getBindingSites bs

View File

@ -1,153 +1,153 @@
module Development.IDE.Types.HscEnvEq
( HscEnvEq,
hscEnv, newHscEnvEq,
hscEnvWithImportPaths,
newHscEnvEqPreserveImportPaths,
newHscEnvEqWithImportPaths,
envImportPaths,
envPackageExports,
envVisibleModuleNames,
deps
) where
import Control.Concurrent.Async (Async, async, waitCatch)
import Control.Concurrent.Strict (modifyVar, newVar)
import Control.DeepSeq (force)
import Control.Exception (evaluate, mask, throwIO)
import Control.Monad.Extra (eitherM, join, mapMaybeM)
import Data.Either (fromRight)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Unique (Unique)
import qualified Data.Unique as Unique
import Development.IDE.GHC.Compat
import qualified Development.IDE.GHC.Compat.Util as Maybes
import Development.IDE.GHC.Error (catchSrcErrors)
import Development.IDE.GHC.Util (lookupPackageConfig)
import Development.IDE.Graph.Classes
import Development.IDE.Types.Exports (ExportsMap, createExportsMap)
import OpenTelemetry.Eventlog (withSpan)
import System.Directory (makeAbsolute)
import System.FilePath
-- | An 'HscEnv' with equality. Two values are considered equal
-- if they are created with the same call to 'newHscEnvEq'.
data HscEnvEq = HscEnvEq
{ envUnique :: !Unique
, hscEnv :: !HscEnv
, deps :: [(UnitId, DynFlags)]
-- ^ In memory components for this HscEnv
-- This is only used at the moment for the import dirs in
-- the DynFlags
, envImportPaths :: Maybe (Set FilePath)
-- ^ If Just, import dirs originally configured in this env
-- If Nothing, the env import dirs are unaltered
, envPackageExports :: IO ExportsMap
, envVisibleModuleNames :: IO (Maybe [ModuleName])
-- ^ 'listVisibleModuleNames' is a pure function,
-- but it could panic due to a ghc bug: https://github.com/haskell/haskell-language-server/issues/1365
-- So it's wrapped in IO here for error handling
-- If Nothing, 'listVisibleModuleNames' panic
}
-- | Wrap an 'HscEnv' into an 'HscEnvEq'.
newHscEnvEq :: FilePath -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
newHscEnvEq cradlePath hscEnv0 deps = do
let relativeToCradle = (takeDirectory cradlePath </>)
hscEnv = removeImportPaths hscEnv0
-- Make Absolute since targets are also absolute
importPathsCanon <-
mapM makeAbsolute $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0)
newHscEnvEqWithImportPaths (Just $ Set.fromList importPathsCanon) hscEnv deps
newHscEnvEqWithImportPaths :: Maybe (Set FilePath) -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do
let dflags = hsc_dflags hscEnv
envUnique <- Unique.newUnique
-- it's very important to delay the package exports computation
envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do
-- compute the package imports
let pkgst = unitState hscEnv
depends = explicitUnits pkgst
modules =
[ m
| d <- depends
, Just pkg <- [lookupPackageConfig d hscEnv]
, (modName, maybeOtherPkgMod) <- unitExposedModules pkg
, let m = case maybeOtherPkgMod of
-- When module is re-exported from another package,
-- the origin module is represented by value in Just
Just otherPkgMod -> otherPkgMod
Nothing -> mkModule (unitInfoId pkg) modName
]
doOne m = do
modIface <- initIfaceLoad hscEnv $
loadInterface "" m (ImportByUser NotBoot)
return $ case modIface of
Maybes.Failed _r -> Nothing
Maybes.Succeeded mi -> Just mi
modIfaces <- mapMaybeM doOne modules
return $ createExportsMap modIfaces
-- similar to envPackageExports, evaluated lazily
envVisibleModuleNames <- onceAsync $
fromRight Nothing
<$> catchSrcErrors
dflags
"listVisibleModuleNames"
(evaluate . force . Just $ listVisibleModuleNames hscEnv)
return HscEnvEq{..}
-- | Wrap an 'HscEnv' into an 'HscEnvEq'.
newHscEnvEqPreserveImportPaths
:: HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
newHscEnvEqPreserveImportPaths = newHscEnvEqWithImportPaths Nothing
-- | Unwrap the 'HscEnv' with the original import paths.
-- Used only for locating imports
hscEnvWithImportPaths :: HscEnvEq -> HscEnv
hscEnvWithImportPaths HscEnvEq{..}
| Just imps <- envImportPaths
= hscSetFlags (setImportPaths (Set.toList imps) (hsc_dflags hscEnv)) hscEnv
| otherwise
= hscEnv
removeImportPaths :: HscEnv -> HscEnv
removeImportPaths hsc = hscSetFlags (setImportPaths [] (hsc_dflags hsc)) hsc
instance Show HscEnvEq where
show HscEnvEq{envUnique} = "HscEnvEq " ++ show (Unique.hashUnique envUnique)
instance Eq HscEnvEq where
a == b = envUnique a == envUnique b
instance NFData HscEnvEq where
rnf (HscEnvEq a b c d _ _) =
-- deliberately skip the package exports map and visible module names
rnf (Unique.hashUnique a) `seq` b `seq` c `seq` rnf d
instance Hashable HscEnvEq where
hashWithSalt s = hashWithSalt s . envUnique
-- | Given an action, produce a wrapped action that runs at most once.
-- The action is run in an async so it won't be killed by async exceptions
-- If the function raises an exception, the same exception will be reraised each time.
onceAsync :: IO a -> IO (IO a)
onceAsync act = do
var <- newVar OncePending
let run as = eitherM throwIO pure (waitCatch as)
pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of
OnceRunning x -> pure (v, unmask $ run x)
OncePending -> do
x <- async (unmask act)
pure (OnceRunning x, unmask $ run x)
data Once a = OncePending | OnceRunning (Async a)
module Development.IDE.Types.HscEnvEq
( HscEnvEq,
hscEnv, newHscEnvEq,
hscEnvWithImportPaths,
newHscEnvEqPreserveImportPaths,
newHscEnvEqWithImportPaths,
envImportPaths,
envPackageExports,
envVisibleModuleNames,
deps
) where
import Control.Concurrent.Async (Async, async, waitCatch)
import Control.Concurrent.Strict (modifyVar, newVar)
import Control.DeepSeq (force)
import Control.Exception (evaluate, mask, throwIO)
import Control.Monad.Extra (eitherM, join, mapMaybeM)
import Data.Either (fromRight)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Unique (Unique)
import qualified Data.Unique as Unique
import Development.IDE.GHC.Compat
import qualified Development.IDE.GHC.Compat.Util as Maybes
import Development.IDE.GHC.Error (catchSrcErrors)
import Development.IDE.GHC.Util (lookupPackageConfig)
import Development.IDE.Graph.Classes
import Development.IDE.Types.Exports (ExportsMap, createExportsMap)
import OpenTelemetry.Eventlog (withSpan)
import System.Directory (makeAbsolute)
import System.FilePath
-- | An 'HscEnv' with equality. Two values are considered equal
-- if they are created with the same call to 'newHscEnvEq'.
data HscEnvEq = HscEnvEq
{ envUnique :: !Unique
, hscEnv :: !HscEnv
, deps :: [(UnitId, DynFlags)]
-- ^ In memory components for this HscEnv
-- This is only used at the moment for the import dirs in
-- the DynFlags
, envImportPaths :: Maybe (Set FilePath)
-- ^ If Just, import dirs originally configured in this env
-- If Nothing, the env import dirs are unaltered
, envPackageExports :: IO ExportsMap
, envVisibleModuleNames :: IO (Maybe [ModuleName])
-- ^ 'listVisibleModuleNames' is a pure function,
-- but it could panic due to a ghc bug: https://github.com/haskell/haskell-language-server/issues/1365
-- So it's wrapped in IO here for error handling
-- If Nothing, 'listVisibleModuleNames' panic
}
-- | Wrap an 'HscEnv' into an 'HscEnvEq'.
newHscEnvEq :: FilePath -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
newHscEnvEq cradlePath hscEnv0 deps = do
let relativeToCradle = (takeDirectory cradlePath </>)
hscEnv = removeImportPaths hscEnv0
-- Make Absolute since targets are also absolute
importPathsCanon <-
mapM makeAbsolute $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0)
newHscEnvEqWithImportPaths (Just $ Set.fromList importPathsCanon) hscEnv deps
newHscEnvEqWithImportPaths :: Maybe (Set FilePath) -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do
let dflags = hsc_dflags hscEnv
envUnique <- Unique.newUnique
-- it's very important to delay the package exports computation
envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do
-- compute the package imports
let pkgst = unitState hscEnv
depends = explicitUnits pkgst
modules =
[ m
| d <- depends
, Just pkg <- [lookupPackageConfig d hscEnv]
, (modName, maybeOtherPkgMod) <- unitExposedModules pkg
, let m = case maybeOtherPkgMod of
-- When module is re-exported from another package,
-- the origin module is represented by value in Just
Just otherPkgMod -> otherPkgMod
Nothing -> mkModule (unitInfoId pkg) modName
]
doOne m = do
modIface <- initIfaceLoad hscEnv $
loadInterface "" m (ImportByUser NotBoot)
return $ case modIface of
Maybes.Failed _r -> Nothing
Maybes.Succeeded mi -> Just mi
modIfaces <- mapMaybeM doOne modules
return $ createExportsMap modIfaces
-- similar to envPackageExports, evaluated lazily
envVisibleModuleNames <- onceAsync $
fromRight Nothing
<$> catchSrcErrors
dflags
"listVisibleModuleNames"
(evaluate . force . Just $ listVisibleModuleNames hscEnv)
return HscEnvEq{..}
-- | Wrap an 'HscEnv' into an 'HscEnvEq'.
newHscEnvEqPreserveImportPaths
:: HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
newHscEnvEqPreserveImportPaths = newHscEnvEqWithImportPaths Nothing
-- | Unwrap the 'HscEnv' with the original import paths.
-- Used only for locating imports
hscEnvWithImportPaths :: HscEnvEq -> HscEnv
hscEnvWithImportPaths HscEnvEq{..}
| Just imps <- envImportPaths
= hscSetFlags (setImportPaths (Set.toList imps) (hsc_dflags hscEnv)) hscEnv
| otherwise
= hscEnv
removeImportPaths :: HscEnv -> HscEnv
removeImportPaths hsc = hscSetFlags (setImportPaths [] (hsc_dflags hsc)) hsc
instance Show HscEnvEq where
show HscEnvEq{envUnique} = "HscEnvEq " ++ show (Unique.hashUnique envUnique)
instance Eq HscEnvEq where
a == b = envUnique a == envUnique b
instance NFData HscEnvEq where
rnf (HscEnvEq a b c d _ _) =
-- deliberately skip the package exports map and visible module names
rnf (Unique.hashUnique a) `seq` b `seq` c `seq` rnf d
instance Hashable HscEnvEq where
hashWithSalt s = hashWithSalt s . envUnique
-- | Given an action, produce a wrapped action that runs at most once.
-- The action is run in an async so it won't be killed by async exceptions
-- If the function raises an exception, the same exception will be reraised each time.
onceAsync :: IO a -> IO (IO a)
onceAsync act = do
var <- newVar OncePending
let run as = eitherM throwIO pure (waitCatch as)
pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of
OnceRunning x -> pure (v, unmask $ run x)
OncePending -> do
x <- async (unmask act)
pure (OnceRunning x, unmask $ run x)
data Once a = OncePending | OnceRunning (Async a)

View File

@ -1,124 +1,124 @@
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE RankNTypes #-}
-- | Custom SYB traversals explicitly designed for operating over the GHC AST.
module Generics.SYB.GHC
( genericIsSubspan,
mkBindListT,
everywhereM',
smallestM,
largestM
) where
import Control.Monad
import Data.Functor.Compose (Compose (Compose))
import Data.Monoid (Any (Any))
import Development.IDE.GHC.Compat
import Development.IDE.Graph.Classes
import Generics.SYB
-- | A generic query intended to be used for calling 'smallestM' and
-- 'largestM'. If the current node is a 'Located', returns whether or not the
-- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which
-- indicates uncertainty. The search strategy in 'smallestM' et al. will
-- continue searching uncertain nodes.
genericIsSubspan ::
forall ast.
Typeable ast =>
-- | The type of nodes we'd like to consider.
Proxy (Located ast) ->
SrcSpan ->
GenericQ (Maybe (Bool, ast))
genericIsSubspan _ dst = mkQ Nothing $ \case
(L span ast :: Located ast) -> Just (dst `isSubspanOf` span, ast)
-- | Lift a function that replaces a value with several values into a generic
-- function. The result doesn't perform any searching, so should be driven via
-- 'everywhereM' or friends.
--
-- The 'Int' argument is the index in the list being bound.
mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m
mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]
-- | Apply a monadic transformation everywhere in a top-down manner.
everywhereM' :: forall m. Monad m => GenericM m -> GenericM m
everywhereM' f = go
where
go :: GenericM m
go = gmapM go <=< f
------------------------------------------------------------------------------
-- Custom SYB machinery
------------------------------------------------------------------------------
-- | Generic monadic transformations that return side-channel data.
type GenericMQ r m = forall a. Data a => a -> m (r, a)
------------------------------------------------------------------------------
-- | Apply the given 'GenericM' at all every node whose children fail the
-- 'GenericQ', but which passes the query itself.
--
-- The query must be a monotonic function when it returns 'Just'. That is, if
-- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It
-- is the True-to-false edge of the query that triggers the transformation.
--
-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
-- with data nodes, so for any given node we can only definitely return an
-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
-- used.
smallestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m
smallestM q f = fmap snd . go
where
go :: GenericMQ Any m
go x = do
case q x of
Nothing -> gmapMQ go x
Just (True, a) -> do
it@(r, x') <- gmapMQ go x
case r of
Any True -> pure it
Any False -> fmap (Any True,) $ f a x'
Just (False, _) -> pure (mempty, x)
------------------------------------------------------------------------------
-- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but
-- don't descend into children if the query matches. Because this traversal is
-- root-first, this policy will find the largest subtrees for which the query
-- holds true.
--
-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
-- with data nodes, so for any given node we can only definitely return an
-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
-- used.
largestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m
largestM q f = go
where
go :: GenericM m
go x = do
case q x of
Just (True, a) -> f a x
Just (False, _) -> pure x
Nothing -> gmapM go x
newtype MonadicQuery r m a = MonadicQuery
{ runMonadicQuery :: m (r, a)
}
deriving stock (Functor)
deriving Applicative via Compose m ((,) r)
------------------------------------------------------------------------------
-- | Like 'gmapM', but also returns side-channel data.
gmapMQ ::
forall f r a. (Monoid r, Data a, Applicative f) =>
(forall d. Data d => d -> f (r, d)) ->
a ->
f (r, a)
gmapMQ f = runMonadicQuery . gfoldl k pure
where
k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b
k c x = c <*> MonadicQuery (f x)
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE RankNTypes #-}
-- | Custom SYB traversals explicitly designed for operating over the GHC AST.
module Generics.SYB.GHC
( genericIsSubspan,
mkBindListT,
everywhereM',
smallestM,
largestM
) where
import Control.Monad
import Data.Functor.Compose (Compose (Compose))
import Data.Monoid (Any (Any))
import Development.IDE.GHC.Compat
import Development.IDE.Graph.Classes
import Generics.SYB
-- | A generic query intended to be used for calling 'smallestM' and
-- 'largestM'. If the current node is a 'Located', returns whether or not the
-- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which
-- indicates uncertainty. The search strategy in 'smallestM' et al. will
-- continue searching uncertain nodes.
genericIsSubspan ::
forall ast.
Typeable ast =>
-- | The type of nodes we'd like to consider.
Proxy (Located ast) ->
SrcSpan ->
GenericQ (Maybe (Bool, ast))
genericIsSubspan _ dst = mkQ Nothing $ \case
(L span ast :: Located ast) -> Just (dst `isSubspanOf` span, ast)
-- | Lift a function that replaces a value with several values into a generic
-- function. The result doesn't perform any searching, so should be driven via
-- 'everywhereM' or friends.
--
-- The 'Int' argument is the index in the list being bound.
mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m
mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]
-- | Apply a monadic transformation everywhere in a top-down manner.
everywhereM' :: forall m. Monad m => GenericM m -> GenericM m
everywhereM' f = go
where
go :: GenericM m
go = gmapM go <=< f
------------------------------------------------------------------------------
-- Custom SYB machinery
------------------------------------------------------------------------------
-- | Generic monadic transformations that return side-channel data.
type GenericMQ r m = forall a. Data a => a -> m (r, a)
------------------------------------------------------------------------------
-- | Apply the given 'GenericM' at all every node whose children fail the
-- 'GenericQ', but which passes the query itself.
--
-- The query must be a monotonic function when it returns 'Just'. That is, if
-- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It
-- is the True-to-false edge of the query that triggers the transformation.
--
-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
-- with data nodes, so for any given node we can only definitely return an
-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
-- used.
smallestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m
smallestM q f = fmap snd . go
where
go :: GenericMQ Any m
go x = do
case q x of
Nothing -> gmapMQ go x
Just (True, a) -> do
it@(r, x') <- gmapMQ go x
case r of
Any True -> pure it
Any False -> fmap (Any True,) $ f a x'
Just (False, _) -> pure (mempty, x)
------------------------------------------------------------------------------
-- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but
-- don't descend into children if the query matches. Because this traversal is
-- root-first, this policy will find the largest subtrees for which the query
-- holds true.
--
-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
-- with data nodes, so for any given node we can only definitely return an
-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
-- used.
largestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m
largestM q f = go
where
go :: GenericM m
go x = do
case q x of
Just (True, a) -> f a x
Just (False, _) -> pure x
Nothing -> gmapM go x
newtype MonadicQuery r m a = MonadicQuery
{ runMonadicQuery :: m (r, a)
}
deriving stock (Functor)
deriving Applicative via Compose m ((,) r)
------------------------------------------------------------------------------
-- | Like 'gmapM', but also returns side-channel data.
gmapMQ ::
forall f r a. (Monoid r, Data a, Applicative f) =>
(forall d. Data d => d -> f (r, d)) ->
a ->
f (r, a)
gmapMQ f = runMonadicQuery . gfoldl k pure
where
k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b
k c x = c <*> MonadicQuery (f x)

View File

@ -1,240 +1,240 @@
// Type definitions for Flot
// Project: http://www.flotcharts.org/
// Definitions by: Matt Burland <https://github.com/burlandm>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
declare module jquery.flot {
interface plotOptions {
colors?: any[];
series?: seriesOptions;
legend?: legendOptions;
xaxis?: axisOptions;
yaxis?: axisOptions;
xaxes?: axisOptions[];
yaxes?: axisOptions[];
grid?: gridOptions;
interaction?: interaction;
hooks?: hooks;
}
interface hooks {
processOptions: { (plot: plot, options: plotOptions): void; } [];
processRawData: { (plot: plot, series: dataSeries, data: any[], datapoints: datapoints): void; }[];
processDatapoints: { (plot: plot, series: dataSeries, datapoints: datapoints): void; }[];
processOffset: { (plot: plot, offset: canvasPoint): void; }[];
drawBackground: { (plot: plot, context: CanvasRenderingContext2D): void; }[];
drawSeries: { (plot: plot, context: CanvasRenderingContext2D, series: dataSeries): void; }[];
draw: { (plot: plot, context: CanvasRenderingContext2D): void; }[];
bindEvents: { (plot: plot, eventHolder: JQuery): void; }[];
drawOverlay: { (plot: plot, context: CanvasRenderingContext2D): void; }[];
shutdown: { (plot: plot, eventHolder: JQuery): void; }[];
}
interface interaction {
redrawOverlayInterval?: number;
}
interface gridOptions {
show?: boolean;
aboveData?: boolean;
color?: any; // color
backgroundColor?: any; //color/gradient or null
margin?: any; // number or margin object
labelMargin?: number;
axisMargin?: number;
markings?: any; //array of markings or (fn: axes -> array of markings)
borderWidth?: any; // number or width object
borderColor?: any; // color or null
minBorderMargin?: number; // or null
clickable?: boolean;
hoverable?: boolean;
autoHighlight?: boolean;
mouseActiveRadius?: number;
tickColor?: any;
markingsColor?: any;
markingsLineWidth?: number;
}
interface legendOptions {
show?: boolean;
labelFormatter?: (label: string, series: any) => string; // null or (fn: string, series object -> string)
labelBoxBorderColor?: any; //color
noColumns?: number;
position?: string; //"ne" or "nw" or "se" or "sw"
margin?: any; //number of pixels or [x margin, y margin]
backgroundColor?: any; //null or color
backgroundOpacity?: number; // between 0 and 1
container?: JQuery; // null or jQuery object/DOM element/jQuery expression
sorted?: any; //null/false, true, "ascending", "descending" or a comparator
}
interface seriesOptions {
color?: any; // color or number
label?: string;
lines?: linesOptions;
bars?: barsOptions;
points?: pointsOptions;
xaxis?: number;
yaxis?: number;
clickable?: boolean;
hoverable?: boolean;
shadowSize?: number;
highlightColor?: any;
// Type definitions for Flot
// Project: http://www.flotcharts.org/
// Definitions by: Matt Burland <https://github.com/burlandm>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
declare module jquery.flot {
interface plotOptions {
colors?: any[];
series?: seriesOptions;
legend?: legendOptions;
xaxis?: axisOptions;
yaxis?: axisOptions;
xaxes?: axisOptions[];
yaxes?: axisOptions[];
grid?: gridOptions;
interaction?: interaction;
hooks?: hooks;
}
interface hooks {
processOptions: { (plot: plot, options: plotOptions): void; } [];
processRawData: { (plot: plot, series: dataSeries, data: any[], datapoints: datapoints): void; }[];
processDatapoints: { (plot: plot, series: dataSeries, datapoints: datapoints): void; }[];
processOffset: { (plot: plot, offset: canvasPoint): void; }[];
drawBackground: { (plot: plot, context: CanvasRenderingContext2D): void; }[];
drawSeries: { (plot: plot, context: CanvasRenderingContext2D, series: dataSeries): void; }[];
draw: { (plot: plot, context: CanvasRenderingContext2D): void; }[];
bindEvents: { (plot: plot, eventHolder: JQuery): void; }[];
drawOverlay: { (plot: plot, context: CanvasRenderingContext2D): void; }[];
shutdown: { (plot: plot, eventHolder: JQuery): void; }[];
}
interface interaction {
redrawOverlayInterval?: number;
}
interface gridOptions {
show?: boolean;
aboveData?: boolean;
color?: any; // color
backgroundColor?: any; //color/gradient or null
margin?: any; // number or margin object
labelMargin?: number;
axisMargin?: number;
markings?: any; //array of markings or (fn: axes -> array of markings)
borderWidth?: any; // number or width object
borderColor?: any; // color or null
minBorderMargin?: number; // or null
clickable?: boolean;
hoverable?: boolean;
autoHighlight?: boolean;
mouseActiveRadius?: number;
tickColor?: any;
markingsColor?: any;
markingsLineWidth?: number;
}
interface legendOptions {
show?: boolean;
labelFormatter?: (label: string, series: any) => string; // null or (fn: string, series object -> string)
labelBoxBorderColor?: any; //color
noColumns?: number;
position?: string; //"ne" or "nw" or "se" or "sw"
margin?: any; //number of pixels or [x margin, y margin]
backgroundColor?: any; //null or color
backgroundOpacity?: number; // between 0 and 1
container?: JQuery; // null or jQuery object/DOM element/jQuery expression
sorted?: any; //null/false, true, "ascending", "descending" or a comparator
}
interface seriesOptions {
color?: any; // color or number
label?: string;
lines?: linesOptions;
bars?: barsOptions;
points?: pointsOptions;
xaxis?: number;
yaxis?: number;
clickable?: boolean;
hoverable?: boolean;
shadowSize?: number;
highlightColor?: any;
stack?: boolean; // NEIL: Since we use the Stack plugin
}
interface dataSeries extends seriesOptions {
data: any[];
}
interface axisOptions {
show?: boolean; // null or true/false
position?: string; // "bottom" or "top" or "left" or "right"
color?: any; // null or color spec
tickColor?: any; // null or color spec
font?: any; // null or font spec object
min?: number;
max?: number;
autoscaleMargin?: number;
transform?: (v: number) => number; // null or fn: number -> number
inverseTransform?: (v: number) => number; // null or fn: number -> number
ticks?: any; // null or number or ticks array or (fn: axis -> ticks array)
tickSize?: any; // number or array
minTickSize?: any; // number or array
tickFormatter?: (t: number, a?: axis) => string; // (fn: number, object -> string) or string
tickDecimals?: number;
labelWidth?: number;
labelHeight?: number;
reserveSpace?: boolean;
tickLength?: number;
alignTicksWithAxis?: number;
}
interface seriesTypeBase {
show?: boolean;
lineWidth?: number;
fill?: any; //boolean or number
fillColor?: any; //null or color/gradient
}
interface linesOptions extends seriesTypeBase {
steps?: boolean;
}
interface barsOptions extends seriesTypeBase {
barWidth?: number;
align?: string;
horizontal?: boolean;
}
interface pointsOptions extends seriesTypeBase {
radius?: number;
symbol?: any;
}
interface gradient {
colors: any[];
}
interface item {
datapoint: number[]; // the point, e.g. [0, 2]
dataIndex: number; // the index of the point in the data array
series: dataSeries; //the series object
seriesIndex: number; //the index of the series
pageX: number;
pageY: number; //the global screen coordinates of the point
}
interface datapoints {
points: number[];
pointsize: number;
format: datapointFormat[];
}
interface datapointFormat {
x?: boolean;
y?: boolean;
number: boolean;
required: boolean;
defaultValue?: number;
}
interface point {
x: number;
y: number;
}
interface offset {
left: number;
top: number;
}
interface canvasPoint {
top: number;
left: number;
bottom?: number;
right?: number;
}
interface axes {
xaxis: axis;
yaxis: axis;
x2axis?: axis;
y2axis?: axis;
}
interface axis extends axisOptions {
options: axisOptions;
p2c(point: point):canvasPoint;
c2p(canvasPoint: canvasPoint):point;
}
interface plugin {
init(options: plotOptions): any;
options?: any;
name?: string;
version?: string;
}
interface plot {
highlight(series: dataSeries, datapoint: item): void;
unhighlight(): void;
unhighlight(series: dataSeries, datapoint: item): void;
setData(data: any): void;
setupGrid(): void;
draw(): void;
triggerRedrawOverlay(): void;
width(): number;
height(): number;
offset(): JQueryCoordinates;
pointOffset(point: point): offset;
resize(): void;
shutdown(): void;
getData(): dataSeries[];
getAxes(): axes;
getXAxes(): axis[];
getYAxes(): axis[];
getPlaceholder(): JQuery;
getCanvas(): HTMLCanvasElement;
getPlotOffset(): canvasPoint;
getOptions(): plotOptions;
}
interface plotStatic {
(placeholder: JQuery, data: dataSeries[], options?: plotOptions): plot;
(placeholder: JQuery, data: any[], options?: plotOptions): plot;
plugins: plugin[];
}
}
interface JQueryStatic {
plot: jquery.flot.plotStatic;
}
}
interface dataSeries extends seriesOptions {
data: any[];
}
interface axisOptions {
show?: boolean; // null or true/false
position?: string; // "bottom" or "top" or "left" or "right"
color?: any; // null or color spec
tickColor?: any; // null or color spec
font?: any; // null or font spec object
min?: number;
max?: number;
autoscaleMargin?: number;
transform?: (v: number) => number; // null or fn: number -> number
inverseTransform?: (v: number) => number; // null or fn: number -> number
ticks?: any; // null or number or ticks array or (fn: axis -> ticks array)
tickSize?: any; // number or array
minTickSize?: any; // number or array
tickFormatter?: (t: number, a?: axis) => string; // (fn: number, object -> string) or string
tickDecimals?: number;
labelWidth?: number;
labelHeight?: number;
reserveSpace?: boolean;
tickLength?: number;
alignTicksWithAxis?: number;
}
interface seriesTypeBase {
show?: boolean;
lineWidth?: number;
fill?: any; //boolean or number
fillColor?: any; //null or color/gradient
}
interface linesOptions extends seriesTypeBase {
steps?: boolean;
}
interface barsOptions extends seriesTypeBase {
barWidth?: number;
align?: string;
horizontal?: boolean;
}
interface pointsOptions extends seriesTypeBase {
radius?: number;
symbol?: any;
}
interface gradient {
colors: any[];
}
interface item {
datapoint: number[]; // the point, e.g. [0, 2]
dataIndex: number; // the index of the point in the data array
series: dataSeries; //the series object
seriesIndex: number; //the index of the series
pageX: number;
pageY: number; //the global screen coordinates of the point
}
interface datapoints {
points: number[];
pointsize: number;
format: datapointFormat[];
}
interface datapointFormat {
x?: boolean;
y?: boolean;
number: boolean;
required: boolean;
defaultValue?: number;
}
interface point {
x: number;
y: number;
}
interface offset {
left: number;
top: number;
}
interface canvasPoint {
top: number;
left: number;
bottom?: number;
right?: number;
}
interface axes {
xaxis: axis;
yaxis: axis;
x2axis?: axis;
y2axis?: axis;
}
interface axis extends axisOptions {
options: axisOptions;
p2c(point: point):canvasPoint;
c2p(canvasPoint: canvasPoint):point;
}
interface plugin {
init(options: plotOptions): any;
options?: any;
name?: string;
version?: string;
}
interface plot {
highlight(series: dataSeries, datapoint: item): void;
unhighlight(): void;
unhighlight(series: dataSeries, datapoint: item): void;
setData(data: any): void;
setupGrid(): void;
draw(): void;
triggerRedrawOverlay(): void;
width(): number;
height(): number;
offset(): JQueryCoordinates;
pointOffset(point: point): offset;
resize(): void;
shutdown(): void;
getData(): dataSeries[];
getAxes(): axes;
getXAxes(): axis[];
getYAxes(): axis[];
getPlaceholder(): JQuery;
getCanvas(): HTMLCanvasElement;
getPlotOffset(): canvasPoint;
getOptions(): plotOptions;
}
interface plotStatic {
(placeholder: JQuery, data: dataSeries[], options?: plotOptions): plot;
(placeholder: JQuery, data: any[], options?: plotOptions): plot;
plugins: plugin[];
}
}
interface JQueryStatic {
plot: jquery.flot.plotStatic;
}

View File

@ -1,8 +1,8 @@
module Development.IDE.Graph.Classes(
Show(..), Typeable, Eq(..), Hashable(..), NFData(..)
) where
import Control.DeepSeq
import Data.Hashable
import Data.Typeable
module Development.IDE.Graph.Classes(
Show(..), Typeable, Eq(..), Hashable(..), NFData(..)
) where
import Control.DeepSeq
import Data.Hashable
import Data.Typeable

View File

@ -1,137 +1,137 @@
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Development.IDE.Graph.Internal.Action
( ShakeValue
, actionFork
, actionBracket
, actionCatch
, actionFinally
, alwaysRerun
, apply1
, apply
, parallel
, reschedule
, runActions
, Development.IDE.Graph.Internal.Action.getDirtySet
, getKeysAndVisitedAge
) where
import Control.Concurrent.Async
import Control.Exception
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Data.IORef
import Development.IDE.Graph.Classes
import Development.IDE.Graph.Internal.Database
import Development.IDE.Graph.Internal.Rules (RuleResult)
import Development.IDE.Graph.Internal.Types
import System.Exit
type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, NFData a)
-- | Always rerun this rule when dirty, regardless of the dependencies.
alwaysRerun :: Action ()
alwaysRerun = do
ref <- Action $ asks actionDeps
liftIO $ modifyIORef ref (AlwaysRerunDeps [] <>)
-- No-op for now
reschedule :: Double -> Action ()
reschedule _ = pure ()
parallel :: [Action a] -> Action [a]
parallel [] = pure []
parallel [x] = fmap (:[]) x
parallel xs = do
a <- Action ask
deps <- liftIO $ readIORef $ actionDeps a
case deps of
UnknownDeps ->
-- if we are already in the rerun mode, nothing we do is going to impact our state
liftIO $ mapConcurrently (ignoreState a) xs
deps -> do
(newDeps, res) <- liftIO $ unzip <$> mapConcurrently (usingState a) xs
liftIO $ writeIORef (actionDeps a) $ mconcat $ deps : newDeps
pure res
where
usingState a x = do
ref <- newIORef mempty
res <- runReaderT (fromAction x) a{actionDeps=ref}
deps <- readIORef ref
pure (deps, res)
ignoreState :: SAction -> Action b -> IO b
ignoreState a x = do
ref <- newIORef mempty
runReaderT (fromAction x) a{actionDeps=ref}
actionFork :: Action a -> (Async a -> Action b) -> Action b
actionFork act k = do
a <- Action ask
deps <- liftIO $ readIORef $ actionDeps a
let db = actionDatabase a
case deps of
UnknownDeps -> do
-- if we are already in the rerun mode, nothing we do is going to impact our state
[res] <- liftIO $ withAsync (ignoreState a act) $ \as -> runActions db [k as]
return res
_ ->
error "please help me"
isAsyncException :: SomeException -> Bool
isAsyncException e
| Just (_ :: AsyncCancelled) <- fromException e = True
| Just (_ :: AsyncException) <- fromException e = True
| Just (_ :: ExitCode) <- fromException e = True
| otherwise = False
actionCatch :: Exception e => Action a -> (e -> Action a) -> Action a
actionCatch a b = do
v <- Action ask
Action $ lift $ catchJust f (runReaderT (fromAction a) v) (\x -> runReaderT (fromAction (b x)) v)
where
-- Catch only catches exceptions that were caused by this code, not those that
-- are a result of program termination
f e | isAsyncException e = Nothing
| otherwise = fromException e
actionBracket :: IO a -> (a -> IO b) -> (a -> Action c) -> Action c
actionBracket a b c = do
v <- Action ask
Action $ lift $ bracket a b (\x -> runReaderT (fromAction (c x)) v)
actionFinally :: Action a -> IO b -> Action a
actionFinally a b = do
v <- Action ask
Action $ lift $ finally (runReaderT (fromAction a) v) b
apply1 :: (RuleResult key ~ value, ShakeValue key, Typeable value) => key -> Action value
apply1 k = head <$> apply [k]
apply :: (RuleResult key ~ value, ShakeValue key, Typeable value) => [key] -> Action [value]
apply ks = do
db <- Action $ asks actionDatabase
(is, vs) <- liftIO $ build db ks
ref <- Action $ asks actionDeps
liftIO $ modifyIORef ref (ResultDeps is <>)
pure vs
runActions :: Database -> [Action a] -> IO [a]
runActions db xs = do
deps <- newIORef mempty
runReaderT (fromAction $ parallel xs) $ SAction db deps
-- | Returns the set of dirty keys annotated with their age (in # of builds)
getDirtySet :: Action [(Key, Int)]
getDirtySet = do
db <- getDatabase
liftIO $ Development.IDE.Graph.Internal.Database.getDirtySet db
getKeysAndVisitedAge :: Action [(Key, Int)]
getKeysAndVisitedAge = do
db <- getDatabase
liftIO $ Development.IDE.Graph.Internal.Database.getKeysAndVisitAge db
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Development.IDE.Graph.Internal.Action
( ShakeValue
, actionFork
, actionBracket
, actionCatch
, actionFinally
, alwaysRerun
, apply1
, apply
, parallel
, reschedule
, runActions
, Development.IDE.Graph.Internal.Action.getDirtySet
, getKeysAndVisitedAge
) where
import Control.Concurrent.Async
import Control.Exception
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Data.IORef
import Development.IDE.Graph.Classes
import Development.IDE.Graph.Internal.Database
import Development.IDE.Graph.Internal.Rules (RuleResult)
import Development.IDE.Graph.Internal.Types
import System.Exit
type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, NFData a)
-- | Always rerun this rule when dirty, regardless of the dependencies.
alwaysRerun :: Action ()
alwaysRerun = do
ref <- Action $ asks actionDeps
liftIO $ modifyIORef ref (AlwaysRerunDeps [] <>)
-- No-op for now
reschedule :: Double -> Action ()
reschedule _ = pure ()
parallel :: [Action a] -> Action [a]
parallel [] = pure []
parallel [x] = fmap (:[]) x
parallel xs = do
a <- Action ask
deps <- liftIO $ readIORef $ actionDeps a
case deps of
UnknownDeps ->
-- if we are already in the rerun mode, nothing we do is going to impact our state
liftIO $ mapConcurrently (ignoreState a) xs
deps -> do
(newDeps, res) <- liftIO $ unzip <$> mapConcurrently (usingState a) xs
liftIO $ writeIORef (actionDeps a) $ mconcat $ deps : newDeps
pure res
where
usingState a x = do
ref <- newIORef mempty
res <- runReaderT (fromAction x) a{actionDeps=ref}
deps <- readIORef ref
pure (deps, res)
ignoreState :: SAction -> Action b -> IO b
ignoreState a x = do
ref <- newIORef mempty
runReaderT (fromAction x) a{actionDeps=ref}
actionFork :: Action a -> (Async a -> Action b) -> Action b
actionFork act k = do
a <- Action ask
deps <- liftIO $ readIORef $ actionDeps a
let db = actionDatabase a
case deps of
UnknownDeps -> do
-- if we are already in the rerun mode, nothing we do is going to impact our state
[res] <- liftIO $ withAsync (ignoreState a act) $ \as -> runActions db [k as]
return res
_ ->
error "please help me"
isAsyncException :: SomeException -> Bool
isAsyncException e
| Just (_ :: AsyncCancelled) <- fromException e = True
| Just (_ :: AsyncException) <- fromException e = True
| Just (_ :: ExitCode) <- fromException e = True
| otherwise = False
actionCatch :: Exception e => Action a -> (e -> Action a) -> Action a
actionCatch a b = do
v <- Action ask
Action $ lift $ catchJust f (runReaderT (fromAction a) v) (\x -> runReaderT (fromAction (b x)) v)
where
-- Catch only catches exceptions that were caused by this code, not those that
-- are a result of program termination
f e | isAsyncException e = Nothing
| otherwise = fromException e
actionBracket :: IO a -> (a -> IO b) -> (a -> Action c) -> Action c
actionBracket a b c = do
v <- Action ask
Action $ lift $ bracket a b (\x -> runReaderT (fromAction (c x)) v)
actionFinally :: Action a -> IO b -> Action a
actionFinally a b = do
v <- Action ask
Action $ lift $ finally (runReaderT (fromAction a) v) b
apply1 :: (RuleResult key ~ value, ShakeValue key, Typeable value) => key -> Action value
apply1 k = head <$> apply [k]
apply :: (RuleResult key ~ value, ShakeValue key, Typeable value) => [key] -> Action [value]
apply ks = do
db <- Action $ asks actionDatabase
(is, vs) <- liftIO $ build db ks
ref <- Action $ asks actionDeps
liftIO $ modifyIORef ref (ResultDeps is <>)
pure vs
runActions :: Database -> [Action a] -> IO [a]
runActions db xs = do
deps <- newIORef mempty
runReaderT (fromAction $ parallel xs) $ SAction db deps
-- | Returns the set of dirty keys annotated with their age (in # of builds)
getDirtySet :: Action [(Key, Int)]
getDirtySet = do
db <- getDatabase
liftIO $ Development.IDE.Graph.Internal.Database.getDirtySet db
getKeysAndVisitedAge :: Action [(Key, Int)]
getKeysAndVisitedAge = do
db <- getDatabase
liftIO $ Development.IDE.Graph.Internal.Database.getKeysAndVisitAge db

View File

@ -1,58 +1,58 @@
-- We deliberately want to ensure the function we add to the rule database
-- has the constraints we need on it when we get it out.
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Development.IDE.Graph.Internal.Rules where
import Control.Exception.Extra
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import qualified Data.ByteString as BS
import Data.Dynamic
import qualified Data.HashMap.Strict as Map
import Data.IORef
import Data.Maybe
import Data.Typeable
import Development.IDE.Graph.Classes
import Development.IDE.Graph.Internal.Types
-- | The type mapping between the @key@ or a rule and the resulting @value@.
-- See 'addBuiltinRule' and 'Development.Shake.Rule.apply'.
type family RuleResult key -- = value
action :: Action a -> Rules ()
action x = do
ref <- Rules $ asks rulesActions
liftIO $ modifyIORef' ref (void x:)
addRule
:: forall key value .
(RuleResult key ~ value, Typeable key, Hashable key, Eq key, Typeable value)
=> (key -> Maybe BS.ByteString -> RunMode -> Action (RunResult value))
-> Rules ()
addRule f = do
ref <- Rules $ asks rulesMap
liftIO $ modifyIORef' ref $ Map.insert (typeRep (Proxy :: Proxy key)) (toDyn f2)
where
f2 :: Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value)
f2 (Key a) b c = do
v <- f (fromJust $ cast a :: key) b c
v <- liftIO $ evaluate v
pure $ Value . toDyn <$> v
runRule
:: TheRules -> Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value)
runRule rules key@(Key t) bs mode = case Map.lookup (typeOf t) rules of
Nothing -> liftIO $ errorIO "Could not find key"
Just x -> unwrapDynamic x key bs mode
runRules :: Dynamic -> Rules () -> IO (TheRules, [Action ()])
runRules rulesExtra (Rules rules) = do
rulesActions <- newIORef []
rulesMap <- newIORef Map.empty
runReaderT rules SRules{..}
(,) <$> readIORef rulesMap <*> readIORef rulesActions
-- We deliberately want to ensure the function we add to the rule database
-- has the constraints we need on it when we get it out.
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Development.IDE.Graph.Internal.Rules where
import Control.Exception.Extra
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import qualified Data.ByteString as BS
import Data.Dynamic
import qualified Data.HashMap.Strict as Map
import Data.IORef
import Data.Maybe
import Data.Typeable
import Development.IDE.Graph.Classes
import Development.IDE.Graph.Internal.Types
-- | The type mapping between the @key@ or a rule and the resulting @value@.
-- See 'addBuiltinRule' and 'Development.Shake.Rule.apply'.
type family RuleResult key -- = value
action :: Action a -> Rules ()
action x = do
ref <- Rules $ asks rulesActions
liftIO $ modifyIORef' ref (void x:)
addRule
:: forall key value .
(RuleResult key ~ value, Typeable key, Hashable key, Eq key, Typeable value)
=> (key -> Maybe BS.ByteString -> RunMode -> Action (RunResult value))
-> Rules ()
addRule f = do
ref <- Rules $ asks rulesMap
liftIO $ modifyIORef' ref $ Map.insert (typeRep (Proxy :: Proxy key)) (toDyn f2)
where
f2 :: Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value)
f2 (Key a) b c = do
v <- f (fromJust $ cast a :: key) b c
v <- liftIO $ evaluate v
pure $ Value . toDyn <$> v
runRule
:: TheRules -> Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value)
runRule rules key@(Key t) bs mode = case Map.lookup (typeOf t) rules of
Nothing -> liftIO $ errorIO "Could not find key"
Just x -> unwrapDynamic x key bs mode
runRules :: Dynamic -> Rules () -> IO (TheRules, [Action ()])
runRules rulesExtra (Rules rules) = do
rulesActions <- newIORef []
rulesMap <- newIORef Map.empty
runReaderT rules SRules{..}
(,) <$> readIORef rulesMap <*> readIORef rulesActions

View File

@ -1,12 +1,12 @@
-- | Fake cabal module for local building
module Paths_hls_graph(getDataDir, version) where
import Data.Version.Extra
-- If hls_graph can't find files in the data directory it tries relative to the executable
getDataDir :: IO FilePath
getDataDir = pure "random_path_that_cannot_possibly_exist"
version :: Version
version = makeVersion [0,0]
-- | Fake cabal module for local building
module Paths_hls_graph(getDataDir, version) where
import Data.Version.Extra
-- If hls_graph can't find files in the data directory it tries relative to the executable
getDataDir :: IO FilePath
getDataDir = pure "random_path_that_cannot_possibly_exist"
version :: Version
version = makeVersion [0,0]

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,20 @@
module InfoUtil
( Eq
, Ord
, Foo (..)
, Bar (..)
, Baz
)
where
import Prelude (Eq, Ord)
data Foo = Foo1 | Foo2
deriving (Eq, Ord)
data Bar = Bar1 | Bar2 | Bar3
deriving (Eq, Ord)
class Baz t
instance Baz Foo
instance Baz Bar
module InfoUtil
( Eq
, Ord
, Foo (..)
, Bar (..)
, Baz
)
where
import Prelude (Eq, Ord)
data Foo = Foo1 | Foo2
deriving (Eq, Ord)
data Bar = Bar1 | Bar2 | Bar3
deriving (Eq, Ord)
class Baz t
instance Baz Foo
instance Baz Bar

View File

@ -32,9 +32,9 @@ import Data.Aeson (FromJSON (..),
Value (Null),
genericParseJSON)
import qualified Data.Aeson as Aeson
import qualified Data.ByteString as BS
import Data.Bifunctor (Bifunctor (first),
second)
import qualified Data.ByteString as BS
import Data.Coerce
import Data.Either (partitionEithers)
import qualified Data.HashMap.Strict as HM

View File

@ -1,79 +1,79 @@
{-# LANGUAGE OverloadedStrings #-}
module Ide.Plugin.StylishHaskell
( descriptor
, provider
)
where
import Control.Monad.IO.Class
import Data.Text (Text)
import qualified Data.Text as T
import Development.IDE hiding (pluginHandlers)
import Development.IDE.GHC.Compat (ModSummary (ms_hspp_opts),
extensionFlags)
import qualified Development.IDE.GHC.Compat.Util as Util
import GHC.LanguageExtensions.Type
import Ide.PluginUtils
import Ide.Types
import Language.Haskell.Stylish
import Language.LSP.Types as J
import System.Directory
import System.FilePath
descriptor :: PluginId -> PluginDescriptor IdeState
descriptor plId = (defaultPluginDescriptor plId)
{ pluginHandlers = mkFormattingHandlers provider
}
-- | Formatter provider of stylish-haskell.
-- Formats the given source in either a given Range or the whole Document.
-- If the provider fails an error is returned that can be displayed to the user.
provider :: FormattingHandler IdeState
provider ide typ contents fp _opts = do
dyn <- fmap (ms_hspp_opts . msrModSummary) $ liftIO $ runAction "stylish-haskell" ide $ use_ GetModSummary fp
let file = fromNormalizedFilePath fp
config <- liftIO $ loadConfigFrom file
mergedConfig <- liftIO $ getMergedConfig dyn config
let (range, selectedContents) = case typ of
FormatText -> (fullRange contents, contents)
FormatRange r -> (normalize r, extractRange r contents)
result = runStylishHaskell file mergedConfig selectedContents
case result of
Left err -> return $ Left $ responseError $ T.pack $ "stylishHaskellCmd: " ++ err
Right new -> return $ Right $ J.List [TextEdit range new]
where
getMergedConfig dyn config
| null (configLanguageExtensions config)
= do
logInfo (ideLogger ide) "stylish-haskell uses the language extensions from DynFlags"
pure
$ config
{ configLanguageExtensions = getExtensions dyn }
| otherwise
= pure config
getExtensions = map showExtension . Util.toList . extensionFlags
showExtension Cpp = "CPP"
showExtension other = show other
-- | Recursively search in every directory of the given filepath for .stylish-haskell.yaml.
-- If no such file has been found, return default config.
loadConfigFrom :: FilePath -> IO Config
loadConfigFrom file = do
currDir <- getCurrentDirectory
setCurrentDirectory (takeDirectory file)
config <- loadConfig (makeVerbose False) Nothing
setCurrentDirectory currDir
pure config
-- | Run stylish-haskell on the given text with the given configuration.
runStylishHaskell :: FilePath -- ^ Location of the file being formatted. Used for error message
-> Config -- ^ Configuration for stylish-haskell
-> Text -- ^ Text to format
-> Either String Text -- ^ Either formatted Text or an error message
runStylishHaskell file config = fmap fromLines . fmt . toLines
where
fromLines = T.pack . unlines
fmt = runSteps (configLanguageExtensions config) (Just file) (configSteps config)
toLines = lines . T.unpack
{-# LANGUAGE OverloadedStrings #-}
module Ide.Plugin.StylishHaskell
( descriptor
, provider
)
where
import Control.Monad.IO.Class
import Data.Text (Text)
import qualified Data.Text as T
import Development.IDE hiding (pluginHandlers)
import Development.IDE.GHC.Compat (ModSummary (ms_hspp_opts),
extensionFlags)
import qualified Development.IDE.GHC.Compat.Util as Util
import GHC.LanguageExtensions.Type
import Ide.PluginUtils
import Ide.Types
import Language.Haskell.Stylish
import Language.LSP.Types as J
import System.Directory
import System.FilePath
descriptor :: PluginId -> PluginDescriptor IdeState
descriptor plId = (defaultPluginDescriptor plId)
{ pluginHandlers = mkFormattingHandlers provider
}
-- | Formatter provider of stylish-haskell.
-- Formats the given source in either a given Range or the whole Document.
-- If the provider fails an error is returned that can be displayed to the user.
provider :: FormattingHandler IdeState
provider ide typ contents fp _opts = do
dyn <- fmap (ms_hspp_opts . msrModSummary) $ liftIO $ runAction "stylish-haskell" ide $ use_ GetModSummary fp
let file = fromNormalizedFilePath fp
config <- liftIO $ loadConfigFrom file
mergedConfig <- liftIO $ getMergedConfig dyn config
let (range, selectedContents) = case typ of
FormatText -> (fullRange contents, contents)
FormatRange r -> (normalize r, extractRange r contents)
result = runStylishHaskell file mergedConfig selectedContents
case result of
Left err -> return $ Left $ responseError $ T.pack $ "stylishHaskellCmd: " ++ err
Right new -> return $ Right $ J.List [TextEdit range new]
where
getMergedConfig dyn config
| null (configLanguageExtensions config)
= do
logInfo (ideLogger ide) "stylish-haskell uses the language extensions from DynFlags"
pure
$ config
{ configLanguageExtensions = getExtensions dyn }
| otherwise
= pure config
getExtensions = map showExtension . Util.toList . extensionFlags
showExtension Cpp = "CPP"
showExtension other = show other
-- | Recursively search in every directory of the given filepath for .stylish-haskell.yaml.
-- If no such file has been found, return default config.
loadConfigFrom :: FilePath -> IO Config
loadConfigFrom file = do
currDir <- getCurrentDirectory
setCurrentDirectory (takeDirectory file)
config <- loadConfig (makeVerbose False) Nothing
setCurrentDirectory currDir
pure config
-- | Run stylish-haskell on the given text with the given configuration.
runStylishHaskell :: FilePath -- ^ Location of the file being formatted. Used for error message
-> Config -- ^ Configuration for stylish-haskell
-> Text -- ^ Text to format
-> Either String Text -- ^ Either formatted Text or an error message
runStylishHaskell file config = fmap fromLines . fmt . toLines
where
fromLines = T.pack . unlines
fmt = runSteps (configLanguageExtensions config) (Just file) (configSteps config)
toLines = lines . T.unpack

File diff suppressed because it is too large Load Diff

View File

@ -1,151 +1,151 @@
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above
module Ide.Arguments
( Arguments(..)
, GhcideArguments(..)
, PrintVersion(..)
, BiosAction(..)
, getArguments
, haskellLanguageServerVersion
, haskellLanguageServerNumericVersion
) where
import Data.Version
import Development.IDE (IdeState)
import Development.IDE.Main (Command (..), commandP)
import GitHash (giHash, tGitInfoCwdTry)
import Ide.Types (IdePlugins)
import Options.Applicative
import Paths_haskell_language_server
import System.Environment
-- ---------------------------------------------------------------------
data Arguments
= VersionMode PrintVersion
| ProbeToolsMode
| ListPluginsMode
| BiosMode BiosAction
| Ghcide GhcideArguments
| VSCodeExtensionSchemaMode
| DefaultConfigurationMode
data GhcideArguments = GhcideArguments
{argsCommand :: Command
,argsCwd :: Maybe FilePath
,argsShakeProfiling :: Maybe FilePath
,argsTesting :: Bool
,argsExamplePlugin :: Bool
-- These next two are for compatibility with existing hie clients, allowing
-- them to just change the name of the exe and still work.
, argsDebugOn :: Bool
, argsLogFile :: Maybe String
, argsThreads :: Int
, argsProjectGhcVersion :: Bool
} deriving Show
data PrintVersion
= PrintVersion
| PrintNumericVersion
deriving (Show, Eq, Ord)
data BiosAction
= PrintCradleType
deriving (Show, Eq, Ord)
getArguments :: String -> IdePlugins IdeState -> IO Arguments
getArguments exeName plugins = execParser opts
where
opts = info ((
VersionMode <$> printVersionParser exeName
<|> probeToolsParser exeName
<|> listPluginsParser
<|> BiosMode <$> biosParser
<|> Ghcide <$> arguments plugins
)
<**> helper)
( fullDesc
<> progDesc "Used as a test bed to check your IDE Client will work"
<> header (exeName ++ " - GHC Haskell LSP server"))
printVersionParser :: String -> Parser PrintVersion
printVersionParser exeName =
flag' PrintVersion
(long "version" <> help ("Show " ++ exeName ++ " and GHC versions"))
<|>
flag' PrintNumericVersion
(long "numeric-version" <> help ("Show numeric version of " ++ exeName))
biosParser :: Parser BiosAction
biosParser =
flag' PrintCradleType
(long "print-cradle" <> help "Print the project cradle type")
probeToolsParser :: String -> Parser Arguments
probeToolsParser exeName =
flag' ProbeToolsMode
(long "probe-tools" <> help ("Show " ++ exeName ++ " version and other tools of interest"))
listPluginsParser :: Parser Arguments
listPluginsParser =
flag' ListPluginsMode
(long "list-plugins" <> help "List all available plugins")
arguments :: IdePlugins IdeState -> Parser GhcideArguments
arguments plugins = GhcideArguments
<$> (commandP plugins <|> lspCommand <|> checkCommand)
<*> optional (strOption $ long "cwd" <> metavar "DIR"
<> help "Change to this directory")
<*> optional (strOption $ long "shake-profiling" <> metavar "DIR"
<> help "Dump profiling reports to this directory")
<*> switch (long "test"
<> help "Enable additional lsp messages used by the testsuite")
<*> switch (long "example"
<> help "Include the Example Plugin. For Plugin devs only")
<*> switch
( long "debug"
<> short 'd'
<> help "Generate debug output"
)
<*> optional (strOption
( long "logfile"
<> short 'l'
<> metavar "LOGFILE"
<> help "File to log to, defaults to stdout"
))
<*> option auto
(short 'j'
<> help "Number of threads (0: automatic)"
<> metavar "NUM"
<> value 0
<> showDefault
)
<*> switch (long "project-ghc-version"
<> help "Work out the project GHC version and print it")
where
lspCommand = LSP <$ flag' True (long "lsp" <> help "Start talking to an LSP server")
checkCommand = Check <$> many (argument str (metavar "FILES/DIRS..."))
-- ---------------------------------------------------------------------
haskellLanguageServerNumericVersion :: String
haskellLanguageServerNumericVersion = showVersion version
haskellLanguageServerVersion :: IO String
haskellLanguageServerVersion = do
path <- getExecutablePath
let gi = $$tGitInfoCwdTry
gitHashSection = case gi of
Right gi -> " (GIT hash: " <> giHash gi <> ")"
Left _ -> ""
return $ "haskell-language-server version: " <> haskellLanguageServerNumericVersion
<> " (GHC: " <> VERSION_ghc
<> ") (PATH: " <> path <> ")"
<> gitHashSection
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above
module Ide.Arguments
( Arguments(..)
, GhcideArguments(..)
, PrintVersion(..)
, BiosAction(..)
, getArguments
, haskellLanguageServerVersion
, haskellLanguageServerNumericVersion
) where
import Data.Version
import Development.IDE (IdeState)
import Development.IDE.Main (Command (..), commandP)
import GitHash (giHash, tGitInfoCwdTry)
import Ide.Types (IdePlugins)
import Options.Applicative
import Paths_haskell_language_server
import System.Environment
-- ---------------------------------------------------------------------
data Arguments
= VersionMode PrintVersion
| ProbeToolsMode
| ListPluginsMode
| BiosMode BiosAction
| Ghcide GhcideArguments
| VSCodeExtensionSchemaMode
| DefaultConfigurationMode
data GhcideArguments = GhcideArguments
{argsCommand :: Command
,argsCwd :: Maybe FilePath
,argsShakeProfiling :: Maybe FilePath
,argsTesting :: Bool
,argsExamplePlugin :: Bool
-- These next two are for compatibility with existing hie clients, allowing
-- them to just change the name of the exe and still work.
, argsDebugOn :: Bool
, argsLogFile :: Maybe String
, argsThreads :: Int
, argsProjectGhcVersion :: Bool
} deriving Show
data PrintVersion
= PrintVersion
| PrintNumericVersion
deriving (Show, Eq, Ord)
data BiosAction
= PrintCradleType
deriving (Show, Eq, Ord)
getArguments :: String -> IdePlugins IdeState -> IO Arguments
getArguments exeName plugins = execParser opts
where
opts = info ((
VersionMode <$> printVersionParser exeName
<|> probeToolsParser exeName
<|> listPluginsParser
<|> BiosMode <$> biosParser
<|> Ghcide <$> arguments plugins
)
<**> helper)
( fullDesc
<> progDesc "Used as a test bed to check your IDE Client will work"
<> header (exeName ++ " - GHC Haskell LSP server"))
printVersionParser :: String -> Parser PrintVersion
printVersionParser exeName =
flag' PrintVersion
(long "version" <> help ("Show " ++ exeName ++ " and GHC versions"))
<|>
flag' PrintNumericVersion
(long "numeric-version" <> help ("Show numeric version of " ++ exeName))
biosParser :: Parser BiosAction
biosParser =
flag' PrintCradleType
(long "print-cradle" <> help "Print the project cradle type")
probeToolsParser :: String -> Parser Arguments
probeToolsParser exeName =
flag' ProbeToolsMode
(long "probe-tools" <> help ("Show " ++ exeName ++ " version and other tools of interest"))
listPluginsParser :: Parser Arguments
listPluginsParser =
flag' ListPluginsMode
(long "list-plugins" <> help "List all available plugins")
arguments :: IdePlugins IdeState -> Parser GhcideArguments
arguments plugins = GhcideArguments
<$> (commandP plugins <|> lspCommand <|> checkCommand)
<*> optional (strOption $ long "cwd" <> metavar "DIR"
<> help "Change to this directory")
<*> optional (strOption $ long "shake-profiling" <> metavar "DIR"
<> help "Dump profiling reports to this directory")
<*> switch (long "test"
<> help "Enable additional lsp messages used by the testsuite")
<*> switch (long "example"
<> help "Include the Example Plugin. For Plugin devs only")
<*> switch
( long "debug"
<> short 'd'
<> help "Generate debug output"
)
<*> optional (strOption
( long "logfile"
<> short 'l'
<> metavar "LOGFILE"
<> help "File to log to, defaults to stdout"
))
<*> option auto
(short 'j'
<> help "Number of threads (0: automatic)"
<> metavar "NUM"
<> value 0
<> showDefault
)
<*> switch (long "project-ghc-version"
<> help "Work out the project GHC version and print it")
where
lspCommand = LSP <$ flag' True (long "lsp" <> help "Start talking to an LSP server")
checkCommand = Check <$> many (argument str (metavar "FILES/DIRS..."))
-- ---------------------------------------------------------------------
haskellLanguageServerNumericVersion :: String
haskellLanguageServerNumericVersion = showVersion version
haskellLanguageServerVersion :: IO String
haskellLanguageServerVersion = do
path <- getExecutablePath
let gi = $$tGitInfoCwdTry
gitHashSection = case gi of
Right gi -> " (GIT hash: " <> giHash gi <> ")"
Left _ -> ""
return $ "haskell-language-server version: " <> haskellLanguageServerNumericVersion
<> " (GHC: " <> VERSION_ghc
<> ") (PATH: " <> path <> ")"
<> gitHashSection

View File

@ -1,82 +1,82 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Information and display strings for HIE's version
-- and the current project's version
module Ide.Version where
import Data.Maybe (listToMaybe)
import Data.Version
import GitHash (giCommitCount, tGitInfoCwdTry)
import Options.Applicative.Simple (simpleVersion)
import qualified Paths_haskell_language_server as Meta
import System.Directory
import System.Exit
import System.Info
import System.Process
import Text.ParserCombinators.ReadP
-- >>> hlsVersion
hlsVersion :: String
hlsVersion =
let gi = $$tGitInfoCwdTry
commitCount = case gi of
Right gi -> show $ giCommitCount gi
Left _ -> "UNKNOWN"
in concat $ concat
[ [$(simpleVersion Meta.version)]
-- Leave out number of commits for --depth=1 clone
-- See https://github.com/commercialhaskell/stack/issues/792
, [" (" ++ commitCount ++ " commits)" | commitCount /= ("1"::String) &&
commitCount /= ("UNKNOWN" :: String)]
, [" ", arch]
, [" ", hlsGhcDisplayVersion]
]
where
hlsGhcDisplayVersion = compilerName ++ "-" ++ VERSION_ghc
data ProgramsOfInterest = ProgramsOfInterest
{ cabalVersion :: Maybe Version
, stackVersion :: Maybe Version
, ghcVersion :: Maybe Version
}
showProgramVersionOfInterest :: ProgramsOfInterest -> String
showProgramVersionOfInterest ProgramsOfInterest {..} =
unlines
[ "cabal:\t\t" ++ showVersionWithDefault cabalVersion
, "stack:\t\t" ++ showVersionWithDefault stackVersion
, "ghc:\t\t" ++ showVersionWithDefault ghcVersion
]
where
showVersionWithDefault :: Maybe Version -> String
showVersionWithDefault = maybe "Not found" showVersion
findProgramVersions :: IO ProgramsOfInterest
findProgramVersions = ProgramsOfInterest
<$> findVersionOf "cabal"
<*> findVersionOf "stack"
<*> findVersionOf "ghc"
-- | Find the version of the given program.
-- Assumes the program accepts the cli argument "--numeric-version".
-- If the invocation has a non-zero exit-code, we return 'Nothing'
findVersionOf :: FilePath -> IO (Maybe Version)
findVersionOf tool =
findExecutable tool >>= \case
Nothing -> pure Nothing
Just path ->
readProcessWithExitCode path ["--numeric-version"] "" >>= \case
(ExitSuccess, sout, _) -> pure $ consumeParser myVersionParser sout
_ -> pure Nothing
where
myVersionParser = do
skipSpaces
version <- parseVersion
skipSpaces
pure version
consumeParser :: ReadP a -> String -> Maybe a
consumeParser p input = listToMaybe $ map fst . filter (null . snd) $ readP_to_S p input
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Information and display strings for HIE's version
-- and the current project's version
module Ide.Version where
import Data.Maybe (listToMaybe)
import Data.Version
import GitHash (giCommitCount, tGitInfoCwdTry)
import Options.Applicative.Simple (simpleVersion)
import qualified Paths_haskell_language_server as Meta
import System.Directory
import System.Exit
import System.Info
import System.Process
import Text.ParserCombinators.ReadP
-- >>> hlsVersion
hlsVersion :: String
hlsVersion =
let gi = $$tGitInfoCwdTry
commitCount = case gi of
Right gi -> show $ giCommitCount gi
Left _ -> "UNKNOWN"
in concat $ concat
[ [$(simpleVersion Meta.version)]
-- Leave out number of commits for --depth=1 clone
-- See https://github.com/commercialhaskell/stack/issues/792
, [" (" ++ commitCount ++ " commits)" | commitCount /= ("1"::String) &&
commitCount /= ("UNKNOWN" :: String)]
, [" ", arch]
, [" ", hlsGhcDisplayVersion]
]
where
hlsGhcDisplayVersion = compilerName ++ "-" ++ VERSION_ghc
data ProgramsOfInterest = ProgramsOfInterest
{ cabalVersion :: Maybe Version
, stackVersion :: Maybe Version
, ghcVersion :: Maybe Version
}
showProgramVersionOfInterest :: ProgramsOfInterest -> String
showProgramVersionOfInterest ProgramsOfInterest {..} =
unlines
[ "cabal:\t\t" ++ showVersionWithDefault cabalVersion
, "stack:\t\t" ++ showVersionWithDefault stackVersion
, "ghc:\t\t" ++ showVersionWithDefault ghcVersion
]
where
showVersionWithDefault :: Maybe Version -> String
showVersionWithDefault = maybe "Not found" showVersion
findProgramVersions :: IO ProgramsOfInterest
findProgramVersions = ProgramsOfInterest
<$> findVersionOf "cabal"
<*> findVersionOf "stack"
<*> findVersionOf "ghc"
-- | Find the version of the given program.
-- Assumes the program accepts the cli argument "--numeric-version".
-- If the invocation has a non-zero exit-code, we return 'Nothing'
findVersionOf :: FilePath -> IO (Maybe Version)
findVersionOf tool =
findExecutable tool >>= \case
Nothing -> pure Nothing
Just path ->
readProcessWithExitCode path ["--numeric-version"] "" >>= \case
(ExitSuccess, sout, _) -> pure $ consumeParser myVersionParser sout
_ -> pure Nothing
where
myVersionParser = do
skipSpaces
version <- parseVersion
skipSpaces
pure version
consumeParser :: ReadP a -> String -> Maybe a
consumeParser p input = listToMaybe $ map fst . filter (null . snd) $ readP_to_S p input