srcodex 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.
Files changed (52) hide show
  1. srcodex/__init__.py +0 -0
  2. srcodex/backend/__init__.py +0 -0
  3. srcodex/backend/chat.py +79 -0
  4. srcodex/backend/main.py +98 -0
  5. srcodex/backend/services/__init__.py +0 -0
  6. srcodex/backend/services/claude_service.py +754 -0
  7. srcodex/backend/services/config_loader.py +113 -0
  8. srcodex/backend/services/file_access_tools.py +279 -0
  9. srcodex/backend/services/file_tree.py +480 -0
  10. srcodex/backend/services/graph_tools.py +874 -0
  11. srcodex/backend/services/logger_setup.py +91 -0
  12. srcodex/backend/services/session_manager.py +81 -0
  13. srcodex/backend/services/status_tracker.py +91 -0
  14. srcodex/cli.py +255 -0
  15. srcodex/core/__init__.py +0 -0
  16. srcodex/core/config.py +113 -0
  17. srcodex/core/logger.py +23 -0
  18. srcodex/indexer/__init__.py +0 -0
  19. srcodex/indexer/cscope_client.py +183 -0
  20. srcodex/indexer/ctags_compat.py +223 -0
  21. srcodex/indexer/ctags_parser.py +456 -0
  22. srcodex/indexer/explorer.py +135 -0
  23. srcodex/indexer/field_access_analyzer.py +436 -0
  24. srcodex/indexer/indexer.py +664 -0
  25. srcodex/indexer/reference_ingestor.py +293 -0
  26. srcodex/indexer/reference_resolver.py +544 -0
  27. srcodex/tui/__init__.py +0 -0
  28. srcodex/tui/app.py +103 -0
  29. srcodex/tui/app.tcss +24 -0
  30. srcodex/tui/components/__init__.py +0 -0
  31. srcodex/tui/components/bars/__init__.py +0 -0
  32. srcodex/tui/components/bars/chat_header.py +48 -0
  33. srcodex/tui/components/bars/code_tab_bar.py +157 -0
  34. srcodex/tui/components/bars/footer_bar.py +128 -0
  35. srcodex/tui/components/bars/left_tab.py +54 -0
  36. srcodex/tui/components/logger.py +57 -0
  37. srcodex/tui/components/panels/__init__.py +0 -0
  38. srcodex/tui/components/panels/chat_panel.py +523 -0
  39. srcodex/tui/components/panels/code_panel.py +229 -0
  40. srcodex/tui/components/panels/side_panel.py +128 -0
  41. srcodex/tui/components/views/__init__.py +0 -0
  42. srcodex/tui/components/views/explorer_view.py +20 -0
  43. srcodex/tui/components/views/search_view.py +148 -0
  44. srcodex/tui/components/widgets/__init__.py +0 -0
  45. srcodex/tui/components/widgets/file_browser.py +16 -0
  46. srcodex/tui/components/widgets/find_box.py +85 -0
  47. srcodex-0.2.0.dist-info/METADATA +170 -0
  48. srcodex-0.2.0.dist-info/RECORD +52 -0
  49. srcodex-0.2.0.dist-info/WHEEL +5 -0
  50. srcodex-0.2.0.dist-info/entry_points.txt +2 -0
  51. srcodex-0.2.0.dist-info/licenses/LICENSE +21 -0
  52. srcodex-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cscope Client - Query cscope database for cross-references
