强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 8 章:Universal Ctags 新特性

第 8 章:Universal Ctags 新特性

8.1 概述

Universal Ctags 是 Exuberant Ctags 的活跃社区继承者,引入了大量新特性。本章深入探讨这些改进。

Universal Ctags 的核心改进方向:

  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
  │ 新输出格式   │   │ 新语言支持   │   │ 解析器改进   │
  │             │   │             │   │             │
  │ • JSON      │   │ • Markdown  │   │ • 子语言    │
  │ • xref      │   │ • YAML      │   │ • 多行正则  │
  │ • XML       │   │ • TOML      │   │ • PCRE2     │
  │             │   │ • Rust      │   │ • packcc    │
  └─────────────┘   └─────────────┘   └─────────────┘
         │                │                │
         ▼                ▼                ▼
  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
  │ 更好工具集成  │   │ 更广语言覆盖  │   │ 更精确解析   │
  └─────────────┘   └─────────────┘   └─────────────┘

8.2 JSON 输出

Universal Ctags 引入了 JSON 格式输出,这是最重要的新特性之一。

基本用法

# 生成 JSON 格式输出
ctags --output-format=json -f - src/main.c

# 输出示例(每行一个 JSON 对象):
{"_type": "tag", "name": "main", "path": "src/main.c", "pattern": "/^int main(){$/", "kind": "function", "signature": "(int argc, char *argv[])"}
{"_type": "tag", "name": "config", "path": "src/main.c", "pattern": "/^static Config config;$/", "kind": "variable"}

JSON 格式 vs 传统格式

