packages feed

ghc-tags 1.2 → 1.3

raw patch · 8 files changed

+94/−89 lines, 8 filesdep ~aesondep ~base

Dependency ranges changed: aeson, base

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# ghc-tags-1.3 (2021-05-23)+* Properly escape ex mode search commands.+* Fix parsing of generated ctags file.+* Remove non-ascii kind symbols (ctags).+* Make generation of Ex mode search commands optional (ctags).+* Don't check stored mtimes if there are no tags.+ # ghc-tags-1.2 (2021-05-22) * Fix sorting of ctags. * Express addresses of ctags as ex commands.
README.md view
@@ -3,8 +3,9 @@ [![Build Status](https://github.com/arybczak/ghc-tags/workflows/Haskell-CI/badge.svg?branch=master)](https://github.com/arybczak/ghc-tags/actions?query=branch%3Amaster) [![Hackage](https://img.shields.io/hackage/v/ghc-tags.svg)](https://hackage.haskell.org/package/ghc-tags) -A command line tool that generates etags (Emacs) and ctags (Vim and other-editors) for efficient code navigation (jump to definition).+A command line tool that generates etags (Emacs) and ctags (Vim, VSCode with+[ctagsx](https://marketplace.visualstudio.com/items?itemName=jtanx.ctagsx) etc.)+for efficient code navigation (jump to definition).  Main features: * Leverages GHC API to obtain accurate information.
ghc-tags.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                ghc-tags-version:             1.2+version:             1.3 synopsis:            Utility for generating ctags and etags with GHC API. description:         Utility for generating etags (Emacs) and ctags (Vim and other                      editors) with GHC API for efficient project navigation.@@ -33,7 +33,7 @@   else      build-depends:    ghc-lib == 9.0.* -  build-depends:       base >=4.14 && <5+  build-depends:       base >=4.14 && <4.16                      , aeson                      , async                      , attoparsec
src/GhcTags/CTag/Header.hs view
@@ -174,20 +174,20 @@   , Header FieldDescription haskellLang "instance" "class, type or data type instance"   , Header FieldDescription haskellLang "Kind" "kind of a type" -  , Header KindDescription haskellLang "`" "module top level term, but not a function"-  , Header KindDescription haskellLang "λ" "module top level function term"-  , Header KindDescription haskellLang "Λ" "type constructor"+  , Header KindDescription haskellLang "M" "module"+  , Header KindDescription haskellLang "f" "function"+  , Header KindDescription haskellLang "A" "type constructor"   , Header KindDescription haskellLang "c" "data constructor"   , Header KindDescription haskellLang "g" "gadt constructor"   , Header KindDescription haskellLang "r" "record field"-  , Header KindDescription haskellLang "≡" "type synonym"-  , Header KindDescription haskellLang "~" "type signature"+  , Header KindDescription haskellLang "=" "type synonym"+  , Header KindDescription haskellLang ":" "type signature"   , Header KindDescription haskellLang "p" "pattern synonym"   , Header KindDescription haskellLang "C" "type class"   , Header KindDescription haskellLang "m" "type class member"   , Header KindDescription haskellLang "i" "type class instance"-  , Header KindDescription haskellLang "F" "type family"-  , Header KindDescription haskellLang "f" "type family instance"+  , Header KindDescription haskellLang "T" "type family"+  , Header KindDescription haskellLang "t" "type family instance"   , Header KindDescription haskellLang "D" "data type family"   , Header KindDescription haskellLang "d" "data type family instance"   , Header KindDescription haskellLang "I" "foreign import"
src/GhcTags/CTag/Parser.hs view
@@ -12,13 +12,12 @@ import           Control.DeepSeq (NFData) import           Data.Attoparsec.Text  (Parser, (<?>)) import qualified Data.Attoparsec.Text  as AT-import           Data.Functor (void, ($>))+import           Data.Functor (($>)) import           qualified Data.Map.Strict as Map import           Data.Text          (Text) import qualified Data.Text          as Text  import           GhcTags.Tag-import qualified GhcTags.Utils as Utils import           GhcTags.CTag.Header import           GhcTags.CTag.Utils @@ -42,37 +41,28 @@     <*> parseFileName     <*  separator -    -- includes an optional ';"' separator     <*> parseTagAddress -    <*> (  -- kind field followed by list of fields or end of line, e.g.-           -- '(TagField, CTagFields)'.-              ((,) <$> ( separator *> parseKindField )-                   <*> ( separator *> parseFields <* endOfLine-                         <|>-                         endOfLine $> mempty)-                       )--          -- kind encoded as a single letter, followed by a list-          -- of fields or end of line.-          <|> (,) <$> ( separator *> (charToTagKind <$> AT.satisfy notTabOrNewLine) )-                  <*> ( separator *> parseFields <* endOfLine-                        <|>-                        endOfLine $> mempty-                      )+    <*> (  -- kind followed by list of fields or end of line+              (,) <$  AT.string ";\""+                  <*  separator+                  <*> (charToTagKind <$> AT.satisfy notTabOrNewLine)+                  <*> fieldsInLine            -- list of fields (kind field might be later, but don't check it, we           -- always format it as the first field) or end of line.-          <|> (NoKind, )-                <$> ( separator *> parseFields <* endOfLine-                      <|>-                      endOfLine $> mempty-                    )+          <|> (NoKind, ) <$ AT.string ";\""+                         <*> fieldsInLine            <|> endOfLine $> (NoKind, mempty)         )    where+    fieldsInLine :: Parser CTagFields+    fieldsInLine = separator *> parseFields <* endOfLine+                   <|>+                   endOfLine $> mempty+     separator :: Parser Char     separator = AT.char '\t' @@ -83,37 +73,25 @@     parseFileName :: Parser TagFileName     parseFileName = TagFileName <$> AT.takeWhile (/= '\t') -    parseExCommand :: Parser ExCommand-    parseExCommand = (\x -> ExCommand $ Text.take (Text.length x - 1) x)-                 <$> AT.scan "" go-                 <*  AT.anyChar+    parseExSearchCommand :: Parser ExCommand+    parseExSearchCommand = ExCommand <$> AT.scan (Nothing, '\0', '\\') go       where-        -- go until either eol or ';"' sequence is found.-        go :: String -> Char -> Maybe String--        go !s c  | -- eol-                   take (length Utils.endOfLine) (c : s)-                     == reverse Utils.endOfLine-                              = Nothing--                 | -- ';"' sequence-                   l == "\";" = Nothing+        go :: (Maybe Char, Char, Char) -> Char -> Maybe (Maybe Char, Char, Char)+        go (Nothing, c0, c1) delim+          -- Support both forward and backward searches.+          | delim == '/' || delim == '?' = go (Just delim, c0, c1) delim+          | otherwise                    = Nothing -                 | otherwise  = Just l-          where-            l = take 2 (c : s)+        go (jdelim@(Just delim), c0, c1) c2+          -- Continue until the next unescaped delimiter.+          | c0 /= '\\' && c1 == delim = Nothing+          | otherwise                 = Just (jdelim, c1, c2)      -- We only parse `TagLine` or `TagCommand`.     parseTagAddress :: Parser CTagAddress-    parseTagAddress =-          TagLine <$> AT.decimal <* (endOfLine <|> void (AT.string ";\""))-      <|>-          TagCommand <$> parseExCommand--    parseKindField :: Parser CTagKind-    parseKindField =-      charToTagKind <$>-        (AT.string "kind:" *> AT.satisfy notTabOrNewLine)+    parseTagAddress = TagLine <$> AT.decimal+                      <|>+                      TagCommand <$> parseExSearchCommand      parseFields :: Parser CTagFields     parseFields = TagFields <$> AT.sepBy parseField separator
src/GhcTags/CTag/Utils.hs view
@@ -9,21 +9,22 @@  tagKindToChar :: CTagKind -> Maybe Char tagKindToChar tk = case tk of+    -- No 'F' kind since it's for a filename.     TkModule                  -> Just 'M'     TkTerm                    -> Just '`'-    TkFunction                -> Just 'λ'-    TkTypeConstructor         -> Just 'Λ'+    TkFunction                -> Just 'f'+    TkTypeConstructor         -> Just 'A'     TkDataConstructor         -> Just 'c'     TkGADTConstructor         -> Just 'g'     TkRecordField             -> Just 'r'-    TkTypeSynonym             -> Just '≡'-    TkTypeSignature           -> Just '⊢'+    TkTypeSynonym             -> Just '='+    TkTypeSignature           -> Just ':'     TkPatternSynonym          -> Just 'p'     TkTypeClass               -> Just 'C'     TkTypeClassMember         -> Just 'm'     TkTypeClassInstance       -> Just 'i'-    TkTypeFamily              -> Just 'f'-    TkTypeFamilyInstance      -> Just 'F'+    TkTypeFamily              -> Just 'T'+    TkTypeFamilyInstance      -> Just 't'     TkDataTypeFamily          -> Just 'd'     TkDataTypeFamilyInstance  -> Just 'D'     TkForeignImport           -> Just 'I'@@ -37,19 +38,19 @@ charToTagKind c = case c of      'M' -> TkModule      '`' -> TkTerm-     'λ' -> TkFunction-     'Λ' -> TkTypeConstructor+     'f' -> TkFunction+     'A' -> TkTypeConstructor      'c' -> TkDataConstructor      'g' -> TkGADTConstructor      'r' -> TkRecordField-     '≡' -> TkTypeSynonym-     '⊢' -> TkTypeSignature+     '=' -> TkTypeSynonym+     ':' -> TkTypeSignature      'p' -> TkPatternSynonym      'C' -> TkTypeClass      'm' -> TkTypeClassMember      'i' -> TkTypeClassInstance-     'f' -> TkTypeFamily-     'F' -> TkTypeFamilyInstance+     't' -> TkTypeFamily+     'T' -> TkTypeFamilyInstance      'd' -> TkDataTypeFamily      'D' -> TkDataTypeFamilyInstance      'I' -> TkForeignImport
src/GhcTags/Config/Args.hs view
@@ -23,14 +23,16 @@   , aTagFile      :: FilePath   , aThreads      :: Int   , aSourcePaths  :: SourcePaths+  , aExModeSearch :: Bool   } deriving Show  argsParser :: Int -> Parser Args argsParser defaultThreads = do-  aTagType     <- ctags <|> etags-  aTagFile0    <- tagFile-  aThreads     <- threads-  aSourcePaths <- (SourceArgs <$> sourcePaths) <|> (ConfigFile <$> configFile)+  aTagType      <- ctags <|> etags+  aTagFile0     <- tagFile+  aThreads      <- threads+  aSourcePaths  <- (SourceArgs <$> sourcePaths) <|> (ConfigFile <$> configFile)+  aExModeSearch <- exModeSearch   pure $ Args { aTagFile = if null aTagFile0                            then defaultOutputFile aTagType                            else aTagFile0@@ -62,9 +64,6 @@                           <> showDefaultWith id                           <> help "Configuration file" -    sourcePaths :: Parser [FilePath]-    sourcePaths = some . argument str $ metavar "<source paths...>"-     threads :: Parser Int     threads = option auto $ long "threads"                          <> short 'j'@@ -72,6 +71,13 @@                          <> value defaultThreads                          <> showDefault                          <> help "Number of threads to use"++    sourcePaths :: Parser [FilePath]+    sourcePaths = some . argument str $ metavar "<source paths...>"++    exModeSearch :: Parser Bool+    exModeSearch = switch $ long "ex-mode-search"+                         <> help "Use Ex mode commands instead of line numbers (ctags)"  parseArgs :: Int -> [String] -> IO Args parseArgs defaultThreads = handleParseResult . execParserPure defaultPrefs opts
src/Main.hs view
@@ -182,7 +182,7 @@     forM_ pcs $ generateTagsForProject (aThreads args) wd     setNumCapabilities 1 -    cleanTagMap <- withMVar (wdTags wd) cleanupTags+    cleanTagMap <- withMVar (wdTags wd) (cleanupTags args)     writeTags (aTagFile args) cleanTagMap     withMVar (wdTimes wd) $ writeTimes (timesFile args) <=< cleanupTimes cleanTagMap   where@@ -190,10 +190,15 @@      initWorkerData :: Args -> Int -> IO WorkerData     initWorkerData args threads = do-      wdTags  <- newMVar =<< case aTagType args of+      tags@DirtyTags{dtTags} <- case aTagType args of         ETag -> readTags SingETag (aTagFile args)         CTag -> readTags SingCTag (aTagFile args)-      wdTimes <- newMVar =<< readTimes (timesFile args)+      -- If tags are empty there is no point looking at mtimes.+      mtimes <- if Map.null dtTags+        then pure Map.empty+        else readTimes (timesFile args)+      wdTags  <- newMVar tags+      wdTimes <- newMVar mtimes       wdQueue <- newTBQueueIO (fromIntegral threads)       pure WorkerData{..} @@ -303,8 +308,8 @@                $ getGhcTags hsModule       in Map.map (Updated True) $!! tags -cleanupTags :: DirtyTags -> IO Tags-cleanupTags DirtyTags{..} = do+cleanupTags :: Args -> DirtyTags -> IO Tags+cleanupTags args DirtyTags{..} = do   newTags <- (`Map.traverseMaybeWithKey` dtTags) $ \file (Updated updated tags) -> do     let path = T.unpack $ getTagFileName file     -- The file might not exists even though it was updated, e.g. when .x files@@ -314,7 +319,9 @@     if | exists && updated -> do            let cleanedTags = ignoreSimilarClose $ sortBy compareNAK tags            case dtKind of-             SingCTag -> addExCommands  file cleanedTags+             SingCTag -> if aExModeSearch args+               then addExCommands file cleanedTags+               else pure $ Just cleanedTags              SingETag -> addFileOffsets file cleanedTags        | exists && not updated -> pure $ Just tags        | otherwise -> pure Nothing@@ -346,8 +353,8 @@              )     ignoreSimilarClose tags = tags --- | Convert 'tagAddress' of CTags to an ex command as editors such as Kate or--- VSCode don't support jumping to a line number and require a line match.+-- | Convert 'tagAddress' of CTags to an Ex mode search command as some editors+-- (e.g. Kate) don't support jumping to a line number and require a line match. addExCommands :: TagFileName -> [CTag] -> IO (Maybe [CTag]) addExCommands file tags = do   let path = T.unpack $ getTagFileName file@@ -364,8 +371,13 @@       TagCommand{}        -> Just tag       TagLine lineNo      -> do         line <- fileLines V.!? (lineNo - 1)-        let exCommand = mconcat ["/^" , T.decodeUtf8 line, "$/"]-        pure tag { tagAddr = TagCommand $ ExCommand exCommand }+        let TagFields fields = tagFields tag+            -- Ex mode forward search command. Slashes need to be escaped.+            exCommand = T.concat ["/^", T.replace "/" "\\/" $ T.decodeUtf8 line, "$/"]+        pure tag+          { tagAddr = TagCommand $ ExCommand exCommand+          , tagFields = TagFields $ TagField "line" (T.pack $ show lineNo) : fields+          }  -- | Add file offsets to etags from a specific file. addFileOffsets :: TagFileName -> [ETag] -> IO (Maybe [ETag])