Skip to content

Commit ffdf969

Browse files
committed
Add SCIP-based indexing for Zig
1 parent 9b19d1e commit ffdf969

File tree

11 files changed

+1350
-211
lines changed

11 files changed

+1350
-211
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ The easiest way to get started with any MCP-compatible application:
9494
**System & Low-Level:**
9595
- C/C++ (`.c`, `.cpp`, `.h`, `.hpp`)
9696
- Rust (`.rs`)
97-
- Zig (`.zig`)
97+
- Zig (`.zig`, `.zon`)
9898
- Go (`.go`)
9999

100100
**Object-Oriented:**

src/code_index_mcp/analyzers/analyzer_factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .javascript_analyzer import JavaScriptAnalyzer
88
from .java_analyzer import JavaAnalyzer
99
from .objective_c_analyzer import ObjectiveCAnalyzer
10+
from .zig_analyzer import ZigAnalyzer
1011

1112

1213
class AnalyzerFactory:
@@ -74,6 +75,7 @@ def _initialize_factory():
7475
AnalyzerFactory.register(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'], JavaScriptAnalyzer)
7576
AnalyzerFactory.register(['.java'], JavaAnalyzer)
7677
AnalyzerFactory.register(['.m', '.mm'], ObjectiveCAnalyzer)
78+
AnalyzerFactory.register(['.zig', '.zon'], ZigAnalyzer)
7779

7880

7981
# Initialize on import
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Zig language analyzer."""
2+
3+
import os
4+
from typing import Dict, Any
5+
from .base_analyzer import LanguageAnalyzer
6+
from .analysis_result import AnalysisResult
7+
8+
9+
class ZigAnalyzer(LanguageAnalyzer):
10+
"""Analyzer for Zig files."""
11+
12+
def analyze(self, content: str, file_path: str, full_path: str = None) -> AnalysisResult:
13+
"""Analyze Zig file content."""
14+
lines = content.splitlines()
15+
16+
# Create result object
17+
_, ext = os.path.splitext(file_path)
18+
result = AnalysisResult(
19+
file_path=file_path,
20+
line_count=self._count_lines(content),
21+
size_bytes=self._get_file_size(content, full_path),
22+
extension=ext,
23+
analysis_type="zig"
24+
)
25+
26+
# Zig-specific analysis
27+
for i, line in enumerate(lines):
28+
line_stripped = line.strip()
29+
30+
# Skip empty lines and comments
31+
if not line_stripped or line_stripped.startswith('//'):
32+
continue
33+
34+
# Check for imports
35+
if '@import(' in line_stripped:
36+
# Extract module name from @import("module") or @import("module.zig")
37+
import_match = line_stripped.split('@import(')
38+
if len(import_match) > 1:
39+
import_name = import_match[1].split(')')[0].strip('"\'')
40+
result.add_symbol("import", f"@import({import_name})", i + 1)
41+
42+
if '@cImport(' in line_stripped:
43+
result.add_symbol("import", "@cImport", i + 1)
44+
45+
# Check for function definitions
46+
if ' fn ' in line_stripped or line_stripped.startswith('fn '):
47+
# Handle both 'pub fn' and 'fn' declarations
48+
if 'fn ' in line_stripped:
49+
fn_part = line_stripped.split('fn ', 1)[1]
50+
func_name = fn_part.split('(')[0].strip()
51+
if func_name:
52+
symbol_type = "function"
53+
if 'pub fn' in line_stripped:
54+
symbol_type = "function_public"
55+
result.add_symbol(symbol_type, func_name, i + 1)
56+
57+
# Check for struct definitions
58+
if ' struct ' in line_stripped or ' struct{' in line_stripped:
59+
# Look for const Name = struct pattern
60+
if 'const ' in line_stripped and '=' in line_stripped:
61+
const_part = line_stripped.split('const ', 1)[1]
62+
struct_name = const_part.split('=')[0].strip()
63+
if struct_name:
64+
result.add_symbol("struct", struct_name, i + 1)
65+
66+
# Check for enum definitions
67+
if ' enum ' in line_stripped or ' enum{' in line_stripped or ' enum(' in line_stripped:
68+
# Look for const Name = enum pattern
69+
if 'const ' in line_stripped and '=' in line_stripped:
70+
const_part = line_stripped.split('const ', 1)[1]
71+
enum_name = const_part.split('=')[0].strip()
72+
if enum_name:
73+
result.add_symbol("enum", enum_name, i + 1)
74+
75+
# Check for error set definitions
76+
if ' error{' in line_stripped or '= error{' in line_stripped:
77+
# Look for const Name = error pattern
78+
if 'const ' in line_stripped and '=' in line_stripped:
79+
const_part = line_stripped.split('const ', 1)[1]
80+
error_name = const_part.split('=')[0].strip()
81+
if error_name:
82+
result.add_symbol("error_set", error_name, i + 1)
83+
84+
# Check for test blocks
85+
if line_stripped.startswith('test '):
86+
test_name = line_stripped[5:].split('{')[0].strip().strip('"')
87+
if test_name:
88+
result.add_symbol("test", test_name, i + 1)
89+
90+
# Check for const/var declarations at module level
91+
if line_stripped.startswith('pub const ') or line_stripped.startswith('const '):
92+
# Skip struct/enum/error definitions already handled
93+
if not any(keyword in line_stripped for keyword in [' struct', ' enum', ' error{']):
94+
const_part = line_stripped.replace('pub const ', '').replace('const ', '')
95+
if ':' in const_part or '=' in const_part:
96+
const_name = const_part.split(':')[0].split('=')[0].strip()
97+
if const_name:
98+
symbol_type = "const_public" if line_stripped.startswith('pub const') else "const"
99+
result.add_symbol(symbol_type, const_name, i + 1)
100+
101+
if line_stripped.startswith('pub var ') or line_stripped.startswith('var '):
102+
var_part = line_stripped.replace('pub var ', '').replace('var ', '')
103+
if ':' in var_part or '=' in var_part:
104+
var_name = var_part.split(':')[0].split('=')[0].strip()
105+
if var_name:
106+
symbol_type = "var_public" if line_stripped.startswith('pub var') else "var"
107+
result.add_symbol(symbol_type, var_name, i + 1)
108+
109+
return result

src/code_index_mcp/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
'.fs', '.fsx', # F#
4242
'.clj', '.cljs', # Clojure
4343
'.vim', # Vim script
44-
'.zig', # Zig
44+
'.zig', '.zon', # Zig
4545

4646
# Web and markup
4747
'.html', '.htm', # HTML

src/code_index_mcp/scip/factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .strategies.javascript_strategy import JavaScriptStrategy
88
from .strategies.java_strategy import JavaStrategy
99
from .strategies.objective_c_strategy import ObjectiveCStrategy
10+
from .strategies.zig_strategy import ZigStrategy
1011
from .strategies.fallback_strategy import FallbackStrategy
1112
from ..constants import SUPPORTED_EXTENSIONS
1213

@@ -34,6 +35,7 @@ def _register_all_strategies(self):
3435
JavaScriptStrategy(priority=95),
3536
JavaStrategy(priority=95),
3637
ObjectiveCStrategy(priority=95),
38+
ZigStrategy(priority=95),
3739
]
3840

3941
for strategy in language_strategies:
@@ -117,6 +119,8 @@ def list_supported_extensions(self) -> Set[str]:
117119
supported.update({'.java'})
118120
elif isinstance(strategy, ObjectiveCStrategy):
119121
supported.update({'.m', '.mm'})
122+
elif isinstance(strategy, ZigStrategy):
123+
supported.update({'.zig', '.zon'})
120124
elif isinstance(strategy, FallbackStrategy):
121125
# Fallback supports everything, but we don't want to list everything here
122126
pass

0 commit comments

Comments
 (0)