4
+ This module provides a Python interface to query cscope databases.
5
+ It runs cscope commands and parses the output into structured data.
6
+ """
7
+
8
+ import subprocess
9
+ from pathlib import Path
10
+ from typing import List, Dict, Optional
11
+ from dataclasses import dataclass
12
+
13
+
14
+ @dataclass
15
+ class Reference:
16
+ """A single cross-reference result from cscope"""
17
+ file_path: str # File where the reference occurs
18
+ function: str # Function name containing the reference
19
+ line_number: int # Line number in the file
20
+ line_text: str # The actual line of code
21
+
22
+ class CscopeClient:
23
+ """Client for querying cscope database"""
24
+
25
+ def __init__(self, cscope_dir: str):
26
+ """
27
+ Initialize cscope client
28
+ Args:
29
+ cscope_dir: Directory containing cscope.out and related files
30
+ """
31
+ self.cscope_dir = Path(cscope_dir)
32
+ self.cscope_out = self.cscope_dir / "cscope.out"
33
+
34
+ if not self.cscope_out.exists():
35
+ raise FileNotFoundError(
36
+ f"Cscope database not found: {self.cscope_out}\n"
37
+ f"Run indexer with --cscope flag to build it."
38
+ )
39
+
40
+ def _run_query(self, query_type: int, symbol: str) -> List[Reference]:
41
+ """
42
+ Run a cscope query and parse results
43
+ Args:
44
+ query_type: Cscope query type (0-8)
45
+ symbol: Symbol to search for
46
+ Returns:
47
+ List of Reference objects
48
+ """
49
+ try:
50
+ result = subprocess.run(
51
+ ['cscope', '-dL', f'-{query_type}', symbol],
52
+ cwd=self.cscope_dir,
53
+ capture_output=True,
54
+ text=True,
55
+ check=True
56
+ )
57
+
58
+ return self._parse_output(result.stdout)
59
+
60
+ except subprocess.CalledProcessError as e:
61
+ # Cscope returns non-zero if no results found
62
+ if e.returncode == 1 and not e.stderr:
63
+ return []
64
+ raise RuntimeError(f"Cscope query failed: {e.stderr}")
65
+
66
+ except FileNotFoundError:
67
+ raise RuntimeError(
68
+ "cscope command not found. Install with: sudo apt install cscope"
69
+ )
70
+
71
+ def _parse_output(self, output: str) -> List[Reference]:
72
+ """
73
+ Parse cscope output into Reference objects
74
+ Cscope output format:
75
+ filename function_name line_number line_text
76
+ """
77
+ references = []
78
+
79
+ for line in output.strip().split('\n'):
80
+ if not line:
81
+ continue
82
+
83
+ # Split on whitespace (max 3 splits to preserve line_text)
84
+ parts = line.split(None, 3)
85
+
86
+ if len(parts) < 3:
87
+ continue
88
+
89
+ file_path = parts[0]
90
+ function = parts[1]
91
+ line_number = int(parts[2])
92
+ line_text = parts[3] if len(parts) > 3 else ''
93
+
94
+ references.append(Reference(
95
+ file_path=file_path,
96
+ function=function,
97
+ line_number=line_number,
98
+ line_text=line_text
99
+ ))
100
+
101
+ return references
102
+
103
+ """
104
+ Below are API's to find references to symbols
105
+ Args: Symbol name, Function name, Text to search for
106
+ Returns: Lists of symbol references, functions...
107
+ """
108
+ def find_symbol(self, symbol: str) -> List[Reference]:
109
+ # List of references where this symbol appears
110
+ return self._run_query(0, symbol)
111
+
112
+ def find_definition(self, symbol: str) -> List[Reference]:
113
+ # List with one reference (the definition location)
114
+ return self._run_query(1, symbol)
115
+
116
+ def find_callees(self, function: str) -> List[Reference]:
117
+ # List of functions called by this function
118
+ return self._run_query(2, function)
119
+
120
+ def find_callers(self, function: str) -> List[Reference]:
121
+ # List of functions that call this function
122
+ return self._run_query(3, function)
123
+
124
+ def find_text(self, text: str) -> List[Reference]:
125
+ # List of references where this text appears
126
+ return self._run_query(4, text)
127
+
128
+ def find_egrep_pattern(self, pattern: str) -> List[Reference]:
129
+ # List of references matching the pattern
130
+ return self._run_query(6, pattern)
131
+
132
+ def find_files_including(self, filename: str) -> List[Reference]:
133
+ # List of files that include this file
134
+ return self._run_query(8, filename)
135
+
136
+ def get_stats(self) -> Dict[str, any]:
137
+ # Dictionary with database information
138
+ stats = {
139
+ 'database_path': str(self.cscope_out),
140
+ 'database_exists': self.cscope_out.exists(),
141
+ }
142
+
143
+ if self.cscope_out.exists():
144
+ stats['database_size_mb'] = self.cscope_out.stat().st_size / (1024 * 1024)
145
+
146
+ # Check for cscope.files
147
+ cscope_files = self.cscope_dir / "cscope.files"
148
+ if cscope_files.exists():
149
+ with open(cscope_files) as f:
150
+ stats['indexed_files'] = len(f.readlines())
151
+
152
+ return stats
153
+
154
+
155
+ # Convenience function for quick queries
156
+ def query_cscope(cscope_dir: str, query_type: str, symbol: str) -> List[Reference]:
157
+ """
158
+ Convenience function for one-off cscope queries
159
+ Args:
160
+ cscope_dir: Directory containing cscope.out
161
+ query_type: Query type (symbol, definition, callers, callees, text, includes)
162
+ symbol: Symbol/text to search for
163
+ Returns:
164
+ List of references
165
+ """
166
+ client = CscopeClient(cscope_dir)
167
+
168
+ query_map = {
169
+ 'symbol': client.find_symbol,
170
+ 'definition': client.find_definition,
171
+ 'callers': client.find_callers,
172
+ 'callees': client.find_callees,
173
+ 'text': client.find_text,
174
+ 'includes': client.find_files_including,
175
+ }
176
+
177
+ if query_type not in query_map:
178
+ raise ValueError(
179
+ f"Invalid query type: {query_type}\n"
180
+ f"Valid types: {', '.join(query_map.keys())}"
181
+ )
182
+
183
+ return query_map[query_type](symbol)
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SRC Code Explorer - CTags Compatibility Check
4
+ Verifies that Universal CTags outputs expected kind values.
5
+ This prevents silent failures when different ctags versions/builds use different kind names.
6
+ """
7
+
8
+ import subprocess
9
+ import json
10
+ import os
11
+ import tempfile
12
+ from typing import Set
13
+
14
+
15
+ # Expected ctags kind values that type_map in ctags_parser.py knows about
16
+ EXPECTED_KINDS = {
17
+ 'function',
18
+ 'prototype',
19
+ 'variable',
20
+ 'struct',
21
+ 'union',
22
+ 'enum',
23
+ 'enumerator',
24
+ 'typedef',
25
+ 'macro',
26
+ 'member',
27
+ 'header',
28
+ }
29
+
30
+ # Core kinds that MUST appear in test code
31
+ CORE_KINDS = {
32
+ 'function',
33
+ 'prototype',
34
+ 'macro',
35
+ 'typedef',
36
+ 'member',
37
+ 'variable',
38
+ 'enumerator',
39
+ }
40
+
41
+
42
+ def verify_ctags_compatibility(ctags_bin: str = "ctags") -> None:
43
+ """
44
+ This startup check ensures that:
45
+ 1. ctags is installed and working
46
+ 2. Kind values match what our type_map expects
47
+ 3. We fail early with a clear error vs silent data corruption
48
+ """
49
+
50
+ try:
51
+ version_result = subprocess.run(
52
+ [ctags_bin, "--version"],
53
+ capture_output=True,
54
+ check=True,
55
+ text=True
56
+ )
57
+ version_info = version_result.stdout.strip().split('\n')[0]
58
+ except (FileNotFoundError, subprocess.CalledProcessError) as e:
59
+ raise RuntimeError(f"ctags not found or failed to run: {e}")
60
+
61
+ # Minimal test C code with all expected symbol types
62
+ test_code = """
63
+ #define TEST_MACRO 1
64
+ typedef struct { int member_x; } test_struct_t;
65
+ typedef union { int u_val; } test_union_t;
66
+ typedef enum { ENUM_VAL = 0 } test_enum_t;
67
+ void test_func(void);
68
+ void test_func(void) {}
69
+ static int test_static_var = 0;
70
+ int test_global_var;
71
+ """
72
+
73
+ # Write test code to temporary file
74
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.c', delete=False) as f:
75
+ test_file = f.name
76
+ f.write(test_code)
77
+
78
+ try:
79
+ # Run ctags with EXACT PRODUCTION FLAGS
80
+ # This verifies both ctags availability AND flag compatibility
81
+ cmd = [
82
+ ctags_bin,
83
+ "--output-format=json",
84
+ "--fields=+nKSz",
85
+ "--kinds-C=+p",
86
+ "-f", "-",
87
+ test_file
88
+ ]
89
+
90
+ try:
91
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
92
+ except subprocess.CalledProcessError as e:
93
+ # Command failed - likely bad flags
94
+ raise RuntimeError(
95
+ f"\n"
96
+ f"╔══════════════════════════════════════════════════════════════════╗\n"
97
+ f"║ CTAGS COMMAND FAILED ║\n"
98
+ f"╚══════════════════════════════════════════════════════════════════╝\n"
99
+ f"\n"
100
+ f"ctags version: {version_info}\n"
101
+ f"\n"
102
+ f"Command that failed:\n"
103
+ f" {' '.join(cmd)}\n"
104
+ f"\n"
105
+ f"Error output:\n"
106
+ f" {e.stderr}\n"
107
+ f"\n"
108
+ f"This likely means ctags doesn't support the flags we use.\n"
109
+ f"Common issues:\n"
110
+ f" - Old ctags: --kinds-C syntax not supported (try --c-kinds)\n"
111
+ f" - Exuberant ctags: Missing --output-format=json support\n"
112
+ f" - BSD ctags: Not compatible (needs Universal CTags)\n"
113
+ f"\n"
114
+ f"Please install Universal CTags:\n"
115
+ f" Ubuntu/Debian: sudo apt install universal-ctags\n"
116
+ f" macOS: brew install universal-ctags\n"
117
+ )
118
+
119
+ # Extract unique kind values from ctags output
120
+ # ALSO verify 'path' field is present (critical for parse_root)
121
+ observed_kinds = set()
122
+ has_path_field = False
123
+ for line in result.stdout.strip().split('\n'):
124
+ if not line or line.startswith('!'):
125
+ continue
126
+ try:
127
+ tag = json.loads(line)
128
+ kind = tag.get('kind')
129
+ if kind:
130
+ observed_kinds.add(kind)
131
+
132
+ # Check for 'path' field (required for grouping symbols by file)
133
+ if 'path' in tag:
134
+ has_path_field = True
135
+ except json.JSONDecodeError:
136
+ continue
137
+
138
+ # Check 0: Verify 'path' field is present in output
139
+ if not has_path_field:
140
+ raise RuntimeError(
141
+ f"\n"
142
+ f"╔══════════════════════════════════════════════════════════════════╗\n"
143
+ f"║ CTAGS COMPATIBILITY CHECK FAILED ║\n"
144
+ f"╚══════════════════════════════════════════════════════════════════╝\n"
145
+ f"\n"
146
+ f"ctags version: {version_info}\n"
147
+ f"\n"
148
+ f"CRITICAL: 'path' field not found in ctags JSON output!\n"
149
+ f"\n"
150
+ f"The 'path' field is required to group symbols by file.\n"
151
+ f"Without it, parse_root() cannot function.\n"
152
+ f"\n"
153
+ f"Command that produced output:\n"
154
+ f" {' '.join(cmd)}\n"
155
+ f"\n"
156
+ f"Please verify:\n"
157
+ f" - Using Universal CTags (not Exuberant)\n"
158
+ f" - JSON output format enabled\n"
159
+ f" - All required fields present\n"
160
+ )
161
+
162
+ # Check 1: Did we see the core kinds we expect?
163
+ missing_core = CORE_KINDS - observed_kinds
164
+ if missing_core:
165
+ raise RuntimeError(
166
+ f"\n"
167
+ f"╔══════════════════════════════════════════════════════════════════╗\n"
168
+ f"║ CTAGS COMPATIBILITY CHECK FAILED ║\n"
169
+ f"╚══════════════════════════════════════════════════════════════════╝\n"
170
+ f"\n"
171
+ f"ctags version: {version_info}\n"
172
+ f"\n"
173
+ f"Missing expected core kinds: {missing_core}\n"
174
+ f"Expected kinds: {EXPECTED_KINDS}\n"
175
+ f"Observed kinds: {observed_kinds}\n"
176
+ f"\n"
177
+ f"Your ctags installation may be incompatible.\n"
178
+ f"Please install Universal CTags:\n"
179
+ f" Ubuntu/Debian: sudo apt install universal-ctags\n"
180
+ f" macOS: brew install universal-ctags\n"
181
+ )
182
+
183
+ # Check 2: Did we see unexpected kinds not in our type_map?
184
+ unexpected_kinds = observed_kinds - EXPECTED_KINDS
185
+ if unexpected_kinds:
186
+ print(f" Warning: ctags returned unexpected kinds: {unexpected_kinds}")
187
+ print(f" These will be stored as-is in the database.")
188
+ print(f" ctags version: {version_info}")
189
+ print()
190
+
191
+ finally:
192
+ try:
193
+ os.unlink(test_file)
194
+ except OSError:
195
+ pass
196
+
197
+
198
+ def get_ctags_version(ctags_bin: str = "ctags") -> str:
199
+ """
200
+ Get ctags version string for debugging/logging.
201
+ """
202
+ try:
203
+ result = subprocess.run(
204
+ [ctags_bin, "--version"],
205
+ capture_output=True,
206
+ check=True,
207
+ text=True
208
+ )
209
+ return result.stdout.strip().split('\n')[0]
210
+ except (FileNotFoundError, subprocess.CalledProcessError):
211
+ return "unknown (ctags not found)"
212
+
213
+
214
+ # Simple test
215
+ if __name__ == "__main__":
216
+ print("Running ctags compatibility check...")
217
+ try:
218
+ verify_ctags_compatibility()
219
+ print("ctags compatibility check PASSED")
220
+ print(f"Version: {get_ctags_version()}")
221
+ except RuntimeError as e:
222
+ print(f"{e}")
223
+ exit(1)