ghc-tags-plugin 0.1.3.0 → 0.1.4.0
raw patch · 12 files changed
+349/−218 lines, 12 filesdep +filepathdep +latticesPVP ok
version bump matches the API change (PVP)
Dependencies added: filepath, lattices
API changes (from Hackage documentation)
+ Plugin.GhcTags: instance GHC.Exception.Type.Exception Plugin.GhcTags.GhcTagsPluginException
+ Plugin.GhcTags: instance GHC.Show.Show Plugin.GhcTags.GhcTagsPluginException
Files
- CHANGELOG.md +8/−0
- Makefile +6/−6
- README.md +79/−29
- ghc-tags-plugin.cabal +11/−9
- lib/Plugin/GhcTags/Generate.hs +101/−60
- lib/Plugin/GhcTags/Tag.hs +27/−34
- lib/Plugin/GhcTags/Vim/Formatter.hs +2/−1
- lib/Plugin/GhcTags/Vim/Parser.hs +1/−1
- src/Plugin/GhcTags.hs +80/−54
- test/Test/Tag.hs +11/−10
- test/Test/Tag/Generators.hs +20/−11
- test/Test/Vim.hs +3/−3
CHANGELOG.md view
@@ -20,3 +20,11 @@ * Change order of tags: type classes, type families and data type families are sorted before their instances. If one is using multipe tags (the default), the order of them also matters (i.e. in the vim `tags` option).++## 0.1.4.0 -- 2020-03.11++* Tags for default instances of associated (data) type familes.+* Added path argument, can be passed using `-fplugin-opt=Plugin.GhcTags:../tags`.+* Wrapped `IOExceptions`, so when it will happen it will be obvious that the+ plugin failed not `ghc`+* Fixed the tag ordering function to be fullfil the transitivness property.
Makefile view
@@ -2,27 +2,27 @@ # install, uninstall and friends ghc-tags-plugin in cabal store # -CABAL_STORE = ${HOME}/.cabal/store/ghc-$(shell ghc --numeric-version)/package.db+PACKAGE_DB = ${HOME}/.cabal/store/ghc-$(shell ghc --numeric-version)/package.db uninstall: ghc-pkg unregister \- --package-db=${CABAL_STORE} \+ --package-db=${PACKAGE_DB} \ --force \ z-ghc-tags-plugin-z-ghc-tags-library \ ghc-tags-plugin install:- cabal install --package-db=${CABAL_STORE} --lib ghc-tags-plugin+ cabal install --package-db=${PACKAGE_DB} --lib ghc-tags-plugin reinstall: uninstall install list:- ghc-pkg list --package-db=${CABAL_STORE} | grep ghc-tags+ ghc-pkg list --package-db=${PACKAGE_DB} | grep ghc-tags latest:- ghc-pkg latest --package-db=${CABAL_STORE} ghc-tags-plugin+ ghc-pkg latest --package-db=${PACKAGE_DB} ghc-tags-plugin recache:- ghc-pkg recache --package-db=${CABAL_STORE}+ ghc-pkg recache --package-db=${PACKAGE_DB} .PHONY: install, uninstall, reinstall, show, latest
README.md view
@@ -5,41 +5,58 @@ A [Ghc Compiler Plugin](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/extending_ghc.html#compiler-plugins) which generates tags for each compiled module or component. -# Requirements +# ● Requirements+ ``` ghc >= 8.6 ``` -# Vim configuration+# ● Vim configuration -Each generated tags file is put next to the corresponding `*.cabal` file. If-you just have a repo with a cabal file in the main directory `vim` default-`tags` setting will work, if you have some modules in subdirectories you will-need to set:+By default each generated tags file is put next to the corresponding `*.cabal`+package file. If you just have a repo with a cabal file in the main directory+`vim` default `tags` setting will work, if you have some modules in+subdirectories you will either need to set: ``` :set tags+=*/tags ```+or pass an option to modify where tags are written, see below. -# Plugin configuration +# ● Plugin options++The plugin accepts an only one option, which is a file path to the tags file.+It can be an absolute path or relative (to the `*.cabal` package file rather+than `cabal.project` file), for example:+```+-fplugin-opt=Plugin.GhcTags:../tags+```+This is useful if for *cabal packages* which are located in subdirectories.+++# ● Configuration: Ghc / Cabal / Stack+ Configuration of this plugin requires some familiarity with `ghc` packages. Check out [documentation](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html#packages)-how to use `-plugin-package` or `-plugin-package-id`. In the examples below we-us `-plugin-package=ghc-tags-plugin` but specifying version+to use `-plugin-package` or `-plugin-package-id`. In the examples below we+use `-plugin-package=ghc-tags-plugin` but specifying version `-package=ghc-tags-plugin-0.0.0.0` (where `0.0.0.0` is the version you-installed), might work better. You can use `ghc-pkg latest` (likely with-appropriate `--package-db` flag) to check which version is available.+installed), might work better. You can use `ghc-pkg latest ghc-tags-plugin`+(likely with appropriate `--package-db` flag) to check which version is+available. -## ghc+## ● Ghc ``` ghc -plugin-package=ghc-tags-plugin -fplugin=Plugin.GhcTags ``` -## cabal+You might also need to pass `-package-db` in which you installed the plugin. +## ● Cabal+ Install the `ghc-tags-plugin` to cabal store with: ``` cabal install --lib ghc-tags-plugin@@ -63,33 +80,66 @@ "C:\\Users\\USER_NAME\\AppData\\Roaming\\cabal\\store\\ghc-8.6.5\\package.db ``` -## stack+Note that you can also configure in this way non-local projects. You will+likely want to pass `-fplugin-opt=Plugin.GhcTags=PATH` where `PATH` is *an+absolute* path to your `tags` file. -Install `ghc-tags-plugin` +## ● Stack++This is alternative method, which also could be modified for `cabal` (but it is+not as nice as the previous method where you don't need to modify any files+checked in vcs).++Add `ghc-tags-plugin` to `build-depends` in your `*.cabal` files. (You should+hide it behind a cabal flag). And add these lines to `stack.yaml` file:+ ```-stack install ghc-tags-plugin+extra-deps:+ - git: https://github.com/coot/ghc-tags-plugin+ commit: a841dae7fb9c335101f7fa4187d02687d306f972++test-project: -plugin-package=ghc-tags-plugin+ -fplugin=Plugin.GhcTags ``` -In `stack.yaml` file add:+Check out an example+[here](https://github.com/coot/ghc-tags-plugin/tree/stack-setup/test-project).+++## ● Ghcid++If you follow the cabal configuration as above (using `stack` should work too) ```-ghc-options:- some-project: -package-db=PACKAGE_DB- -plugin-package=ghc-tags-plugin- -fplugin=Plugin.GhcTags+ghcid --comaand "cabal repl project" ```+will update `tags` file as you modify your project. -where `PACKAGE_DB` is the package db where `ghc-tags-plugin` was installed by-`stack`. -## modifying `cabal` files+## ● Makefile -You can always add `ghc-tags-plugin` as a build dependency in a cabal file (for-each component). You should hide it behind a flag and then use `cabal` or `stack`-to enable it (or `cabal.project.local` or `stack.yaml` files for that purpose).+The [Makefile](https://github.com/coot/ghc-tags-plugin/blob/master/Makefile)+contains some useful commands, e.g. `install`, `uninstall` or `reinstall` the+package in a `package.db` (by default into `cabal` store). This is mostly for+development, but it could be useful in other scenarios as well. -# Security implications of compiler plugins+# ● Exceptions +If a `GHC` plugin throws an exception, `GHC` stops. This plugin wraps+`IOException`s, to make it obvious that it filed rather than `GHC`. This+might mean you misconfigured the plugin (by passing wrong options). The+result might look like this:++```+ghc: panic! (the 'impossible' happened)+ (GHC version 8.6.5 for x86_64-unknown-linux):+ GhcTagsPluginIOException ../: openFile: inappropriate type (Is a directory)++```+++# ● Security implications of compiler plugins+ Such plugins can: * run arbitrary `IO`;@@ -98,5 +148,5 @@ This plugin only reads & writes to `tags` file (and updates a shared mutable state) as of `IO`, and does not-[modify/](https://github.com/coot/ghc-tags-plugin/blob/master/lib/Plugin/GhcTags.hs#L79)+[modify/](https://github.com/coot/ghc-tags-plugin/blob/master/src/Plugin/GhcTags.hs#L95) the syntax tree.
ghc-tags-plugin.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: ghc-tags-plugin-version: 0.1.3.0+version: 0.1.4.0 synopsis: A compiler plugin which generates tags file from GHC syntax tree. description: A compiler source plugin which takes parsed Haskell syntax tree and saves@@ -26,9 +26,10 @@ hs-source-dirs: src exposed-modules: Plugin.GhcTags build-depends: base >=4.12.0.0 && <4.14,+ bytestring ^>=0.10, containers ^>=0.6, directory ^>=1.3,- bytestring ^>=0.10,+ filepath ^>=1.4, ghc >=8.4 && <8.9, text ^>=1.2, @@ -46,11 +47,11 @@ Plugin.GhcTags.Vim.Formatter Paths_ghc_tags_plugin autogen-modules: Paths_ghc_tags_plugin- build-depends: base >=4.12.0.0 && <4.14,- attoparsec ^>=0.13.0.0,+ build-depends: attoparsec ^>=0.13.0.0,+ base >=4.12.0.0 && <4.14,+ bytestring ^>=0.10, containers ^>=0.6, directory ^>=1.3,- bytestring ^>=0.10, ghc >=8.4 && <8.9, text ^>=1.2 @@ -66,15 +67,16 @@ Test.Tag.Generators Test.Vim default-language: Haskell2010 - build-depends: base,- attoparsec,+ build-depends: attoparsec,+ base, bytestring,+ lattices,+ QuickCheck,+ quickcheck-instances, tasty, tasty-golden, tasty-quickcheck, text,- QuickCheck,- quickcheck-instances, ghc-tags-library ghc-options: -Wall
lib/Plugin/GhcTags/Generate.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} --- | Interface for generatating tags for a parsed module.+-- | Interface for generating tags for a parsed module. -- module Plugin.GhcTags.Generate ( GhcTag (..)@@ -16,8 +16,7 @@ , getGhcTags ) where --- import Data.ByteString (ByteString)-import Data.List (find)+ import Data.Maybe (mapMaybe, maybeToList) import Data.Foldable (foldl') import Data.Text (Text)@@ -77,19 +76,9 @@ import Name ( nameOccName , occNameFS )-{--import OccName ( NameSpace- , isDataConNameSpace- , isTcClsNameSpace- , isTvNameSpace- , isValNameSpace- , isVarNameSpace- , occNameSpace- , pprNameSpace- )--} --- | `ctags` can generate tags kind, so do we.++-- | `ctags` can generate tags kinds, so do we. -- data GhcKind = TkTerm | TkFunction@@ -158,17 +147,24 @@ +-- | Unit of data associated with a tag. Vim natively supports `file:` and+-- `kind:` tags but it can display any other tags too.+-- data TagField = TagField { fieldName :: Text, fieldValue :: Text } deriving (Eq, Ord, Show) ++-- | File field; tags which contain 'fileFields' are called static (aka static+-- in 'C'), such tags are only visible in the current file)+-- fileField :: TagField fileField = TagField { fieldName = "file", fieldValue = "" } --- | We can read names from using fields of type 'GHC.Hs.Extensions.IdP' (a tpye+-- | We can read names from using fields of type 'GHC.Hs.Extensions.IdP' (a type -- family) which for @'Parsed@ resolved to 'RdrName' -- data GhcTag = GhcTag {@@ -179,6 +175,7 @@ } deriving Show + appendField :: TagField -> GhcTag -> GhcTag appendField f gt = gt { gtFields = f : gtFields gt } @@ -186,10 +183,14 @@ type GhcTags = [GhcTag] +-- | Check if an identifier is exported, if it is not return 'fileField'.+-- getFileTagField :: Maybe [IE GhcPs] -> Located RdrName -> Maybe TagField getFileTagField Nothing _name = Nothing-getFileTagField (Just ies) name =- maybe (Just fileField) (const Nothing) $ find (\a -> ieName a == Just (unLoc name)) ies+getFileTagField (Just ies) (L _ name) =+ if any (\ie -> ieName ie == Just name) ies+ then Nothing+ else Just fileField where -- TODO: the GHC's one is partial, and I got a panic error. ieName :: IE GhcPs -> Maybe RdrName@@ -199,6 +200,7 @@ ieName (IEThingAll _ (L _ n)) = Just $ ieWrappedName n ieName _ = Nothing + -- | Either class members or type constructors. -- getFileTagFieldForMember :: Maybe [IE GhcPs]@@ -207,7 +209,9 @@ -> Maybe TagField getFileTagFieldForMember Nothing _memberName _className = Nothing getFileTagFieldForMember (Just ies) memberName className =- maybe (Just fileField) (const Nothing) $ find go ies+ if any go ies+ then Nothing+ else Just fileField where go :: IE GhcPs -> Bool @@ -232,6 +236,8 @@ go _ = False +-- | Create a 'GhcTag', effectively a smart constructor.+-- mkGhcTag :: Located RdrName -- ^ @RdrName ~ IdP GhcPs@ it *must* be a name of a top level identifier. -> GhcKind@@ -287,8 +293,8 @@ -- * data type families instances -- * data type family instances constructors ---getGhcTags :: Located (HsModule GhcPs)- -> GhcTags+getGhcTags ::Located (HsModule GhcPs)+ -> GhcTags getGhcTags (L _ HsModule { hsmodDecls, hsmodExports }) = reverse $ foldl' go [] hsmodDecls where@@ -308,21 +314,34 @@ -> GhcKind -> GhcTag mkGhcTagForMember memberName className kind =- mkGhcTag memberName kind (maybeToList $ getFileTagFieldForMember mies memberName className)+ mkGhcTag memberName kind+ (maybeToList $ getFileTagFieldForMember mies memberName className) ++ -- Main routine which traverse all top level declarations.+ -- go :: GhcTags -> LHsDecl GhcPs -> GhcTags go tags (L _ hsDecl) = case hsDecl of+ -- type or class declaration TyClD _ tyClDecl -> case tyClDecl of++ -- type family declarations FamDecl { tcdFam } -> case mkFamilyDeclTags tcdFam Nothing of Just tag -> tag : tags Nothing -> tags + -- type synonyms SynDecl { tcdLName } -> mkGhcTag' tcdLName TkTypeSynonym : tags + -- data declaration:+ -- type,+ -- constructors,+ -- record fields+ -- DataDecl { tcdLName, tcdDataDefn } -> case tcdDataDefn of HsDataDefn { dd_cons } ->@@ -332,23 +351,47 @@ XHsDataDefn {} -> tags - -- TODO: add 'tcdATDefs'- ClassDecl { tcdLName, tcdSigs, tcdMeths, tcdATs } ->+ -- Type class declaration:+ -- type class name,+ -- type class members,+ -- default methods,+ -- default data type instance+ --+ ClassDecl { tcdLName, tcdSigs, tcdMeths, tcdATs, tcdATDefs } -> -- class name mkGhcTag' tcdLName TkTypeClass -- class methods : (mkClsMemberTags tcdLName . unLoc) `concatMap` tcdSigs -- default methods ++ foldl' (\tags' hsBind -> mkHsBindLRTags (unLoc hsBind) ++ tags')- tags+ [] tcdMeths -- associated types ++ (flip mkFamilyDeclTags (Just tcdLName) . unLoc) `mapMaybe` tcdATs+ -- associated type defaults (data type families, type families+ -- (open or closed)+ ++ foldl'+ (\tags' (L _ tyFamDeflEqn) ->+ case tyFamDeflEqn of+ FamEqn { feqn_rhs } -> + case hsTypeTagName (unLoc feqn_rhs) of+ -- TODO: add a `default` field+ Just a -> mkGhcTag' a TkTypeFamilyInstance : tags'+ Nothing -> tags'+ XFamEqn {} -> tags')+ [] tcdATDefs+ ++ tags XTyClDecl {} -> tags + -- Instance declarations+ -- class instances+ -- type family instance+ -- data type family instances+ -- InstD _ instDecl -> case instDecl of+ -- class instance declaration ClsInstD { cid_inst } -> case cid_inst of XClsInstDecl {} -> tags@@ -361,23 +404,26 @@ -- TODO: @hsbib_body :: LHsType GhcPs@ HsIB { hsib_body } -> case mkLHsTypeTag hsib_body of- Nothing -> tyFamTags ++ dataFamTags ++ tags+ Nothing -> tyFamTags ++ dataFamTags ++ tags Just tag -> tag : tyFamTags ++ dataFamTags ++ tags where+ -- associated type and data type family instances dataFamTags = (mkDataFamInstDeclTag . unLoc) `concatMap` cid_datafam_insts tyFamTags = (mkTyFamInstDeclTag . unLoc) `mapMaybe` cid_tyfam_insts + -- data family instance DataFamInstD { dfid_inst } -> mkDataFamInstDeclTag dfid_inst ++ tags + -- type family instance TyFamInstD { tfid_inst } -> case mkTyFamInstDeclTag tfid_inst of- Nothing -> tags+ Nothing -> tags Just tag -> tag : tags XInstDecl {} -> tags - -- deriveving declaration+ -- deriving declaration DerivD {} -> tags -- value declaration@@ -412,31 +458,34 @@ -- TODO: Rules are named it would be nice to get them too RuleD {} -> tags-- -- TODO: splices SpliceD {} -> tags- DocD {} -> tags RoleAnnotD {} -> tags XHsDecl {} -> tags - -- tags of all constructors of a type++ -- generate tags of all constructors of a type+ -- mkConsTags :: Located RdrName -- name of the type -> ConDecl GhcPs- -- constrtructor declaration+ -- constructor declaration -> GhcTags+ mkConsTags tyName ConDeclGADT { con_names, con_args } = (\n -> mkGhcTagForMember n tyName TkGADTConstructor) `map` con_names- ++ mkHsConDeclDetails con_args+ ++ mkHsConDeclDetails tyName con_args+ mkConsTags tyName ConDeclH98 { con_name, con_args } = mkGhcTagForMember con_name tyName TkDataConstructor- : mkHsConDeclDetails con_args+ : mkHsConDeclDetails tyName con_args+ mkConsTags _ XConDecl {} = [] - mkHsConDeclDetails :: HsConDeclDetails GhcPs -> GhcTags- mkHsConDeclDetails (RecCon (L _ fields)) = foldl' f [] fields+ mkHsConDeclDetails :: Located RdrName -> HsConDeclDetails GhcPs -> GhcTags+ mkHsConDeclDetails tyName (RecCon (L _ fields)) =+ foldl' f [] fields where f :: GhcTags -> LConDeclField GhcPs -> GhcTags f ts (L _ ConDeclField { cd_fld_names }) = foldl' g ts cd_fld_names@@ -444,12 +493,13 @@ g :: GhcTags -> LFieldOcc GhcPs -> GhcTags g ts (L _ FieldOcc { rdrNameFieldOcc }) =- mkGhcTag' rdrNameFieldOcc TkRecordField+ mkGhcTagForMember rdrNameFieldOcc tyName TkRecordField : ts g ts _ = ts - mkHsConDeclDetails _ = []+ mkHsConDeclDetails _ _ = [] + mkHsBindLRTags :: HsBindLR GhcPs GhcPs -> GhcTags mkHsBindLRTags hsBind = case hsBind of@@ -464,7 +514,7 @@ VarBind { var_id, var_rhs = L srcSpan _ } -> [mkGhcTag' (L srcSpan var_id) TkTerm] - -- abstraction binding are only used after translaction+ -- abstraction binding is only used after translation AbsBinds {} -> [] PatSynBind _ PSB { psb_id } -> [mkGhcTag' psb_id TkPatternSynonym]@@ -472,6 +522,7 @@ XHsBindsLR {} -> [] + mkClsMemberTags :: Located RdrName -> Sig GhcPs -> GhcTags mkClsMemberTags clsName (TypeSig _ lhs _) = (\n -> mkGhcTagForMember n clsName TkTypeSignature)@@ -484,6 +535,7 @@ `map` lhs mkClsMemberTags _ _ = [] + mkSigTags :: Sig GhcPs -> GhcTags mkSigTags (TypeSig _ lhs _) = flip mkGhcTag' TkTypeSignature `map` lhs mkSigTags (PatSynSig _ lhs _) = flip mkGhcTag' TkPatternSynonym `map` lhs@@ -503,6 +555,7 @@ mkSigTags CompleteMatchSig {} = [] mkSigTags XSig {} = [] + mkFamilyDeclTags :: FamilyDecl GhcPs -> Maybe (Located RdrName) -- if this type family is associate, pass the name of the@@ -519,10 +572,12 @@ ClosedTypeFamily {} -> TkTypeFamily mkFamilyDeclTags XFamilyDecl {} _ = Nothing + -- used to generate tag of an instance declaration mkLHsTypeTag :: LHsType GhcPs -> Maybe GhcTag mkLHsTypeTag (L _ hsType) = (\a -> mkGhcTag a TkTypeClassInstance []) <$> hsTypeTagName hsType + hsTypeTagName :: HsType GhcPs -> Maybe (Located RdrName) hsTypeTagName hsType = case hsType of@@ -538,7 +593,9 @@ _ -> Nothing - -- todo: type constructors++ -- data family instance declaration+ -- mkDataFamInstDeclTag :: DataFamInstDecl GhcPs -> GhcTags mkDataFamInstDeclTag DataFamInstDecl { dfid_eqn } = case dfid_eqn of@@ -554,6 +611,9 @@ HsIB { hsib_body = XFamEqn {} } -> [] ++ -- type family instance declaration+ -- mkTyFamInstDeclTag :: TyFamInstDecl GhcPs -> Maybe GhcTag mkTyFamInstDeclTag TyFamInstDecl { tfid_eqn } = case tfid_eqn of@@ -564,22 +624,3 @@ Just $ mkGhcTag' feqn_tycon TkTypeFamilyInstance HsIB { hsib_body = XFamEqn {} } -> Nothing------- Debugging-----{--rdrNameToBS :: RdrName -> ByteString-rdrNameToBS = fs_bs . occNameFS . rdrNameOcc--showNameSpace :: NameSpace -> String-showNameSpace ns | isDataConNameSpace ns = "data constructor"- | isTcClsNameSpace ns = "type constructor or class"- | isTvNameSpace ns = "type variable"- | isValNameSpace ns = "variable"- | otherwise = error "impossible happend"--rdrNameSpace :: RdrName -> String-rdrNameSpace = showNameSpace . occNameSpace . rdrNameOcc --}
lib/Plugin/GhcTags/Tag.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}@@ -27,6 +26,7 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Text (Text)+import qualified Data.Text as Text import qualified Data.Text.Encoding as Text -- GHC imports@@ -53,13 +53,13 @@ -- | 'ByteString' which encodes a tag name. -- newtype TagName = TagName { getTagName :: Text }- deriving newtype (Eq, Ord, Show)+ deriving (Eq, Ord, Show) -- | 'ByteString' which encodes a tag file. ---newtype TagFile = TagFile { getTagFile :: Text }- deriving newtype (Eq, Ord, Show)+newtype TagFile = TagFile { getTagFile :: String }+ deriving (Eq, Ord, Show) -- | When we parse a `tags` file we can eithera find no kind or recognize the@@ -87,40 +87,23 @@ } deriving (Eq, Show) +-- | Total order relation on 'Tag' elements.+--+-- It sorts type classes / type families ('TkTypeClass', 'TkTypeFamily',+-- 'TkDataTypeFamily') before instances ('TkTypeClassInstance',+-- 'TkTypeFamilyInstance', 'TkDataTypeFamilyInstance'); but also (as a side+-- effect of keeping transitivity property) it will put type classes and their+-- instances before other kinds.+-- compareTags :: Tag -> Tag -> Ordering compareTags t0 t1 | on (/=) tagName t0 t1 = on compare tagName t0 t1 -- sort type classes / type families before their instances, -- and take precendence over a file where they are defined.- | tagKind t0 == GhcKind TkTypeClass- &&- tagKind t1 == GhcKind TkTypeClassInstance- = LT-- | tagKind t1 == GhcKind TkTypeClass- &&- tagKind t0 == GhcKind TkTypeClassInstance- = GT-- | tagKind t0 == GhcKind TkTypeFamily- &&- tagKind t1 == GhcKind TkTypeFamilyInstance- = LT-- | tagKind t1 == GhcKind TkTypeFamily- &&- tagKind t0 == GhcKind TkTypeFamilyInstance- = GT-- | tagKind t0 == GhcKind TkDataTypeFamily- &&- tagKind t1 == GhcKind TkDataTypeFamilyInstance- = LT-- | tagKind t1 == GhcKind TkDataTypeFamily- &&- tagKind t0 == GhcKind TkDataTypeFamilyInstance- = GT+ -- + -- This will also sort type classes and instances before any+ -- other terms.+ | on (/=) getTkClass t0 t1 = on compare getTkClass t0 t1 | on (/=) tagFile t0 t1 = on compare tagFile t0 t1 | on (/=) tagAddr t0 t1 = on compare tagAddr t0 t1@@ -129,6 +112,16 @@ -- this is not compatible with 'Eq' intsance, but we are not -- defining a 'Ord' instance! | otherwise = EQ+ where+ getTkClass :: Tag -> Maybe GhcKind+ getTkClass t = case tagKind t of+ GhcKind TkTypeClass -> Just TkTypeClass+ GhcKind TkTypeClassInstance -> Just TkTypeClassInstance+ GhcKind TkTypeFamily -> Just TkTypeFamily+ GhcKind TkTypeFamilyInstance -> Just TkTypeFamilyInstance+ GhcKind TkDataTypeFamily -> Just TkDataTypeFamily+ GhcKind TkDataTypeFamilyInstance -> Just TkDataTypeFamilyInstance+ _ -> Nothing ghcTagToTag :: GhcTag -> Maybe Tag@@ -137,7 +130,7 @@ UnhelpfulSpan {} -> Nothing RealSrcSpan realSrcSpan -> Just $ Tag { tagName = TagName (Text.decodeUtf8 $ fs_bs gtTag)- , tagFile = TagFile (Text.decodeUtf8 $ fs_bs (srcSpanFile realSrcSpan))+ , tagFile = TagFile (Text.unpack $ Text.decodeUtf8 $ fs_bs (srcSpanFile realSrcSpan)) , tagAddr = Left (srcSpanStartLine realSrcSpan) , tagKind = GhcKind gtKind , tagFields = gtFields
lib/Plugin/GhcTags/Vim/Formatter.hs view
@@ -12,6 +12,7 @@ import qualified Data.ByteString.Builder as BS import Data.Char (isAscii) import Data.Version (showVersion)+import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Text.Printf (printf) @@ -27,7 +28,7 @@ formatTag Tag { tagName, tagFile, tagAddr, tagKind, tagFields} = (BS.byteString . Text.encodeUtf8 . getTagName $ tagName) <> BS.charUtf8 '\t'- <> (BS.byteString . Text.encodeUtf8 . getTagFile $ tagFile)+ <> (BS.byteString . Text.encodeUtf8 . Text.pack . getTagFile $ tagFile) <> BS.charUtf8 '\t' <> either BS.intDec (BS.byteString . Text.encodeUtf8) tagAddr -- we are using extended format: '_TAG_FILE_FROMAT 2'
lib/Plugin/GhcTags/Vim/Parser.hs view
@@ -80,7 +80,7 @@ <?> "parsing tag name failed" parseFileName :: Parser TagFile- parseFileName = TagFile <$> AT.takeWhile (/= '\t')+ parseFileName = TagFile . Text.unpack <$> AT.takeWhile (/= '\t') parseExCommand :: Parser Text parseExCommand = (\x -> Text.take (Text.length x - 1) x) <$>
src/Plugin/GhcTags.hs view
@@ -15,10 +15,11 @@ import Data.List (sortBy) import qualified Data.Map as Map import Data.Maybe (mapMaybe)+import System.Directory+import System.FilePath import System.IO import System.IO.Error (tryIOError) import System.IO.Unsafe (unsafePerformIO)-import System.Directory import qualified Data.Text.Encoding as Text import GhcPlugins ( CommandLineOption@@ -38,16 +39,16 @@ -- | Global shared state which persists across compilation of different--- modules - a nasty hack which is only used for optimzation.+-- modules - a nasty hack which is only used for optimization. -- tagsMVar :: MVar (Maybe TagsMap) tagsMVar = unsafePerformIO $ newMVar Nothing -- | The GhcTags plugin. It will run for every compiled module and have access--- to parsed syntax tree. It will inpect it and:+-- to parsed syntax tree. It will inspect it and: -- -- * update a global mutable state variable, which stores a tag map.--- It is shared accross modules compiled in the same `ghc` run.+-- It is shared across modules compiled in the same `ghc` run. -- * update 'tags' file. -- -- The global mutable variable save us from parsing the tags file for every@@ -72,15 +73,25 @@ -- plugin :: Plugin plugin = GhcPlugins.defaultPlugin {- parsedResultAction = ghcTagPlugin,+ parsedResultAction = ghcTagsPlugin, pluginRecompile = GhcPlugins.purePlugin } +-- | IOExcption wrapper; it is useful for the user to know that it's the plugin+-- not `ghc` that thrown the error.+--+data GhcTagsPluginException =+ GhcTagsPluginIOExceptino IOException+ deriving Show++instance Exception GhcTagsPluginException++ -- | The plugin does not change the 'HsParedModule', it only runs side effects. ---ghcTagPlugin :: [CommandLineOption] -> ModSummary -> HsParsedModule -> Hsc HsParsedModule-ghcTagPlugin options _modSummary hsParsedModule@HsParsedModule {hpm_module} =+ghcTagsPlugin :: [CommandLineOption] -> ModSummary -> HsParsedModule -> Hsc HsParsedModule+ghcTagsPlugin options _modSummary hsParsedModule@HsParsedModule {hpm_module} = hsParsedModule <$ GhcPlugins.liftIO (updateTags tagsFile hpm_module) where tagsFile :: FilePath@@ -99,58 +110,73 @@ -> Located (HsModule GhcPs) -> IO () updateTags tagsFile lmodule =- -- Take exclusive lock. This assures that only one thread will have access to- -- the tags file. Parsing and the rest of the compilation pipeline can- -- happen concurrently.- mvarLock tagsMVar $ \mTagsMap -> do- (tagsMap :: TagsMap) <-- case mTagsMap of+ -- wrap 'IOException's+ handle (throwIO . GhcTagsPluginIOExceptino) $ - Just tagsMap -> return tagsMap+ -- Take exclusive lock. This assures that only one thread will have access to+ -- the tags file. Parsing and the rest of the compilation pipeline can+ -- happen concurrently.+ mvarLock tagsMVar $ \mTagsMap -> do+ (tagsMap :: TagsMap) <-+ case mTagsMap of - -- the 'tagsMVar' is empty, which means we are compiling the first- -- module. In this case read the tags from disk.- Nothing -> do- a <- doesFileExist tagsFile- res <-- if a- then do- mtext <- tryIOError (Text.decodeUtf8 <$> BS.readFile tagsFile)- case mtext of- Left err -> do- putStrLn $ "GhcTags: error reading \"" ++ tagsFile ++ "\": " ++ (show err)- pure $ Right []- Right txt ->- Vim.parseTagsFile txt- else pure $ Right []- case res of- Left err -> do- putStrLn $ "GhcTags: error reading or parsing \"" ++ tagsFile ++ "\": " ++ err- return $ Map.empty- Right tagList -> do- return $ mkTagsMap tagList+ Just tagsMap -> return tagsMap - let tagsMap' :: TagsMap- tagsMap' =- (mkTagsMap -- created 'TagsMap'- . mapMaybe ghcTagToTag -- tranalte 'GhcTag' to 'Tag'- . getGhcTags -- generate 'GhcTag's- $ lmodule)- `Map.union`- tagsMap+ -- the 'tagsMVar' is empty, which means we are compiling the first+ -- module. In this case read the tags from disk.+ Nothing -> do+ a <- doesFileExist tagsFile+ res <-+ if a+ then do+ mtext <- tryIOError (Text.decodeUtf8 <$> BS.readFile tagsFile)+ case mtext of+ Left err -> do+ putStrLn $ "GhcTags: error reading \"" ++ tagsFile ++ "\": " ++ (show err)+ pure $ Right []+ Right txt ->+ Vim.parseTagsFile txt+ else pure $ Right []+ case res of+ Left err -> do+ putStrLn $ "GhcTags: error reading or parsing \"" ++ tagsFile ++ "\": " ++ err+ return $ Map.empty+ Right tagList -> do+ return $ mkTagsMap tagList - -- update tags file, this will force evaluation `tagsMap'`, so when we- -- write it to `tagsMVar' it will not contain any thunks.- withFile tagsFile WriteMode- $ flip BS.hPutBuilder- ( Vim.formatTagsFile- . sortBy compareTags- . concat- . Map.elems- $ tagsMap'- )+ cwd <- getCurrentDirectory+ -- absolute directory path of the tags file; we need canonical path+ -- (without ".." and ".") to make 'makeRelative' works.+ tagsDir <- canonicalizePath (fst $ splitFileName tagsFile) - pure (Just tagsMap')+ let tagsMap' :: TagsMap+ tagsMap' =+ (mkTagsMap -- created 'TagsMap'+ . map (fixFileName cwd tagsDir)+ -- fix file names+ . mapMaybe ghcTagToTag -- translate 'GhcTag' to 'Tag'+ . getGhcTags -- generate 'GhcTag's+ $ lmodule)+ `Map.union`+ tagsMap+ + -- update tags file, this will force evaluation `tagsMap'`, so when we+ -- write it to `tagsMVar' it will not contain any thunks.+ withFile tagsFile WriteMode+ $ flip BS.hPutBuilder+ ( Vim.formatTagsFile+ . sortBy compareTags+ . concat+ . Map.elems+ $ tagsMap'+ )++ pure (Just tagsMap')++ where+ fixFileName :: FilePath -> FilePath -> Tag -> Tag+ fixFileName cwd tagsDir tag@Tag { tagFile = TagFile path } =+ tag { tagFile = TagFile (makeRelative tagsDir (cwd </> path)) } -- | The 'MVar' is used as an exlusive lock. Also similar to 'bracket' but
test/Test/Tag.hs view
@@ -35,17 +35,18 @@ instance Arbitrary ArbOrdTag where arbitrary = fmap ArbOrdTag $ Tag- <$> elements (TagName `map`- [ "find"- , "Ord"- , "Eq"- , "hPut"- ])+ <$> elements+ (TagName `map`+ [ "find"+ , "Ord"+ , "Eq"+ ]) <*> genTagKind- <*> elements (TagFile `map`- [ "Main.hs"- , "Lib.hs"- ])+ <*> elements+ (TagFile `map`+ [ "Main.hs"+ , "Lib.hs"+ ]) <*> frequency [ (8, Left . getPositive <$> arbitrary) , (1, Right . (wrap '/' . fixAddr) <$> genTextNonEmpty)
test/Test/Tag/Generators.hs view
@@ -3,10 +3,13 @@ module Test.Tag.Generators where import qualified Data.Char as Char+import Data.List as List import Data.Maybe (isNothing) import Data.Text (Text) import qualified Data.Text as Text +import Algebra.Lattice+ import Test.QuickCheck import Test.QuickCheck.Instances.Text () @@ -26,9 +29,19 @@ -- filter only printable characters, removing tabs and newlines which have -- special role in vim tag syntax fixText :: Text -> Text-fixText = Text.filter (\x -> x /= '\t' && x /= '\n' && Char.isPrint x)+fixText = Text.filter ( ((/= Char.Control) . Char.generalCategory)+ /\ Char.isPrint) +fixFilePath :: String -> String+fixFilePath = List.filter ( ((/= Char.Control) . Char.generalCategory)+ /\ Char.isPrint) +genFilePath :: Gen String+genFilePath =+ suchThat+ (fixFilePath <$> arbitrary)+ (not . null)+ genField :: Gen TagField genField = TagField@@ -41,7 +54,9 @@ -- filter only printable characters, removing tabs, newlines and colons which -- have special role in vim field syntax fixFieldText :: Text -> Text-fixFieldText = Text.filter (\x -> x /= '\t' && x /= ':' && x /= '\n' && Char.isPrint x)+fixFieldText = Text.filter ( (/= ':')+ /\ ((/= Char.Control) . Char.generalCategory)+ /\ Char.isPrint) -- address cannot contain ";\"" sequence@@ -81,13 +96,7 @@ ] where genChar = suchThat arbitrary- (\x -> x /= '\t'- && x /= '\n'- && x /= ':'- && x /= '\NUL'- && isNothing (charToGhcKind x)+ ( ((/= Char.Control) . Char.generalCategory)+ /\ (/= ':')+ /\ (isNothing . charToGhcKind) )----------
test/Test/Vim.hs view
@@ -38,7 +38,7 @@ Tag <$> (TagName <$> genTextNonEmpty) <*> genTagKind- <*> (TagFile <$> genTextNonEmpty)+ <*> (TagFile <$> genFilePath) <*> frequency [ (2, Left . getPositive <$> arbitrary) , (1, Right . (wrap '/' . fixAddr) <$> genTextNonEmpty)@@ -51,8 +51,8 @@ , not (Text.null x) ] ++ [ ArbTag $ tag { tagFile = TagFile x }- | x <- fixText `map` shrink (getTagFile tagFile)- , not (Text.null x)+ | x <- fixFilePath `map` shrink (getTagFile tagFile)+ , not (null x) ] ++ [ ArbTag $ tag { tagAddr = addr } | addr <- case tagAddr of