yi-0.7.0: src/library/Yi/Tag.hs
{-# LANGUAGE FlexibleContexts, DeriveDataTypeable, TemplateHaskell #-}
-- | A module for CTags integration
module Yi.Tag
(
lookupTag,
importTagTable,
hintTags,
completeTag,
Tag,
TagTable(..),
getTags,
setTags,
resetTags,
getTagsFileList,
setTagsFileList
)
where
{- Standard Library Module Imports -}
import Prelude (map, words, lines, readFile, reads)
import Yi.Prelude
import Yi.Editor
import Yi.Dynamic
import qualified Data.ByteString as BS
import qualified Data.ByteString.UTF8 as BS8
import Data.Maybe (mapMaybe)
import Data.List (isPrefixOf)
import System.FilePath (takeFileName, takeDirectory, FilePath, (</>))
import System.FriendlyPath
import Data.Map (Map, fromList, lookup, keys)
import Data.List.Split (splitOn)
import qualified Data.Trie as Trie
import Data.Binary
import Data.DeriveTH
newtype Tags = Tags (Maybe TagTable) deriving Typeable
instance Initializable Tags where
initial = Tags Nothing
newtype TagsFileList = TagsFileList [FilePath] deriving Typeable
instance Initializable TagsFileList where
initial = TagsFileList ["tags"]
type Tag = String
data TagTable = TagTable { tagFileName :: FilePath
-- ^ local name of the tag file
-- TODO: reload if this file is changed
, tagBaseDir :: FilePath
-- ^ path to the tag file directory
-- tags are relative to this path
, tagFileMap :: Map Tag (FilePath, Int)
-- ^ map from tags to files
, tagTrie :: Trie.Trie
-- ^ trie to speed up tag hinting
} deriving Typeable
-- | Find the location of a tag using the tag table.
-- Returns a full path and line number
lookupTag :: Tag -> TagTable -> Maybe (FilePath, Int)
lookupTag tag tagTable = do
(file, line) <- Data.Map.lookup tag $ tagFileMap tagTable
return $ (tagBaseDir tagTable </> file, line)
-- | Super simple parsing CTag format 1 parsing algorithm
-- TODO: support search patterns in addition to lineno
readCTags :: String -> Map Tag (FilePath, Int)
readCTags =
fromList . mapMaybe (parseTagLine . words) . lines
where parseTagLine (tag:tagfile:lineno:_) =
-- remove ctag control lines
if "!_TAG_" `isPrefixOf` tag then Nothing
else Just (tag, (tagfile, fst . head . reads $ lineno))
parseTagLine _ = Nothing
-- | Read in a tag file from the system
importTagTable :: FilePath -> IO TagTable
importTagTable filename = do
friendlyName <- expandTilda filename
tagStr <- fmap BS8.toString $ BS.readFile friendlyName
let ctags = readCTags tagStr
return $ TagTable { tagFileName = takeFileName filename,
tagBaseDir = takeDirectory filename,
tagFileMap = ctags,
tagTrie = Trie.fromList $ keys ctags
}
-- | Gives all the possible expanded tags that could match a given @prefix@
hintTags :: TagTable -> String -> [String]
hintTags tags prefix = map (prefix ++) $ Trie.possibleSuffixes prefix $ tagTrie tags
-- | Extends the string to the longest certain length
completeTag :: TagTable -> String -> String
completeTag tags prefix = prefix ++ (Trie.certainSuffix prefix $ tagTrie tags)
-- ---------------------------------------------------------------------
-- Direct access interface to TagTable.
-- | Set a new TagTable
setTags :: TagTable -> EditorM ()
setTags = setDynamic . Tags . Just
-- | Reset the TagTable
resetTags :: EditorM ()
resetTags = setDynamic $ Tags Nothing
-- | Get the currently registered tag table
getTags :: EditorM (Maybe TagTable)
getTags = do
Tags t <- getDynamic
return t
setTagsFileList :: String -> EditorM ()
setTagsFileList fps = do
resetTags
setDynamic $ TagsFileList (splitOn "," fps)
getTagsFileList :: EditorM [FilePath]
getTagsFileList = do
TagsFileList fps <- getDynamic
return fps
-- template haskell at the end to avoid 'not found' compilation errors
$(derives [makeBinary] [''Tags, ''TagTable, ''TagsFileList])
-- For GHC 7.0 with template-haskell 2.5 (at least on my computer - coconnor) the Binary instance
-- needs to be defined before the YiVariable instance.
--
-- GHC 7.1 does not appear to have this issue.
instance YiVariable Tags
instance YiVariable TagsFileList