传统格式:
main	src/main.c	/^int main(){$/;"	f	signature:(int argc, char *argv[])

JSON 格式:
{"_type":"tag","name":"main","path":"src/main.c","pattern":"/^int main(){$/","kind":"function","signature":"(int argc, char *argv[])"}

JSON 优势:
  ✓ 结构化,易于程序解析
  ✓ 不依赖 Tab 分隔
  ✓ 可直接被 jq, Python json 等处理
  ✓ 字段名明确,不需要记忆字段顺序

伪标签(Pseudo Tags)的 JSON 输出

ctags --output-format=json --pseudo-tags=+TAG_OUTPUT_MODE -f - .

# 输出:
{"_type":"ptag", "name": "TAG_OUTPUT_MODE", "path": "u-ctags", "pattern": "u-ctags or e-ctags"}
{"_type":"ptag", "name": "TAG_FILE_SORTED", "path": "1", "pattern": "0=unsorted, 1=sorted, 2=foldcase"}

使用 jq 处理 JSON 输出

# 安装 jq(如未安装)
sudo apt install jq

# 列出所有函数名
ctags --output-format=json -f - . | jq -r 'select(.kind == "function") | .name'

# 列出所有类及其成员
ctags --output-format=json -f - . | jq -r 'select(.kind == "class" or .kind == "member") | "\(.kind): \(.name)"'

# 按文件分组统计
ctags --output-format=json -f - . | jq -r '.path' | sort | uniq -c | sort -rn

# 查找特定符号
ctags --output-format=json -f - . | jq 'select(.name == "main")'

# 列出所有带签名的函数
ctags --output-format=json -f - . | jq -r 'select(.signature) | "\(.name)\(.signature)"'

# 统计每种语言的标签数
ctags --output-format=json -f - . | jq -r '.language // "unknown"' | sort | uniq -c | sort -rn

# 查找某个作用域内的所有成员
ctags --output-format=json --fields='*' -f - . | \
    jq 'select(.scope == "class:MyClass") | .name'

使用 Python 处理 JSON 输出

#!/usr/bin/env python3
"""使用 Python 处理 ctags JSON 输出"""

import json
import subprocess

def get_tags(path=".", language=None):
    """获取项目标签"""
    cmd = ["ctags", "--output-format=json", "-f", "-", "-R"]
    if language:
        cmd.extend([f"--languages={language}"])
    cmd.append(path)

    result = subprocess.run(cmd, capture_output=True, text=True)
    tags = []
    for line in result.stdout.strip().split('\n'):
        if line:
            try:
                tags.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return tags

def find_functions(tags, name=None):
    """查找函数定义"""
    functions = [t for t in tags if t.get("kind") == "function"]
    if name:
        functions = [f for f in functions if name.lower() in f["name"].lower()]
    return functions

def build_class_hierarchy(tags):
    """构建类层次结构"""
    classes = [t for t in tags if t.get("kind") == "class"]
    members = [t for t in tags if "scope" in t and t["scope"].startswith("class:")]

    hierarchy = {}
    for cls in classes:
        cls_name = cls["name"]
        hierarchy[cls_name] = {
            "file": cls["path"],
            "members": [m for m in members
                       if m.get("scope") == f"class:{cls_name}"]
        }
    return hierarchy

# 使用示例
if __name__ == "__main__":
    tags = get_tags(".")

    # 列出所有函数
    print("=== Functions ===")
    for func in find_functions(tags):
        sig = func.get("signature", "()")
        print(f"  {func['name']}{sig} @ {func['path']}")

    # 列出所有类
    print("\n=== Classes ===")
    for t in tags:
        if t.get("kind") == "class":
            print(f"  {t['name']} @ {t['path']}")

8.3 新语言支持

Markdown 解析器

# Markdown 标题标签化
ctags --list-kinds=Markdown

# 输出:
# c  chapters    [on]    chapters (h1, h2, h3...)

# 测试
cat > test.md << 'EOF'
# Chapter 1: Introduction

Some text here.

## Section 1.1

More text.

### Subsection 1.1.1

Details.

# Chapter 2: Getting Started

EOF

ctags -f - test.md

# 输出:
# Chapter 1: Introduction       test.md    /^# Chapter 1: Introduction$/       c
# Section 1.1                   test.md    /^## Section 1.1$/                  c
# Subsection 1.1.1              test.md    /^### Subsection 1.1.1$/            c
# Chapter 2: Getting Started     test.md    /^# Chapter 2: Getting Started$/    c

Markdown 配置优化:

# .ctags.d/markdown.ctags
--map-Markdown=+.md
--map-Markdown=+.markdown
--map-Markdown=+.mdown
--map-Markdown=+.mkd

# 只索引 h1 和 h2 标题(减少噪音)
# --kinds-Markdown=c  默认已开启

YAML 解析器

ctags --list-kinds=YAML

# 测试
cat > config.yml << 'EOF'
server:
  host: localhost
  port: 8080
database:
  url: postgres://localhost/mydb
  pool:
    max: 10
    min: 2
EOF

ctags -f - config.yml

TOML 解析器

ctags --list-kinds=TOML

cat > config.toml << 'EOF'
[server]
host = "localhost"
port = 8080

[database]
url = "postgres://localhost/mydb"

[database.pool]
max = 10
min = 2
EOF

ctags -f - config.toml

其他新语言

语言Kind说明
Rustmodule, function, struct, enum, trait, impl, type, union完整支持
Gopackage, function, type, struct, interface, member, variable, constant完整支持
Kotlinclass, function, property, interface, object, enum较好支持
Dartclass, function, variable, constructor, method, enum较好支持
Juliafunction, module, struct, abstract, constant基础支持
Elixirmodule, function, macro, callback, protocol基础支持
Zigfunction, variable, struct, enum, union, field基础支持
Nimfunction, variable, type, enum, const, proc基础支持
# 查看某个语言的完整信息
ctags --list-kinds-full=Rust
ctags --list-kinds-full=Go
ctags --list-kinds-full=Kotlin

8.4 子语言解析器(Subparsers)

Universal Ctags 引入了子语言(subparser)概念,允许在一个文件中解析多种语言。

HTML 内嵌语言

┌─────────────────────────────────────────┐
│ HTML 文件                                │
│                                         │
│  ┌──────────────────────────────────┐   │
│  │ <style>                         │   │
│  │   .btn { color: red; }     ← CSS│   │
│  │ </style>                        │   │
│  └──────────────────────────────────┘   │
│                                         │
│  ┌──────────────────────────────────┐   │
│  │ <script>                        │   │
│  │   function init() {}   ← JS     │   │
│  │   class App {}         ← JS     │   │
│  │ </script>                       │   │
│  └──────────────────────────────────┘   │
│                                         │
│  <!-- 嵌入的 PHP -->                     │
│  <?php function render() {} ?> ← PHP    │
│                                         │
└─────────────────────────────────────────┘
# 查看 HTML 的子解析器
ctags --list-subparsers=HTML

# 输出:
# NAME                          BASEPARSER DIRECTION
# CSS                           HTML       DOWN
# JavaScript                    HTML       DOWN
# PHP                           HTML       SUB

# 测试
cat > test.html << 'EOF'
<html>
<head>
<style>
.btn { display: block; }
#header { width: 100%; }
</style>
<script>
function initApp() {
    console.log("ready");
}
class MyComponent {
    render() { return "<div></div>"; }
}
</script>
</head>
<body></body>
</html>
EOF

ctags -f - test.html

# 输出包含:
# .btn            test.html    ...    c (CSS class selector)
# header          test.html    ...    i (CSS id selector)
# initApp         test.html    ...    f (JavaScript function)
# MyComponent     test.html    ...    c (JavaScript class)
# render          test.html    ...    m (JavaScript method)

Vue 文件解析

# .ctags.d/vue.ctags
--map-HTML=+.vue

# Vue 单文件组件 <script> 中的 JavaScript 会被自动解析

控制子语言

# 只解析 HTML 中的 JavaScript(不解析 CSS)
ctags --submap-HTML=+JavaScript --submap-HTML=-CSS .

# 反之亦然
ctags --submap-HTML=+CSS --submap-HTML=-JavaScript .

8.5 伪标签增强

Universal Ctags 生成的伪标签(pseudo tags)比 Exuberant Ctags 更丰富。

# 查看所有伪标签
head -20 tags

# 输出示例:
# !_TAG_FILE_FORMAT	2	/extended format; --format=1 will not append ;" to lines/
# !_TAG_FILE_SORTED	1	/0=unsorted, 1=sorted, 2=foldcase/
# !_TAG_OUTPUT_FILESEP	slash	/slash or backslash/
# !_TAG_OUTPUT_MODE	u-ctags	/u-ctags or e-ctags/
# !_TAG_PATTERN_LENGTH_LIMIT	96	/0 for no limit/
# !_TAG_PROC_CWD	/path/to/project//
# !_TAG_PROGRAM_AUTHOR	Universal Ctags Team	//
# !_TAG_PROGRAM_NAME	Universal Ctags	/Derived from Exuberant Ctags/
# !_TAG_PROGRAM_URL	https://ctags.io/	/official site/
# !_TAG_PROGRAM_VERSION	6.1.0	/6.1.0/

有用的伪标签

伪标签说明用途
TAG_FILE_SORTED文件是否排序编辑器选择查找算法
TAG_OUTPUT_MODE输出模式u-ctagse-ctags
TAG_PROGRAM_VERSION程序版本兼容性检查
TAG_PROC_CWD处理时的工作目录路径解析

控制伪标签输出

# 禁用所有伪标签
--pseudo-tags=-'*'

# 只输出特定伪标签
--pseudo-tags=+TAG_FILE_SORTED
--pseudo-tags=+TAG_PROGRAM_VERSION

# 启用额外的伪标签
--pseudo-tags=+TAG_OUTPUT_MODE

8.6 改进的 C/C++ 解析器

模板支持

// C++ 模板
template<typename T>
class Container {
public:
    void push(const T& item);
    T pop();
};

// Universal Ctags 可以提取模板信息
// Container<T>  →  template:<typename T>
ctags --fields=+S+t -f - src/*.cpp
# Container	src/main.cpp	...	c	template:<typename T>
# push	src/main.cpp	...	m	signature:(const T& item)

Lambda 支持

auto add = [](int a, int b) { return a + b; };

constexpr 支持

constexpr int max_size = 1024;

using 声明

using IntVector = std::vector<int>;
namespace fs = std::filesystem;

8.7 packcc 解析器生成器

Universal Ctags 内置了 packcc——一个 PEG(Parsing Expression Grammar)解析器生成器,使得编写新的语言解析器更加容易。

packcc vs 正则

正则定义(--regex-*):
  ✓ 简单,快速上手
  ✓ 适合简单的单行模式
  ✗ 无法处理嵌套结构
  ✗ 无法处理上下文相关语法

packcc(PEG 语法):
  ✓ 可以处理复杂语法
  ✓ 可以处理嵌套结构
  ✓ 有良好的错误处理
  ✗ 需要编写 C 代码
  ✗ 编译进 ctags

packcc 解析器示例

# parsers/mylang.peg(简化示例)

# 顶层语法规则
program <- statement*

# 语句
statement <- func_def / class_def / import_stmt

# 函数定义
func_def <- "function" S identifier S "(" params ")" S block
{
    /* 生成标签 */
    makeSimpleTag(NXT_func_def, MyLangKinds, K_FUNCTION);
}

