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 +7/−0
- README.md +3/−2
- ghc-tags.cabal +2/−2
- src/GhcTags/CTag/Header.hs +7/−7
- src/GhcTags/CTag/Parser.hs +27/−49
- src/GhcTags/CTag/Utils.hs +13/−12
- src/GhcTags/Config/Args.hs +13/−7
- src/Main.hs +22/−10
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 @@ [](https://github.com/arybczak/ghc-tags/actions?query=branch%3Amaster) [](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])