lushtags (empty) → 0.0.1
raw patch · 9 files changed
+480/−0 lines, 9 filesdep +basedep +haskell-src-extsdep +textsetup-changedbinary-added
Dependencies added: base, haskell-src-exts, text, vector
Files
- AUTHORS +1/−0
- LICENSE +19/−0
- README.md +60/−0
- Setup.lhs +3/−0
- doc/screenshot-tagbar-2011-09-19.png binary
- lushtags.cabal +59/−0
- src/Main.hs +78/−0
- src/Tags.hs +218/−0
- util/tagbar-haskell.vim +42/−0
+ AUTHORS view
@@ -0,0 +1,1 @@+Bit Connor <bit@mutantlemon.com>
+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (C) 2011 The lushtags Authors (see AUTHORS file)++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ README.md view
@@ -0,0 +1,60 @@+lushtags+========++Create ctags compatible tags files for Haskell programs++Similar programs are [hasktags][1], [gasbag][2], [hothasktags][3], and GHC's+builtin ctags generation.++lushtags differs from these by being designed to have smooth integration with+the [Vim Tagbar][4] plugin.++Tagbar is nice because it deals with creating tags automatically. There is no+need to manually run commands or keep track of tag files. Just open any Haskell+file in Vim and the Tagbar window will instantly show an interactive browsable+list of all the functions and declarations in the file. This window also+updates automatically as you edit the file.++The tags created by lushtags are marked with several extensions, so that when+used with Tagbar you get these features:++- Type signatures are displayed for functions.+- Tags are properly scoped so that, for example, data declarations appear as a+ tree with their constructors scoped as children.+- Definitions that are exported from the module are marked as "public" and+ appear emphasized in Tagbar.+- Tag locations are internally stored as patterns, not line numbers, so that+ you can correctly jump to tags even if they have moved in the source code+ during editing.++++[1]: http://hackage.haskell.org/package/hasktags+[2]: http://kingfisher.nfshost.com/sw/gasbag/+[3]: http://hackage.haskell.org/package/hothasktags+[4]: http://majutsushi.github.com/tagbar/++Using lushtags with Vim and the Tagbar plugin+---------------------------------------------++1. Build and install the lushtags executable++ $ cabal configure+ $ cabal build+ $ cabal install++2. Install the Tagbar plugin. Tagbar can be found at++ <http://www.vim.org/scripts/script.php?script_id=3465> + <http://majutsushi.github.com/tagbar/>++3. Install the included Haskell Tagbar configuration:++ $ cp util/tagbar-haskell.vim ~/.vim/plugin/++4. Try it out:++ $ vim Hello.hs++Now open the Tagbar with the command `:TagbarOpen`. An interactive sidebar will+appear with all of the tags in your Haskell source file.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ doc/screenshot-tagbar-2011-09-19.png view
binary file changed (absent → 87849 bytes)
+ lushtags.cabal view
@@ -0,0 +1,59 @@+name: lushtags+version: 0.0.1+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE+author: Bit Connor+homepage: https://github.com/bitc/lushtags+maintainer: bit@mutantlemon.com+category: Development+synopsis: Create ctags compatible tags files for Haskell programs+description:+ > Create ctags compatible tags files for Haskell programs+ >+ > Similar programs are [hasktags][1], [gasbag][2], [hothasktags][3], and GHC's+ > builtin ctags generation.+ >+ > lushtags differs from these by being designed to have smooth integration with+ > the [Vim Tagbar][4] plugin.+ >+ > Tagbar is nice because it deals with creating tags automatically. There is no+ > need to manually run commands or keep track of tag files. Just open any Haskell+ > file in Vim and the Tagbar window will instantly show an interactive browsable+ > list of all the functions and declarations in the file. This window also+ > updates automatically as you edit the file.+ >+ > The tags created by lushtags are marked with several extensions, so that when+ > used with Tagbar you get these features:+ >+ > - Type signatures are displayed for functions.+ > - Tags are properly scoped so that, for example, data declarations appear as a+ > tree with their constructors scoped as children.+ > - Definitions that are exported from the module are marked as "public" and+ > appear emphasized in Tagbar.+ > - Tag locations are internally stored as patterns, not line numbers, so that+ > you can correctly jump to tags even if they have moved in the source code+ > during editing.+ >+ > + >+ > [1]: http://hackage.haskell.org/package/hasktags+ > [2]: http://kingfisher.nfshost.com/sw/gasbag/+ > [3]: http://hackage.haskell.org/package/hothasktags+ > [4]: http://majutsushi.github.com/tagbar/++source-repository head+ type: git+ location: git://github.com/bitc/lushtags.git++executable lushtags+ hs-source-dirs: src+ main-is: Main.hs+ other-modules: Tags+ default-language: Haskell98+ ghc-options: -Wall+ build-depends: base >= 4 && < 5,+ vector == 0.9.*,+ text == 0.11.*,+ haskell-src-exts == 1.11.*
+ src/Main.hs view
@@ -0,0 +1,78 @@+----------------------------------------------------------------------------+-- |+-- Module : Main+-- License : MIT (see LICENSE)+-- Authors : Bit Connor <bit@mutantlemon.com>+--+-- Maintainer : Bit Connor <bit@mutantlemon.com>+-- Stability : unstable+-- Portability : portable+--+-- lushtags, Create ctags compatible tags files for Haskell programs+--+-----------------------------------------------------------------------------++module Main (main) where++import Data.List (isPrefixOf, partition)+import Data.Vector(Vector, fromList)+import Language.Haskell.Exts.Annotated (parseFileContentsWithMode, ParseMode(..), knownExtensions, ParseResult(ParseOk, ParseFailed))+import System.Environment (getArgs, getProgName)+import System.IO (hPutStrLn, stderr)+import qualified Data.Text as T (unpack, lines, pack, unlines)+import qualified Data.Text.IO as T (readFile, putStr)++import Tags (Tag, createTags, tagToString)++main :: IO ()+main = do+ rawArgs <- getArgs+ let (options, files) = getOptions rawArgs+ ignore_parse_error = "--ignore-parse-error" `elem` options+ case files of+ [] -> do+ progName <- getProgName+ hPutStrLn stderr $ "Usage: " ++ progName ++ " [options] [--] <file>"+ filename:_ -> processFile filename ignore_parse_error >>= printTags++printTags :: [Tag] -> IO ()+printTags tags =+ T.putStr $ T.unlines $ map (T.pack . tagToString) tags++processFile :: FilePath -> Bool -> IO [Tag]+processFile file ignore_parse_error = do+ (fileContents, fileLines) <- loadFile file+ case parseFileContentsWithMode parseMode fileContents of+ ParseFailed loc message ->+ if ignore_parse_error then+ return []+ else+ -- TODO Better error reporting+ fail $ "Parse error: " ++ show loc ++ ": " ++ message+ ParseOk parsedModule -> return $ createTags (parsedModule, fileLines)+ where+ parseMode = ParseMode+ { parseFilename = file+ , extensions = knownExtensions+ , ignoreLanguagePragmas = False+ , ignoreLinePragmas = True+ , fixities = Nothing+ }++loadFile :: FilePath -> IO (String, Vector String)+loadFile file = do+ text <- T.readFile file+ let fullContents = T.unpack text+ let textLines = T.lines text+ let stringLines = map T.unpack textLines+ let fileLines = fromList stringLines+ return (fullContents, fileLines)++getOptions :: [String] -> ([String], [String])+getOptions args =+ let (start, end) = span (/= "--") args+ (options, nonOptions) = partition (isPrefixOf "-") start+ in (options, nonOptions ++ dropSep end)+ where+ dropSep ("--":xs) = xs+ dropSep xs = xs
+ src/Tags.hs view
@@ -0,0 +1,218 @@+----------------------------------------------------------------------------+-- |+-- Module : Tags+-- License : MIT (see LICENSE)+-- Authors : Bit Connor <bit@mutantlemon.com>+--+-- Maintainer : Bit Connor <bit@mutantlemon.com>+-- Stability : unstable+-- Portability : portable+--+-- Extract Tags from a Haskell Module+--+-----------------------------------------------------------------------------++module Tags+ (+ Tag(..)+ , createTags+ , tagToString+ ) where++import Data.Vector(Vector, (!))+import Language.Haskell.Exts.Annotated (SrcSpan(..), SrcSpanInfo(..))+import Language.Haskell.Exts.Annotated.Syntax+import Language.Haskell.Exts.Pretty (prettyPrintStyleMode, Style(..), Mode(OneLineMode), defaultMode)++data Tag = Tag+ { tagName :: String+ , tagFile :: String+ , tagPattern :: String+ , tagKind :: TagKind+ , tagLine :: Int+ , tagParent :: Maybe (TagKind, String)+ , tagSignature :: Maybe String+ , tagAccess :: Maybe TagAccess+ }+ deriving (Eq, Ord, Show)++data TagKind+ = TModule+ | TExport+ | TImport+ | TType+ | TData+ | TNewType+ | TConstructor+ | TFunction+ deriving (Eq, Ord, Show)++-- Access modifiers comes from the C++ world. Haskell doesn't have them, but+-- they are useful for marking our tags with similar meanings. For example,+-- marking all exported functions as public, or marking qualified imports+-- differently from unqualified ones.+data TagAccess+ = AccessPublic+ | AccessPrivate+ | AccessProtected+ deriving (Eq, Ord, Show)++-- First letter of each kind name must be unique!+tagKindName :: TagKind -> String+tagKindName TModule = "module"+tagKindName TExport = "export"+tagKindName TImport = "import"+tagKindName TType = "type"+tagKindName TData = "data"+tagKindName TNewType = "newtype"+tagKindName TConstructor = "constructor"+tagKindName TFunction = "function"++tagKindLetter :: TagKind -> Char+tagKindLetter = head . tagKindName++tagToString :: Tag -> String+tagToString tag =+ let parentStr = case tagParent tag of+ Nothing -> ""+ Just (kind, name) -> "\t" ++ tagKindName kind ++ ":" ++ name+ signatureStr = case tagSignature tag of+ Nothing -> ""+ Just sig -> "\tsignature:("++ sig ++ ")"+ accessStr = case tagAccess tag of+ Nothing -> ""+ Just AccessPublic -> "\taccess:public"+ Just AccessPrivate -> "\taccess:private"+ Just AccessProtected -> "\taccess:protected"+ in tagName tag ++ "\t" +++ tagFile tag ++ "\t" +++ tagPattern tag ++ ";\"\t" +++ tagKindLetter (tagKind tag) : "\t" +++ "line:" ++ show (tagLine tag) +++ parentStr +++ signatureStr +++ accessStr++type FileLines = Vector String+type TagC = FileLines -> Tag++createTags :: (Module SrcSpanInfo, FileLines) -> [Tag]+createTags (Module _ mbHead _ imports decls, fileLines) =+ let moduleTags = map tagC (maybe [] createModuleTags mbHead)+ importTags = map (tagC . createImportTag) imports+ declsTags = map tagC (concatMap createDeclTags decls)+ exportTags = filter ((==TExport) . tagKind) moduleTags+ -- TODO If there is no ModuleHead then apply public access modifier to all+ -- declarations tags+ in moduleTags ++ importTags ++ (applyAccessModifiers exportTags declsTags)+ where+ tagC :: TagC -> Tag+ tagC = ($ fileLines)+createTags _ = error "TODO Module is XmlPage/XmlHybrid (!)"++-- Apply a public access modifier to all declarations that are listed in the+-- module export specification+applyAccessModifiers :: [Tag] -> [Tag] -> [Tag]+applyAccessModifiers exportTags declTags = map applySingle declTags+ where+ applySingle :: Tag -> Tag+ applySingle tag =+ let name = tagName tag+ exported = any ((==name) . tagName) exportTags+ in if exported+ then tag { tagAccess = Just AccessPublic }+ else tag++createTag :: String -> TagKind -> Maybe (TagKind, String) -> Maybe String -> Maybe TagAccess -> SrcSpanInfo -> TagC+createTag name kind parent signature access (SrcSpanInfo (SrcSpan file line _ _ _) _) fileLines = Tag+ { tagName = name+ , tagFile = file+ -- TODO This probably needs to be escaped:+ , tagPattern = "/^" ++ (fileLines ! (line - 1)) ++ "$/"+ , tagKind = kind+ , tagLine = line+ , tagParent = parent+ , tagSignature = signature+ , tagAccess = access+ }++createModuleTags :: ModuleHead SrcSpanInfo -> [TagC]+createModuleTags (ModuleHead _ (ModuleName moduleLoc moduleName) _ mbExportSpecList) =+ case mbExportSpecList of+ Nothing -> [moduleTag]+ Just (ExportSpecList _ exports) ->+ moduleTag : map createExportTag exports+ where+ moduleTag = createTag moduleName TModule Nothing Nothing Nothing moduleLoc+ createExportTag :: ExportSpec SrcSpanInfo -> TagC+ createExportTag exportSpec =+ let (name, loc) = extractExportSpec exportSpec+ in createTag name TExport Nothing Nothing Nothing loc++createImportTag :: ImportDecl SrcSpanInfo -> TagC+createImportTag (ImportDecl loc (ModuleName _ name) qualified _ _ mbAlias mbSpecs) =+ let signature = case mbAlias of+ Nothing -> Nothing+ Just (ModuleName _ alias) -> Just alias+ access = case mbSpecs of+ Just (ImportSpecList _ False _) ->+ if qualified then Just AccessProtected else Nothing+ Just (ImportSpecList _ True _) ->+ -- imported names are excluded by 'hiding'+ if qualified then Just AccessProtected else Just AccessPrivate+ Nothing -> if qualified then Just AccessProtected else Just AccessPublic+ in createTag name TImport Nothing signature access loc++createDeclTags :: Decl SrcSpanInfo -> [TagC]+createDeclTags (TypeDecl _ hd _) =+ let (name, loc) = extractDeclHead hd+ in [createTag name TType Nothing Nothing Nothing loc]+createDeclTags (DataDecl _ dataOrNew _ hd constructors _) =+ let (name, loc) = extractDeclHead hd+ kind = case dataOrNew of+ DataType _ -> TData+ NewType _ -> TNewType+ dataTag = createTag name kind Nothing Nothing Nothing loc+ in dataTag : map (createConstructorTag (kind, name)) constructors+createDeclTags (TypeSig _ names t) =+ map createFunctionTag names+ where+ sig = prettyPrintStyleMode (Style OneLineMode 0 0) defaultMode t+ createFunctionTag :: Name SrcSpanInfo -> TagC+ createFunctionTag name =+ let (n, loc) = extractName name+ in createTag n TFunction Nothing (Just sig) Nothing loc+createDeclTags _ = []++-- TODO Also create tags for record fields+createConstructorTag :: (TagKind, String) -> QualConDecl SrcSpanInfo -> TagC+createConstructorTag parent (QualConDecl _ _ _ con) =+ let (name, loc) = extractConDecl con+ in createTag name TConstructor (Just parent) Nothing Nothing loc++extractExportSpec :: ExportSpec SrcSpanInfo -> (String, SrcSpanInfo)+extractExportSpec (EVar _ name) = extractQName name+extractExportSpec (EAbs _ name) = extractQName name+extractExportSpec (EThingAll _ name) = extractQName name+extractExportSpec (EThingWith _ name _) = extractQName name+extractExportSpec (EModuleContents _ (ModuleName loc name)) = (name, loc)++extractDeclHead :: DeclHead SrcSpanInfo -> (String, SrcSpanInfo)+extractDeclHead (DHead _ name _) = extractName name+extractDeclHead (DHInfix _ _ name _) = extractName name+extractDeclHead (DHParen _ hd') = extractDeclHead hd'++extractConDecl :: ConDecl SrcSpanInfo -> (String, SrcSpanInfo)+extractConDecl (ConDecl _ name _) = extractName name+extractConDecl (InfixConDecl _ _ name _) = extractName name+extractConDecl (RecDecl _ name _) = extractName name++extractName :: Name SrcSpanInfo -> (String, SrcSpanInfo)+extractName (Ident loc name) = (name, loc)+extractName (Symbol loc name) = (name, loc)++extractQName :: QName SrcSpanInfo -> (String, SrcSpanInfo)+extractQName (Qual loc (ModuleName _ moduleName) name) =+ (moduleName ++ "." ++ fst (extractName name), loc)+extractQName (UnQual loc name) = (fst (extractName name), loc)+extractQName (Special loc _) = ("_special_", loc) -- TODO
+ util/tagbar-haskell.vim view
@@ -0,0 +1,42 @@+" This is a Haskell configuration for the Vim Tagbar plugin that uses+" lushtags.+"+" Tagbar can be found at:+" http://www.vim.org/scripts/script.php?script_id=3465+" http://majutsushi.github.com/tagbar/+"+" Paste this in to your vimrc file+" OR copy this file into your .vim/plugin directory+" OR load it from your vimrc file by adding a line like:+"+" source /path/to/tagbar-haskell.vim++if executable('lushtags')+ let g:tagbar_type_haskell = {+ \ 'ctagsbin' : 'lushtags',+ \ 'ctagsargs' : '--ignore-parse-error --',+ \ 'kinds' : [+ \ 'm:module:0',+ \ 'e:exports:1',+ \ 'i:imports:1',+ \ 't:declarations:0',+ \ 'd:declarations:1',+ \ 'n:declarations:1',+ \ 'f:functions:0',+ \ 'c:constructors:0'+ \ ],+ \ 'sro' : '.',+ \ 'kind2scope' : {+ \ 'd' : 'data',+ \ 'n' : 'newtype',+ \ 'c' : 'constructor',+ \ 't' : 'type'+ \ },+ \ 'scope2kind' : {+ \ 'data' : 'd',+ \ 'newtype' : 'n',+ \ 'constructor' : 'c',+ \ 'type' : 't'+ \ }+ \ }+endif