# 类定义
class_def <- "class" S identifier S block
{
    makeSimpleTag(NXT_class_def, MyLangKinds, K_CLASS);
}

# 标识符
identifier <- [a-zA-Z_][a-zA-Z0-9_]*

# 空白
S <- [ \t\n]*

# 参数列表
params <- identifier ("," S identifier)*

# 块
block <- "{" [^}]* "}"

📖 扩展阅读:packcc 解析器开发参见 Universal Ctags 自定义解析器开发文档


8.8 字符编码改进

# 查看支持的编码
ctags --list-features | grep iconv

# 启用字符编码转换
--input-encoding=utf-8
--output-encoding=utf-8

# 多编码支持
--input-encoding-utf8=utf-8
--input-encoding-cn=gbk
--input-encoding-jp=euc-jp

# 自动检测编码
--input-encoding=auto

8.9 wildcard(通配符)支持

# 使用通配符指定文件
ctags --languages=C 'src/*.c'

# 递归通配
ctags --languages=Python '**/*.py'

# 注意:需要 shell 或 ctags 的通配符支持
# 如果 shell 不展开,ctags 自身可以处理(需要 wildcard 特性)
ctags --list-features | grep wildcard

8.10 版本检查与兼容性

# 检查版本
ctags --version
# Universal Ctags 6.1.0, Copyright (C) 2015-2024 Universal Ctags Team

# 检查特性
ctags --list-features

# 期望输出包含:
# json          - JSON 输出
# pcre2         - PCRE2 正则引擎
# wildcard      - 通配符支持
# iconv         - 字符编码转换
# packcc        - PEG 解析器生成器
# sandbox       - 安全沙箱

# 向后兼容性测试
# Exuberant Ctags 的选项大多兼容
ctags --version 2>&1 | grep -i "exuberant" || echo "Universal Ctags"

# 检查不兼容项
# --list-kinds=LANG  → --list-kinds-full=LANG(新增)
# --output-format=json(新增)
# --mline-regex-*(新增)

8.11 本章小结

新特性说明实用价值
JSON 输出--output-format=json便于脚本和工具处理
子语言解析器HTML+JS+CSS 一体化Web 开发
新语言Markdown, Rust, Go, Kotlin…更广的语言覆盖
多行正则--mline-regex-复杂模式匹配
packccPEG 解析器生成器开发复杂语言解析器
PCRE2更强大的正则引擎复杂正则模式
伪标签增强更丰富的元数据工具集成

扩展阅读


上一章 ← 第 7 章:高级特性 · 下一章 → 第 9 章:工具链集成