reveal-cli 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plugins/c-header.yaml +89 -0
- plugins/gdscript.yaml +96 -0
- plugins/python.yaml +94 -0
- plugins/yaml.yaml +87 -0
- reveal/__init__.py +20 -0
- reveal/_archive_old_v0.1/analyzers/__init__.py +39 -0
- reveal/_archive_old_v0.1/analyzers/base.py +143 -0
- reveal/_archive_old_v0.1/analyzers/bash_analyzer.py +202 -0
- reveal/_archive_old_v0.1/analyzers/csharp_analyzer.py +167 -0
- reveal/_archive_old_v0.1/analyzers/gdscript_analyzer.py +201 -0
- reveal/_archive_old_v0.1/analyzers/go_analyzer.py +230 -0
- reveal/_archive_old_v0.1/analyzers/javascript_analyzer.py +272 -0
- reveal/_archive_old_v0.1/analyzers/json_analyzer.py +143 -0
- reveal/_archive_old_v0.1/analyzers/markdown_analyzer.py +116 -0
- reveal/_archive_old_v0.1/analyzers/php_analyzer.py +166 -0
- reveal/_archive_old_v0.1/analyzers/python_analyzer.py +118 -0
- reveal/_archive_old_v0.1/analyzers/rust_analyzer.py +202 -0
- reveal/_archive_old_v0.1/analyzers/sql_analyzer.py +534 -0
- reveal/_archive_old_v0.1/analyzers/text_analyzer.py +35 -0
- reveal/_archive_old_v0.1/analyzers/toml_analyzer.py +137 -0
- reveal/_archive_old_v0.1/analyzers/treesitter_base.py +276 -0
- reveal/_archive_old_v0.1/analyzers/yaml_analyzer.py +127 -0
- reveal/_archive_old_v0.1/breadcrumbs.py +147 -0
- reveal/_archive_old_v0.1/cli.py +445 -0
- reveal/_archive_old_v0.1/core.py +136 -0
- reveal/_archive_old_v0.1/detectors.py +29 -0
- reveal/_archive_old_v0.1/formatters.py +272 -0
- reveal/_archive_old_v0.1/grep_filter.py +85 -0
- reveal/_archive_old_v0.1/plugin_loader.py +178 -0
- reveal/_archive_old_v0.1/registry.py +286 -0
- reveal/analyzers/__init__.py +23 -0
- reveal/analyzers/go.py +13 -0
- reveal/analyzers/jupyter_analyzer.py +226 -0
- reveal/analyzers/markdown.py +79 -0
- reveal/analyzers/python.py +15 -0
- reveal/analyzers/rust.py +13 -0
- reveal/analyzers/yaml_json.py +110 -0
- reveal/base.py +193 -0
- reveal/main.py +216 -0
- reveal/tests/__init__.py +1 -0
- reveal/tests/test_json_yaml_line_numbers.py +238 -0
- reveal/tests/test_line_numbers.py +151 -0
- reveal/tests/test_toml_analyzer.py +220 -0
- reveal/tree_view.py +105 -0
- reveal/treesitter.py +282 -0
- reveal_cli-0.2.0.dist-info/METADATA +319 -0
- reveal_cli-0.2.0.dist-info/RECORD +51 -0
- reveal_cli-0.2.0.dist-info/WHEEL +5 -0
- reveal_cli-0.2.0.dist-info/entry_points.txt +2 -0
- reveal_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
- reveal_cli-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Markdown file analyzer."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict, Any, List
|
|
5
|
+
from .base import BaseAnalyzer
|
|
6
|
+
from ..registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register(['.md', '.markdown', '.mdown'], name='Markdown', icon='📝')
|
|
10
|
+
class MarkdownAnalyzer(BaseAnalyzer):
|
|
11
|
+
"""Analyzer for Markdown documentation files"""
|
|
12
|
+
|
|
13
|
+
def analyze_structure(self) -> Dict[str, Any]:
|
|
14
|
+
"""Analyze Markdown structure."""
|
|
15
|
+
headings = []
|
|
16
|
+
paragraph_count = 0
|
|
17
|
+
code_block_count = 0
|
|
18
|
+
list_count = 0
|
|
19
|
+
|
|
20
|
+
in_code_block = False
|
|
21
|
+
current_paragraph = False
|
|
22
|
+
|
|
23
|
+
for line in self.lines:
|
|
24
|
+
stripped = line.strip()
|
|
25
|
+
|
|
26
|
+
# Code blocks
|
|
27
|
+
if stripped.startswith('```'):
|
|
28
|
+
in_code_block = not in_code_block
|
|
29
|
+
if in_code_block:
|
|
30
|
+
code_block_count += 1
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
if in_code_block:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
# Headings
|
|
37
|
+
if stripped.startswith('#'):
|
|
38
|
+
match = re.match(r'^(#{1,3})\s+(.+)$', stripped)
|
|
39
|
+
if match:
|
|
40
|
+
level = len(match.group(1))
|
|
41
|
+
title = match.group(2)
|
|
42
|
+
headings.append((level, title))
|
|
43
|
+
current_paragraph = False
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
# Lists
|
|
47
|
+
if re.match(r'^\s*[-*+]\s+', line) or re.match(r'^\s*\d+\.\s+', line):
|
|
48
|
+
list_count += 1
|
|
49
|
+
current_paragraph = False
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
# Paragraphs
|
|
53
|
+
if stripped and not current_paragraph:
|
|
54
|
+
paragraph_count += 1
|
|
55
|
+
current_paragraph = True
|
|
56
|
+
elif not stripped:
|
|
57
|
+
current_paragraph = False
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
'headings': headings,
|
|
61
|
+
'paragraph_count': paragraph_count,
|
|
62
|
+
'code_block_count': code_block_count,
|
|
63
|
+
'list_count': list_count
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def generate_preview(self) -> List[tuple[int, str]]:
|
|
67
|
+
"""Generate Markdown preview."""
|
|
68
|
+
preview = []
|
|
69
|
+
in_frontmatter = False
|
|
70
|
+
frontmatter_start = False
|
|
71
|
+
first_heading_found = False
|
|
72
|
+
lines_after_heading = 0
|
|
73
|
+
in_code_block = False
|
|
74
|
+
|
|
75
|
+
for i, line in enumerate(self.lines, 1):
|
|
76
|
+
stripped = line.strip()
|
|
77
|
+
|
|
78
|
+
# Frontmatter detection
|
|
79
|
+
if i == 1 and stripped == '---':
|
|
80
|
+
in_frontmatter = True
|
|
81
|
+
frontmatter_start = True
|
|
82
|
+
preview.append((i, line))
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if in_frontmatter:
|
|
86
|
+
preview.append((i, line))
|
|
87
|
+
if stripped == '---':
|
|
88
|
+
in_frontmatter = False
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Code blocks
|
|
92
|
+
if stripped.startswith('```'):
|
|
93
|
+
in_code_block = not in_code_block
|
|
94
|
+
if in_code_block:
|
|
95
|
+
preview.append((i, f"[Code block: {stripped[3:] or 'unknown'}]"))
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
if in_code_block:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# First heading + paragraph
|
|
102
|
+
if stripped.startswith('#'):
|
|
103
|
+
preview.append((i, line))
|
|
104
|
+
first_heading_found = True
|
|
105
|
+
lines_after_heading = 0
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if first_heading_found and lines_after_heading < 5:
|
|
109
|
+
if stripped:
|
|
110
|
+
preview.append((i, line))
|
|
111
|
+
lines_after_heading += 1
|
|
112
|
+
|
|
113
|
+
if len(preview) >= 20:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
return preview[:30]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PHP file analyzer using tree-sitter.
|
|
3
|
+
|
|
4
|
+
Extracts:
|
|
5
|
+
- Classes
|
|
6
|
+
- Functions
|
|
7
|
+
- Methods
|
|
8
|
+
- Traits
|
|
9
|
+
- Interfaces
|
|
10
|
+
- Namespaces
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Dict, Any, List
|
|
14
|
+
from .treesitter_base import TreeSitterAnalyzer, TREE_SITTER_AVAILABLE
|
|
15
|
+
from ..registry import register
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@register(['.php'], name='PHP', icon='🐘')
|
|
19
|
+
class PHPAnalyzer(TreeSitterAnalyzer):
|
|
20
|
+
"""
|
|
21
|
+
Analyzer for PHP source files.
|
|
22
|
+
|
|
23
|
+
Features:
|
|
24
|
+
- Classes, interfaces, traits
|
|
25
|
+
- Functions and methods
|
|
26
|
+
- Namespaces
|
|
27
|
+
- Use statements
|
|
28
|
+
|
|
29
|
+
All positions are exact (tree-sitter preserves source locations).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
language_name = 'php'
|
|
33
|
+
|
|
34
|
+
def __init__(self, lines: List[str], **kwargs):
|
|
35
|
+
"""Initialize PHP analyzer."""
|
|
36
|
+
self.node_type_map = {
|
|
37
|
+
'function': 'function_definition',
|
|
38
|
+
'class': 'class_declaration',
|
|
39
|
+
'import': 'namespace_use_declaration',
|
|
40
|
+
}
|
|
41
|
+
super().__init__(lines, **kwargs)
|
|
42
|
+
|
|
43
|
+
def extract_custom(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
44
|
+
"""Extract PHP-specific items."""
|
|
45
|
+
return {
|
|
46
|
+
'methods': self._extract_nodes('method_declaration', name_field='name'),
|
|
47
|
+
'interfaces': self._extract_nodes('interface_declaration', name_field='name'),
|
|
48
|
+
'traits': self._extract_nodes('trait_declaration', name_field='name'),
|
|
49
|
+
'namespaces': self._extract_namespaces(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def _extract_namespaces(self) -> List[Dict[str, Any]]:
|
|
53
|
+
"""Extract namespace declarations."""
|
|
54
|
+
if not self.tree:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
namespaces = []
|
|
58
|
+
cursor = self.tree.walk()
|
|
59
|
+
|
|
60
|
+
def visit(cursor):
|
|
61
|
+
node = cursor.node
|
|
62
|
+
|
|
63
|
+
if node.type == 'namespace_definition':
|
|
64
|
+
# Get namespace name
|
|
65
|
+
name_node = node.child_by_field_name('name')
|
|
66
|
+
if name_node:
|
|
67
|
+
line = node.start_point[0] + 1
|
|
68
|
+
if self.in_focus_range(line):
|
|
69
|
+
namespaces.append({
|
|
70
|
+
'name': name_node.text.decode('utf-8'),
|
|
71
|
+
'line': line
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
# Recurse
|
|
75
|
+
if cursor.goto_first_child():
|
|
76
|
+
visit(cursor)
|
|
77
|
+
while cursor.goto_next_sibling():
|
|
78
|
+
visit(cursor)
|
|
79
|
+
cursor.goto_parent()
|
|
80
|
+
|
|
81
|
+
visit(cursor)
|
|
82
|
+
return namespaces
|
|
83
|
+
|
|
84
|
+
def format_structure(self, structure: Dict[str, Any]) -> List[str]:
|
|
85
|
+
"""
|
|
86
|
+
Custom formatting for PHP structure output.
|
|
87
|
+
|
|
88
|
+
Groups items logically:
|
|
89
|
+
1. Namespaces
|
|
90
|
+
2. Use statements
|
|
91
|
+
3. Types (classes, interfaces, traits)
|
|
92
|
+
4. Functions
|
|
93
|
+
5. Methods
|
|
94
|
+
"""
|
|
95
|
+
if not TREE_SITTER_AVAILABLE or 'error' in structure:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
lines = []
|
|
99
|
+
|
|
100
|
+
# Namespaces
|
|
101
|
+
namespaces = structure.get('namespaces', [])
|
|
102
|
+
if namespaces:
|
|
103
|
+
lines.append(f"\nNamespaces ({len(namespaces)}):")
|
|
104
|
+
for item in namespaces:
|
|
105
|
+
loc = self.format_location(item['line'])
|
|
106
|
+
lines.append(f" {loc:30} {item['name']}")
|
|
107
|
+
|
|
108
|
+
# Use statements
|
|
109
|
+
imports = structure.get('imports', [])
|
|
110
|
+
if imports:
|
|
111
|
+
lines.append(f"\nUse statements ({len(imports)}):")
|
|
112
|
+
for item in imports[:10]:
|
|
113
|
+
loc = self.format_location(item['line'])
|
|
114
|
+
name = item['name']
|
|
115
|
+
if len(name) > 60:
|
|
116
|
+
name = name[:57] + "..."
|
|
117
|
+
lines.append(f" {loc:30} {name}")
|
|
118
|
+
if len(imports) > 10:
|
|
119
|
+
lines.append(f" ... and {len(imports) - 10} more")
|
|
120
|
+
|
|
121
|
+
# Types
|
|
122
|
+
classes = structure.get('classes', [])
|
|
123
|
+
interfaces = structure.get('interfaces', [])
|
|
124
|
+
traits = structure.get('traits', [])
|
|
125
|
+
|
|
126
|
+
type_count = len(classes) + len(interfaces) + len(traits)
|
|
127
|
+
if type_count > 0:
|
|
128
|
+
lines.append(f"\nTypes ({type_count}):")
|
|
129
|
+
|
|
130
|
+
if classes:
|
|
131
|
+
lines.append(f" Classes ({len(classes)}):")
|
|
132
|
+
for item in classes:
|
|
133
|
+
loc = self.format_location(item['line'])
|
|
134
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
135
|
+
|
|
136
|
+
if interfaces:
|
|
137
|
+
lines.append(f" Interfaces ({len(interfaces)}):")
|
|
138
|
+
for item in interfaces:
|
|
139
|
+
loc = self.format_location(item['line'])
|
|
140
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
141
|
+
|
|
142
|
+
if traits:
|
|
143
|
+
lines.append(f" Traits ({len(traits)}):")
|
|
144
|
+
for item in traits:
|
|
145
|
+
loc = self.format_location(item['line'])
|
|
146
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
147
|
+
|
|
148
|
+
# Functions
|
|
149
|
+
functions = structure.get('functions', [])
|
|
150
|
+
if functions:
|
|
151
|
+
lines.append(f"\nFunctions ({len(functions)}):")
|
|
152
|
+
for item in functions:
|
|
153
|
+
loc = self.format_location(item['line'])
|
|
154
|
+
lines.append(f" {loc:30} function {item['name']}()")
|
|
155
|
+
|
|
156
|
+
# Methods
|
|
157
|
+
methods = structure.get('methods', [])
|
|
158
|
+
if methods:
|
|
159
|
+
lines.append(f"\nMethods ({len(methods)}):")
|
|
160
|
+
for item in methods[:20]:
|
|
161
|
+
loc = self.format_location(item['line'])
|
|
162
|
+
lines.append(f" {loc:30} {item['name']}()")
|
|
163
|
+
if len(methods) > 20:
|
|
164
|
+
lines.append(f" ... and {len(methods) - 20} more")
|
|
165
|
+
|
|
166
|
+
return lines if lines else None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Python file analyzer."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from typing import Dict, Any, List
|
|
5
|
+
from .base import BaseAnalyzer
|
|
6
|
+
from ..registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register(['.py', '.pyx', '.pyi'], name='Python', icon='🐍')
|
|
10
|
+
class PythonAnalyzer(BaseAnalyzer):
|
|
11
|
+
"""Analyzer for Python source files with AST-based analysis"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, lines: List[str], **kwargs):
|
|
14
|
+
super().__init__(lines, **kwargs)
|
|
15
|
+
self.parse_error = None
|
|
16
|
+
self.tree = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
content = '\n'.join(lines)
|
|
20
|
+
self.tree = ast.parse(content)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
self.parse_error = str(e)
|
|
23
|
+
|
|
24
|
+
def analyze_structure(self) -> Dict[str, Any]:
|
|
25
|
+
"""Analyze Python structure."""
|
|
26
|
+
if self.parse_error:
|
|
27
|
+
return {
|
|
28
|
+
'error': self.parse_error,
|
|
29
|
+
'imports': [],
|
|
30
|
+
'classes': [],
|
|
31
|
+
'functions': [],
|
|
32
|
+
'assignments': []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
imports = []
|
|
36
|
+
classes = []
|
|
37
|
+
functions = []
|
|
38
|
+
assignments = []
|
|
39
|
+
|
|
40
|
+
for node in ast.walk(self.tree):
|
|
41
|
+
if isinstance(node, ast.Import):
|
|
42
|
+
for alias in node.names:
|
|
43
|
+
imports.append(alias.name)
|
|
44
|
+
elif isinstance(node, ast.ImportFrom):
|
|
45
|
+
module = node.module or ''
|
|
46
|
+
for alias in node.names:
|
|
47
|
+
imports.append(f"{module}.{alias.name}" if module else alias.name)
|
|
48
|
+
|
|
49
|
+
# Only top-level elements
|
|
50
|
+
for node in self.tree.body:
|
|
51
|
+
if isinstance(node, ast.ClassDef):
|
|
52
|
+
classes.append({'name': node.name, 'line': node.lineno})
|
|
53
|
+
elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
|
|
54
|
+
functions.append({'name': node.name, 'line': node.lineno})
|
|
55
|
+
elif isinstance(node, ast.Assign):
|
|
56
|
+
for target in node.targets:
|
|
57
|
+
if isinstance(target, ast.Name):
|
|
58
|
+
assignments.append({'name': target.id, 'line': node.lineno})
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
'imports': imports,
|
|
62
|
+
'classes': classes,
|
|
63
|
+
'functions': functions,
|
|
64
|
+
'assignments': assignments
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def generate_preview(self) -> List[tuple[int, str]]:
|
|
68
|
+
"""Generate Python preview."""
|
|
69
|
+
preview = []
|
|
70
|
+
|
|
71
|
+
# Module docstring
|
|
72
|
+
if self.tree and isinstance(self.tree.body[0], ast.Expr) and isinstance(self.tree.body[0].value, ast.Constant):
|
|
73
|
+
docstring_node = self.tree.body[0]
|
|
74
|
+
# Find docstring in lines
|
|
75
|
+
for i, line in enumerate(self.lines[:20], 1):
|
|
76
|
+
if '"""' in line or "'''" in line:
|
|
77
|
+
preview.append((i, line))
|
|
78
|
+
# Add next few lines if multiline docstring
|
|
79
|
+
if line.count('"""') == 1 or line.count("'''") == 1:
|
|
80
|
+
for j in range(i, min(i + 10, len(self.lines) + 1)):
|
|
81
|
+
if j > i:
|
|
82
|
+
preview.append((j, self.lines[j - 1]))
|
|
83
|
+
if '"""' in self.lines[j - 1][1:] or "'''" in self.lines[j - 1][1:]:
|
|
84
|
+
break
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
if self.parse_error:
|
|
88
|
+
# Fallback to first 20 lines
|
|
89
|
+
for i, line in enumerate(self.lines[:20], 1):
|
|
90
|
+
if (i, line) not in preview:
|
|
91
|
+
preview.append((i, line))
|
|
92
|
+
return preview
|
|
93
|
+
|
|
94
|
+
# Class signatures + first docstring line
|
|
95
|
+
for node in self.tree.body:
|
|
96
|
+
if isinstance(node, ast.ClassDef):
|
|
97
|
+
class_line = node.lineno
|
|
98
|
+
preview.append((class_line, self.lines[class_line - 1]))
|
|
99
|
+
|
|
100
|
+
# Get class docstring
|
|
101
|
+
if node.body and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant):
|
|
102
|
+
doc_line = node.body[0].lineno
|
|
103
|
+
preview.append((doc_line, self.lines[doc_line - 1]))
|
|
104
|
+
|
|
105
|
+
# Function signatures + first docstring line
|
|
106
|
+
for node in self.tree.body:
|
|
107
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
108
|
+
func_line = node.lineno
|
|
109
|
+
preview.append((func_line, self.lines[func_line - 1]))
|
|
110
|
+
|
|
111
|
+
# Get function docstring
|
|
112
|
+
if node.body and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant):
|
|
113
|
+
doc_line = node.body[0].lineno
|
|
114
|
+
preview.append((doc_line, self.lines[doc_line - 1]))
|
|
115
|
+
|
|
116
|
+
# Sort by line number and limit
|
|
117
|
+
preview = sorted(list(set(preview)), key=lambda x: x[0])
|
|
118
|
+
return preview[:30]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rust file analyzer using tree-sitter.
|
|
3
|
+
|
|
4
|
+
Demonstrates how easy it is to add language support using TreeSitterAnalyzer.
|
|
5
|
+
This entire analyzer is ~100 lines including docs and formatting!
|
|
6
|
+
|
|
7
|
+
Extracts:
|
|
8
|
+
- Functions (fn)
|
|
9
|
+
- Structs
|
|
10
|
+
- Enums
|
|
11
|
+
- Traits
|
|
12
|
+
- Impls (trait implementations)
|
|
13
|
+
- Use statements (imports)
|
|
14
|
+
- Mods (module declarations)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Dict, Any, List
|
|
18
|
+
from .treesitter_base import TreeSitterAnalyzer, TREE_SITTER_AVAILABLE
|
|
19
|
+
from ..registry import register
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register(['.rs'], name='Rust', icon='🦀')
|
|
23
|
+
class RustAnalyzer(TreeSitterAnalyzer):
|
|
24
|
+
"""
|
|
25
|
+
Analyzer for Rust source files.
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- Functions, structs, enums, traits with line numbers
|
|
29
|
+
- Impl blocks (trait implementations)
|
|
30
|
+
- Module structure
|
|
31
|
+
- Use statements (imports)
|
|
32
|
+
- Macro definitions
|
|
33
|
+
|
|
34
|
+
All positions are exact (tree-sitter preserves source locations).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Set language for tree-sitter
|
|
38
|
+
language_name = 'rust'
|
|
39
|
+
|
|
40
|
+
def __init__(self, lines: List[str], **kwargs):
|
|
41
|
+
"""Initialize Rust analyzer."""
|
|
42
|
+
# Map reveal's generic names to Rust's tree-sitter node types
|
|
43
|
+
self.node_type_map = {
|
|
44
|
+
'function': 'function_item',
|
|
45
|
+
'struct': 'struct_item',
|
|
46
|
+
'class': 'struct_item', # Rust uses structs, not classes
|
|
47
|
+
'import': 'use_declaration',
|
|
48
|
+
}
|
|
49
|
+
super().__init__(lines, **kwargs)
|
|
50
|
+
|
|
51
|
+
def extract_custom(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
52
|
+
"""Extract Rust-specific items."""
|
|
53
|
+
return {
|
|
54
|
+
'enums': self._extract_nodes('enum_item', name_field='name'),
|
|
55
|
+
'traits': self._extract_nodes('trait_item', name_field='name'),
|
|
56
|
+
'impls': self._extract_impl_blocks(),
|
|
57
|
+
'mods': self._extract_nodes('mod_item', name_field='name'),
|
|
58
|
+
'macros': self._extract_nodes('macro_definition', name_field='name'),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def _extract_impl_blocks(self) -> List[Dict[str, Any]]:
|
|
62
|
+
"""
|
|
63
|
+
Extract impl blocks with special handling.
|
|
64
|
+
|
|
65
|
+
Rust impl blocks look like:
|
|
66
|
+
- impl Foo { ... } (inherent impl)
|
|
67
|
+
- impl Trait for Foo { ... } (trait impl)
|
|
68
|
+
"""
|
|
69
|
+
if not self.tree:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
impls = []
|
|
73
|
+
cursor = self.tree.walk()
|
|
74
|
+
|
|
75
|
+
def visit(cursor):
|
|
76
|
+
node = cursor.node
|
|
77
|
+
|
|
78
|
+
if node.type == 'impl_item':
|
|
79
|
+
# Try to get type being implemented
|
|
80
|
+
type_node = node.child_by_field_name('type')
|
|
81
|
+
trait_node = node.child_by_field_name('trait')
|
|
82
|
+
|
|
83
|
+
if trait_node and type_node:
|
|
84
|
+
# impl Trait for Type
|
|
85
|
+
trait_name = trait_node.text.decode('utf-8')
|
|
86
|
+
type_name = type_node.text.decode('utf-8')
|
|
87
|
+
name = f"{trait_name} for {type_name}"
|
|
88
|
+
elif type_node:
|
|
89
|
+
# impl Type
|
|
90
|
+
type_name = type_node.text.decode('utf-8')
|
|
91
|
+
name = type_name
|
|
92
|
+
else:
|
|
93
|
+
name = "<impl>"
|
|
94
|
+
|
|
95
|
+
line = node.start_point[0] + 1
|
|
96
|
+
if self.in_focus_range(line):
|
|
97
|
+
impls.append({
|
|
98
|
+
'name': name,
|
|
99
|
+
'line': line
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
# Recurse
|
|
103
|
+
if cursor.goto_first_child():
|
|
104
|
+
visit(cursor)
|
|
105
|
+
while cursor.goto_next_sibling():
|
|
106
|
+
visit(cursor)
|
|
107
|
+
cursor.goto_parent()
|
|
108
|
+
|
|
109
|
+
visit(cursor)
|
|
110
|
+
return impls
|
|
111
|
+
|
|
112
|
+
def format_structure(self, structure: Dict[str, Any]) -> List[str]:
|
|
113
|
+
"""
|
|
114
|
+
Custom formatting for Rust structure output.
|
|
115
|
+
|
|
116
|
+
Groups items logically:
|
|
117
|
+
1. Module/Crate info
|
|
118
|
+
2. Imports (use statements)
|
|
119
|
+
3. Types (structs, enums, traits)
|
|
120
|
+
4. Implementations
|
|
121
|
+
5. Functions
|
|
122
|
+
6. Macros
|
|
123
|
+
"""
|
|
124
|
+
if not TREE_SITTER_AVAILABLE or 'error' in structure:
|
|
125
|
+
return None # Use default formatting
|
|
126
|
+
|
|
127
|
+
lines = []
|
|
128
|
+
|
|
129
|
+
# Use statements
|
|
130
|
+
imports = structure.get('imports', [])
|
|
131
|
+
if imports:
|
|
132
|
+
lines.append(f"\nUse statements ({len(imports)}):")
|
|
133
|
+
for item in imports[:10]: # Limit display
|
|
134
|
+
loc = self.format_location(item['line'])
|
|
135
|
+
# Shorten long imports
|
|
136
|
+
name = item['name']
|
|
137
|
+
if len(name) > 60:
|
|
138
|
+
name = name[:57] + "..."
|
|
139
|
+
lines.append(f" {loc:30} {name}")
|
|
140
|
+
if len(imports) > 10:
|
|
141
|
+
lines.append(f" ... and {len(imports) - 10} more")
|
|
142
|
+
|
|
143
|
+
# Types section
|
|
144
|
+
structs = structure.get('structs', [])
|
|
145
|
+
enums = structure.get('enums', [])
|
|
146
|
+
traits = structure.get('traits', [])
|
|
147
|
+
|
|
148
|
+
type_count = len(structs) + len(enums) + len(traits)
|
|
149
|
+
if type_count > 0:
|
|
150
|
+
lines.append(f"\nTypes ({type_count}):")
|
|
151
|
+
|
|
152
|
+
if structs:
|
|
153
|
+
lines.append(f" Structs ({len(structs)}):")
|
|
154
|
+
for item in structs:
|
|
155
|
+
loc = self.format_location(item['line'])
|
|
156
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
157
|
+
|
|
158
|
+
if enums:
|
|
159
|
+
lines.append(f" Enums ({len(enums)}):")
|
|
160
|
+
for item in enums:
|
|
161
|
+
loc = self.format_location(item['line'])
|
|
162
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
163
|
+
|
|
164
|
+
if traits:
|
|
165
|
+
lines.append(f" Traits ({len(traits)}):")
|
|
166
|
+
for item in traits:
|
|
167
|
+
loc = self.format_location(item['line'])
|
|
168
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
169
|
+
|
|
170
|
+
# Implementations
|
|
171
|
+
impls = structure.get('impls', [])
|
|
172
|
+
if impls:
|
|
173
|
+
lines.append(f"\nImplementations ({len(impls)}):")
|
|
174
|
+
for item in impls:
|
|
175
|
+
loc = self.format_location(item['line'])
|
|
176
|
+
lines.append(f" {loc:30} impl {item['name']}")
|
|
177
|
+
|
|
178
|
+
# Functions
|
|
179
|
+
functions = structure.get('functions', [])
|
|
180
|
+
if functions:
|
|
181
|
+
lines.append(f"\nFunctions ({len(functions)}):")
|
|
182
|
+
for item in functions:
|
|
183
|
+
loc = self.format_location(item['line'])
|
|
184
|
+
lines.append(f" {loc:30} fn {item['name']}()")
|
|
185
|
+
|
|
186
|
+
# Modules
|
|
187
|
+
mods = structure.get('mods', [])
|
|
188
|
+
if mods:
|
|
189
|
+
lines.append(f"\nModules ({len(mods)}):")
|
|
190
|
+
for item in mods:
|
|
191
|
+
loc = self.format_location(item['line'])
|
|
192
|
+
lines.append(f" {loc:30} mod {item['name']}")
|
|
193
|
+
|
|
194
|
+
# Macros
|
|
195
|
+
macros = structure.get('macros', [])
|
|
196
|
+
if macros:
|
|
197
|
+
lines.append(f"\nMacros ({len(macros)}):")
|
|
198
|
+
for item in macros:
|
|
199
|
+
loc = self.format_location(item['line'])
|
|
200
|
+
lines.append(f" {loc:30} {item['name']}!")
|
|
201
|
+
|
|
202
|
+
return lines if lines else None
|