code-intelligence 1.2.0__py3-none-win_amd64.whl → 2.0.0__py3-none-win_amd64.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.
- code_intelligence/__init__.py +7 -6
- code_intelligence/_code_intelligence.dll +0 -0
- code_intelligence/symdb.py +459 -49
- {code_intelligence-1.2.0.dist-info → code_intelligence-2.0.0.dist-info}/METADATA +1 -1
- code_intelligence-2.0.0.dist-info/RECORD +6 -0
- code_intelligence-1.2.0.dist-info/RECORD +0 -6
- {code_intelligence-1.2.0.dist-info → code_intelligence-2.0.0.dist-info}/WHEEL +0 -0
code_intelligence/__init__.py
CHANGED
|
@@ -3,14 +3,13 @@ from .symdb import (
|
|
|
3
3
|
SymbolType,
|
|
4
4
|
Symbol,
|
|
5
5
|
Location,
|
|
6
|
-
|
|
6
|
+
ImmediateSymbolDatabase,
|
|
7
7
|
LazySymbolDatabase,
|
|
8
|
+
AsyncSymbolDatabase,
|
|
9
|
+
AsyncResult,
|
|
8
10
|
)
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
SymbolDatabase = ImmideateSymbolDatabase
|
|
12
|
-
|
|
13
|
-
__version__ = "1.2.0"
|
|
12
|
+
__version__ = "2.0.0"
|
|
14
13
|
__version_info__ = tuple(int(i) for i in __version__.split('.'))
|
|
15
14
|
|
|
16
15
|
__all__ = [
|
|
@@ -18,7 +17,9 @@ __all__ = [
|
|
|
18
17
|
"SymbolType",
|
|
19
18
|
"Symbol",
|
|
20
19
|
"Location",
|
|
21
|
-
"
|
|
20
|
+
"ImmediateSymbolDatabase",
|
|
22
21
|
"LazySymbolDatabase",
|
|
22
|
+
"AsyncSymbolDatabase",
|
|
23
|
+
"AsyncResult",
|
|
23
24
|
"SymbolDatabase",
|
|
24
25
|
]
|
|
Binary file
|
code_intelligence/symdb.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import ctypes
|
|
3
3
|
import platform
|
|
4
|
-
from enum import IntEnum
|
|
4
|
+
from enum import IntEnum, IntFlag
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
|
|
@@ -9,21 +9,14 @@ from dataclasses import dataclass, field
|
|
|
9
9
|
class Language(IntEnum):
|
|
10
10
|
# UNKNOWN = 0
|
|
11
11
|
PYTHON = 1
|
|
12
|
-
JAVASCRIPT = 2
|
|
13
|
-
TYPESCRIPT = 3
|
|
12
|
+
JAVASCRIPT = 2 # Not implemented yet - enum exists but no parser
|
|
13
|
+
TYPESCRIPT = 3 # Not implemented yet - enum exists but no parser
|
|
14
14
|
CPP = 4
|
|
15
15
|
C = 5
|
|
16
|
-
JAVA = 6
|
|
16
|
+
JAVA = 6 # Not implemented yet - enum exists but no parser
|
|
17
17
|
RUST = 7
|
|
18
18
|
GO = 8
|
|
19
19
|
PHP = 9
|
|
20
|
-
RUBY = 10
|
|
21
|
-
CSHARP = 11
|
|
22
|
-
KOTLIN = 12
|
|
23
|
-
SWIFT = 13
|
|
24
|
-
SCALA = 14
|
|
25
|
-
DART = 15
|
|
26
|
-
LUA = 16
|
|
27
20
|
|
|
28
21
|
|
|
29
22
|
class SymbolType(IntEnum):
|
|
@@ -35,17 +28,15 @@ class SymbolType(IntEnum):
|
|
|
35
28
|
MODULE = 5
|
|
36
29
|
IMPORT = 6
|
|
37
30
|
FROM_IMPORT = 7
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
LOCAL = 17
|
|
48
|
-
TYPE = 18
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SymbolFilter(IntFlag):
|
|
34
|
+
"""Symbol filter flags for find_symbols_in_text (bitwise combinable)"""
|
|
35
|
+
NONE = 0
|
|
36
|
+
CLASSES = 1 << 0
|
|
37
|
+
FUNCTIONS = 1 << 1
|
|
38
|
+
OTHER = 1 << 2
|
|
39
|
+
ALL = CLASSES | FUNCTIONS | OTHER
|
|
49
40
|
|
|
50
41
|
Path_Like = str | Path
|
|
51
42
|
|
|
@@ -92,9 +83,18 @@ class Symbol:
|
|
|
92
83
|
def location(self) -> "Location":
|
|
93
84
|
return self.definition
|
|
94
85
|
|
|
86
|
+
@dataclass(slots=True)
|
|
87
|
+
class SymbolLink:
|
|
88
|
+
"""A clickable link to a symbol in text."""
|
|
89
|
+
start_offset_in_text: int = 0
|
|
90
|
+
end_offset_in_text: int = 0
|
|
91
|
+
symbol: Symbol = field(default_factory=Symbol)
|
|
92
|
+
|
|
93
|
+
def __repr__(self) -> str:
|
|
94
|
+
return f"SymbolLink({self.start_offset_in_text}-{self.end_offset_in_text}, {self.symbol.name})"
|
|
95
|
+
|
|
95
96
|
# --- Private ctypes Implementation ---
|
|
96
97
|
|
|
97
|
-
# Matches ci_location_t (field name 'path')
|
|
98
98
|
class _Location(ctypes.Structure):
|
|
99
99
|
_fields_ = [
|
|
100
100
|
("path", ctypes.c_char_p),
|
|
@@ -104,18 +104,95 @@ class _Location(ctypes.Structure):
|
|
|
104
104
|
("end_column", ctypes.c_uint32),
|
|
105
105
|
]
|
|
106
106
|
|
|
107
|
-
# Matches ci_symbol_t (field name 'definition')
|
|
108
107
|
class _Symbol(ctypes.Structure):
|
|
109
108
|
_fields_ = [
|
|
110
109
|
("name", ctypes.c_char_p),
|
|
111
|
-
("symbol_type", ctypes.
|
|
110
|
+
("symbol_type", ctypes.c_uint8),
|
|
112
111
|
("definition", _Location),
|
|
113
112
|
("parent", ctypes.c_char_p),
|
|
114
113
|
("signature", ctypes.c_char_p),
|
|
115
114
|
("documentation", ctypes.c_char_p),
|
|
115
|
+
("_is_stub", ctypes.c_bool),
|
|
116
|
+
("_target_module", ctypes.c_char_p),
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
class _SymbolLink(ctypes.Structure):
|
|
120
|
+
_fields_ = [
|
|
121
|
+
("start_offset_in_text", ctypes.c_int),
|
|
122
|
+
("end_offset_in_text", ctypes.c_int),
|
|
123
|
+
("symbol", _Symbol),
|
|
116
124
|
]
|
|
117
125
|
|
|
118
126
|
|
|
127
|
+
class _PerfStat(ctypes.Structure):
|
|
128
|
+
_fields_ = [
|
|
129
|
+
("language", ctypes.c_uint8),
|
|
130
|
+
("files", ctypes.c_uint64),
|
|
131
|
+
("symbols", ctypes.c_uint64),
|
|
132
|
+
("bytes_read", ctypes.c_uint64),
|
|
133
|
+
("read_ns", ctypes.c_uint64),
|
|
134
|
+
("parse_ns", ctypes.c_uint64),
|
|
135
|
+
("extract_ns", ctypes.c_uint64),
|
|
136
|
+
("alloc_read_inc_bytes", ctypes.c_uint64),
|
|
137
|
+
("alloc_parse_inc_bytes", ctypes.c_uint64),
|
|
138
|
+
("alloc_extract_inc_bytes", ctypes.c_uint64),
|
|
139
|
+
("rss_max_bytes", ctypes.c_uint64),
|
|
140
|
+
("nodes_visited", ctypes.c_uint64),
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Worker thread result structures - must match C ci_result_t exactly
|
|
145
|
+
class _SuccessSymbols(ctypes.Structure):
|
|
146
|
+
_fields_ = [("symbols", ctypes.POINTER(_Symbol)), ("count", ctypes.c_uint32)]
|
|
147
|
+
|
|
148
|
+
class _SuccessLinks(ctypes.Structure):
|
|
149
|
+
_fields_ = [("links", ctypes.POINTER(_SymbolLink)), ("count", ctypes.c_uint32)]
|
|
150
|
+
|
|
151
|
+
class _SuccessSymbol(ctypes.Structure):
|
|
152
|
+
_fields_ = [("symbol", _Symbol)]
|
|
153
|
+
|
|
154
|
+
class _SuccessJson(ctypes.Structure):
|
|
155
|
+
_fields_ = [("json", ctypes.c_char_p)]
|
|
156
|
+
|
|
157
|
+
class _SuccessBool(ctypes.Structure):
|
|
158
|
+
_fields_ = [("success", ctypes.c_bool)]
|
|
159
|
+
|
|
160
|
+
class _Error(ctypes.Structure):
|
|
161
|
+
_fields_ = [("message", ctypes.c_char_p)]
|
|
162
|
+
|
|
163
|
+
class _ResultUnion(ctypes.Union):
|
|
164
|
+
_fields_ = [
|
|
165
|
+
("success_symbols", _SuccessSymbols),
|
|
166
|
+
("success_links", _SuccessLinks),
|
|
167
|
+
("success_symbol", _SuccessSymbol),
|
|
168
|
+
("success_json", _SuccessJson),
|
|
169
|
+
("success_bool", _SuccessBool),
|
|
170
|
+
("error", _Error),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
class _Result(ctypes.Structure):
|
|
174
|
+
_fields_ = [
|
|
175
|
+
("id", ctypes.c_uint64), # request ID
|
|
176
|
+
("type", ctypes.c_uint8), # ci_result_type_t (RES_SUCCESS=0, RES_ERROR=1)
|
|
177
|
+
("union_data", _ResultUnion),
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# Request type enum (must match C code)
|
|
182
|
+
class RequestType(IntEnum):
|
|
183
|
+
SCAN_FILE = 1
|
|
184
|
+
REMOVE_FILE = 2
|
|
185
|
+
UPDATE_FILE = 3
|
|
186
|
+
FIND_DEFINITIONS = 4
|
|
187
|
+
FIND_REFERENCES = 5
|
|
188
|
+
GET_SYMBOL = 6
|
|
189
|
+
GET_ALL_SYMBOLS = 7
|
|
190
|
+
GET_FILE_SYMBOLS = 8
|
|
191
|
+
FIND_SYMBOLS_IN_TEXT = 9
|
|
192
|
+
EXPORT_JSON = 10
|
|
193
|
+
SHUTDOWN = 11
|
|
194
|
+
|
|
195
|
+
|
|
119
196
|
CURRENT_OS = platform.system().lower()
|
|
120
197
|
|
|
121
198
|
if CURRENT_OS == "darwin":
|
|
@@ -148,24 +225,67 @@ _lib = _load_library("_code_intelligence")
|
|
|
148
225
|
_lib.symbol_db_create.argtypes = []
|
|
149
226
|
_lib.symbol_db_create.restype = ctypes.c_void_p
|
|
150
227
|
|
|
228
|
+
# Worker thread async API signatures
|
|
229
|
+
# Enqueue functions - return request ID
|
|
230
|
+
_lib.enqueue_scan_file.argtypes = [ctypes.c_char_p, ctypes.c_uint8]
|
|
231
|
+
_lib.enqueue_scan_file.restype = ctypes.c_uint64
|
|
232
|
+
|
|
233
|
+
_lib.enqueue_remove_file.argtypes = [ctypes.c_char_p]
|
|
234
|
+
_lib.enqueue_remove_file.restype = ctypes.c_uint64
|
|
235
|
+
|
|
236
|
+
_lib.enqueue_update_file.argtypes = [ctypes.c_char_p, ctypes.c_uint8]
|
|
237
|
+
_lib.enqueue_update_file.restype = ctypes.c_uint64
|
|
238
|
+
|
|
239
|
+
_lib.enqueue_find_definitions.argtypes = [ctypes.c_char_p]
|
|
240
|
+
_lib.enqueue_find_definitions.restype = ctypes.c_uint64
|
|
241
|
+
|
|
242
|
+
_lib.enqueue_find_references.argtypes = [ctypes.c_char_p]
|
|
243
|
+
_lib.enqueue_find_references.restype = ctypes.c_uint64
|
|
244
|
+
|
|
245
|
+
_lib.enqueue_get_symbol.argtypes = [ctypes.c_char_p, ctypes.c_uint32, ctypes.c_uint32]
|
|
246
|
+
_lib.enqueue_get_symbol.restype = ctypes.c_uint64
|
|
247
|
+
|
|
248
|
+
_lib.enqueue_get_all_symbols.argtypes = []
|
|
249
|
+
_lib.enqueue_get_all_symbols.restype = ctypes.c_uint64
|
|
250
|
+
|
|
251
|
+
_lib.enqueue_get_file_symbols.argtypes = [ctypes.c_char_p]
|
|
252
|
+
_lib.enqueue_get_file_symbols.restype = ctypes.c_uint64
|
|
253
|
+
|
|
254
|
+
_lib.enqueue_find_symbols_in_text.argtypes = [ctypes.c_char_p, ctypes.c_uint32]
|
|
255
|
+
_lib.enqueue_find_symbols_in_text.restype = ctypes.c_uint64
|
|
256
|
+
|
|
257
|
+
_lib.enqueue_export_json.argtypes = []
|
|
258
|
+
_lib.enqueue_export_json.restype = ctypes.c_uint64
|
|
259
|
+
|
|
260
|
+
# Poll results function
|
|
261
|
+
_lib.poll_results.argtypes = [ctypes.POINTER(ctypes.c_uint32)]
|
|
262
|
+
_lib.poll_results.restype = ctypes.c_void_p # Returns ci_result_t*
|
|
263
|
+
|
|
264
|
+
# Free results array
|
|
265
|
+
_lib.result_array_free.argtypes = [ctypes.c_void_p]
|
|
266
|
+
_lib.result_array_free.restype = None
|
|
267
|
+
|
|
268
|
+
# Async API is mandatory in this build
|
|
269
|
+
_ASYNC_API_AVAILABLE = True
|
|
270
|
+
|
|
151
271
|
_lib.symbol_db_destroy.argtypes = [ctypes.c_void_p]
|
|
152
272
|
_lib.symbol_db_destroy.restype = None
|
|
153
273
|
|
|
154
274
|
_lib.symbol_db_set_project_root.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
|
|
155
275
|
_lib.symbol_db_set_project_root.restype = None
|
|
156
276
|
|
|
157
|
-
_lib.symbol_db_scan_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.
|
|
277
|
+
_lib.symbol_db_scan_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint8]
|
|
158
278
|
_lib.symbol_db_scan_file.restype = ctypes.c_bool
|
|
159
279
|
|
|
160
280
|
# Batch scan
|
|
161
|
-
_lib.symbol_db_scan_files.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p), ctypes.c_uint32, ctypes.
|
|
281
|
+
_lib.symbol_db_scan_files.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p), ctypes.c_uint32, ctypes.c_uint8]
|
|
162
282
|
_lib.symbol_db_scan_files.restype = ctypes.c_bool
|
|
163
283
|
|
|
164
284
|
_lib.symbol_db_remove_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
|
|
165
285
|
_lib.symbol_db_remove_file.restype = ctypes.c_bool
|
|
166
286
|
|
|
167
287
|
# Update file (re-scan and replace)
|
|
168
|
-
_lib.symbol_db_update_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.
|
|
288
|
+
_lib.symbol_db_update_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint8]
|
|
169
289
|
_lib.symbol_db_update_file.restype = ctypes.c_bool
|
|
170
290
|
|
|
171
291
|
# Arrays with out_count
|
|
@@ -178,6 +298,9 @@ _lib.symbol_db_find_references.restype = ctypes.POINTER(_Symbol)
|
|
|
178
298
|
_lib.symbol_db_get_all_symbols.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32)]
|
|
179
299
|
_lib.symbol_db_get_all_symbols.restype = ctypes.POINTER(_Symbol)
|
|
180
300
|
|
|
301
|
+
_lib.symbol_db_get_file_symbols.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32)]
|
|
302
|
+
_lib.symbol_db_get_file_symbols.restype = ctypes.POINTER(_Symbol)
|
|
303
|
+
|
|
181
304
|
# bool return + out _Symbol
|
|
182
305
|
_lib.symbol_db_get_symbol.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.POINTER(_Symbol)]
|
|
183
306
|
_lib.symbol_db_get_symbol.restype = ctypes.c_bool
|
|
@@ -192,6 +315,21 @@ _lib.ci_flush_coverage.restype = None
|
|
|
192
315
|
_lib.symbol_db_clear.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
193
316
|
_lib.symbol_db_clear.restype = None
|
|
194
317
|
|
|
318
|
+
_lib.symbol_db_dump_stats.argtypes = [ctypes.c_void_p]
|
|
319
|
+
_lib.symbol_db_dump_stats.restype = None
|
|
320
|
+
|
|
321
|
+
_lib.symbol_db_get_stats.argtypes = [ctypes.c_void_p, ctypes.POINTER(_PerfStat), ctypes.c_uint32]
|
|
322
|
+
_lib.symbol_db_get_stats.restype = ctypes.c_uint32
|
|
323
|
+
|
|
324
|
+
_lib.symbol_db_find_symbols_in_text.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32)]
|
|
325
|
+
_lib.symbol_db_find_symbols_in_text.restype = ctypes.POINTER(_SymbolLink)
|
|
326
|
+
|
|
327
|
+
_lib.symbol_db_find_symbols_in_text_filtered.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32)]
|
|
328
|
+
_lib.symbol_db_find_symbols_in_text_filtered.restype = ctypes.POINTER(_SymbolLink)
|
|
329
|
+
|
|
330
|
+
_lib.symbol_db_export_symbols_to_json.argtypes = [ctypes.c_void_p]
|
|
331
|
+
_lib.symbol_db_export_symbols_to_json.restype = ctypes.c_char_p
|
|
332
|
+
|
|
195
333
|
def _flush_coverage():
|
|
196
334
|
try:
|
|
197
335
|
_lib.ci_flush_coverage()
|
|
@@ -203,11 +341,11 @@ def _flush_coverage():
|
|
|
203
341
|
class CStringView:
|
|
204
342
|
"""A view into a C string that delays decoding until needed."""
|
|
205
343
|
__slots__ = ('_ptr', '_decoded')
|
|
206
|
-
|
|
344
|
+
|
|
207
345
|
def __init__(self, c_char_p):
|
|
208
346
|
self._ptr = c_char_p
|
|
209
347
|
self._decoded = None
|
|
210
|
-
|
|
348
|
+
|
|
211
349
|
def __str__(self):
|
|
212
350
|
if self._decoded is None:
|
|
213
351
|
if self._ptr:
|
|
@@ -215,13 +353,13 @@ class CStringView:
|
|
|
215
353
|
else:
|
|
216
354
|
self._decoded = ""
|
|
217
355
|
return self._decoded
|
|
218
|
-
|
|
356
|
+
|
|
219
357
|
def __repr__(self):
|
|
220
358
|
return f"CStringView({str(self)!r})"
|
|
221
|
-
|
|
359
|
+
|
|
222
360
|
def __bool__(self):
|
|
223
361
|
return bool(self._ptr)
|
|
224
|
-
|
|
362
|
+
|
|
225
363
|
def __eq__(self, other):
|
|
226
364
|
if isinstance(other, CStringView):
|
|
227
365
|
return str(self) == str(other)
|
|
@@ -269,39 +407,39 @@ def _to_py_symbol(c_symbol: _Symbol) -> Symbol:
|
|
|
269
407
|
|
|
270
408
|
class SymbolArrayView:
|
|
271
409
|
"""A view into a C symbol array that manages the C memory and provides lazy access."""
|
|
272
|
-
|
|
410
|
+
|
|
273
411
|
def __init__(self, ptr: ctypes.POINTER(_Symbol), count: int):
|
|
274
412
|
self._ptr = ptr
|
|
275
413
|
self._count = count
|
|
276
414
|
self._symbols = None # Lazy converted symbols
|
|
277
|
-
|
|
415
|
+
|
|
278
416
|
def __len__(self):
|
|
279
417
|
return self._count
|
|
280
|
-
|
|
418
|
+
|
|
281
419
|
def __getitem__(self, index):
|
|
282
420
|
if isinstance(index, slice):
|
|
283
421
|
# Handle slice objects
|
|
284
422
|
start, stop, step = index.indices(self._count)
|
|
285
423
|
return [self[i] for i in range(start, stop, step)]
|
|
286
|
-
|
|
424
|
+
|
|
287
425
|
if index < 0:
|
|
288
426
|
index = self._count + index
|
|
289
427
|
if index < 0 or index >= self._count:
|
|
290
428
|
raise IndexError("Symbol array index out of range")
|
|
291
|
-
|
|
429
|
+
|
|
292
430
|
# Lazy conversion - convert only accessed symbols
|
|
293
431
|
if self._symbols is None:
|
|
294
432
|
self._symbols = [None] * self._count
|
|
295
|
-
|
|
433
|
+
|
|
296
434
|
if self._symbols[index] is None:
|
|
297
435
|
self._symbols[index] = _to_py_symbol_lazy(self._ptr[index])
|
|
298
|
-
|
|
436
|
+
|
|
299
437
|
return self._symbols[index]
|
|
300
|
-
|
|
438
|
+
|
|
301
439
|
def __iter__(self):
|
|
302
440
|
for i in range(self._count):
|
|
303
441
|
yield self[i]
|
|
304
|
-
|
|
442
|
+
|
|
305
443
|
def __del__(self):
|
|
306
444
|
# Free the C array when Python object is garbage collected
|
|
307
445
|
if hasattr(self, '_ptr') and self._ptr:
|
|
@@ -325,10 +463,80 @@ def _convert_symbol_array_lazy(ptr: ctypes.POINTER(_Symbol), count: int) -> Symb
|
|
|
325
463
|
if ptr:
|
|
326
464
|
_lib.symbol_array_free(ptr) # Free empty array
|
|
327
465
|
return SymbolArrayView(None, 0)
|
|
328
|
-
|
|
466
|
+
|
|
329
467
|
return SymbolArrayView(ptr, count)
|
|
330
468
|
|
|
331
469
|
|
|
470
|
+
def _to_py_symbol_link(c_link: _SymbolLink) -> SymbolLink:
|
|
471
|
+
"""Convert C symbol link to Python symbol link."""
|
|
472
|
+
return SymbolLink(
|
|
473
|
+
start_offset_in_text=c_link.start_offset_in_text,
|
|
474
|
+
end_offset_in_text=c_link.end_offset_in_text,
|
|
475
|
+
symbol=_to_py_symbol_lazy(c_link.symbol),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _convert_symbol_link_array(ptr: ctypes.POINTER(_SymbolLink), count: int) -> list[SymbolLink]:
|
|
480
|
+
"""Convert C symbol link array to Python list."""
|
|
481
|
+
if not ptr or count <= 0:
|
|
482
|
+
return []
|
|
483
|
+
try:
|
|
484
|
+
return [_to_py_symbol_link(ptr[i]) for i in range(count)]
|
|
485
|
+
finally:
|
|
486
|
+
# Free the array buffer
|
|
487
|
+
_lib.symbol_array_free(ptr)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# --- Async API Result Handling ---
|
|
491
|
+
|
|
492
|
+
@dataclass
|
|
493
|
+
class AsyncResult:
|
|
494
|
+
"""Result from an async operation."""
|
|
495
|
+
request_id: int
|
|
496
|
+
success: bool
|
|
497
|
+
error_message: str | None = None
|
|
498
|
+
data: any = None # Actual result data (symbols, boolean, etc.)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _process_results(results_ptr: ctypes.c_void_p, count: int) -> list[AsyncResult]:
|
|
502
|
+
"""Process results from poll_results.
|
|
503
|
+
|
|
504
|
+
IMPORTANT: All result payloads must become fully Python-owned before we free
|
|
505
|
+
the C results array to avoid use-after-free. Eagerly copy symbols/arrays.
|
|
506
|
+
"""
|
|
507
|
+
if not results_ptr or count <= 0:
|
|
508
|
+
return []
|
|
509
|
+
|
|
510
|
+
# Cast to a flat pointer to _Result entries
|
|
511
|
+
results_array = ctypes.cast(results_ptr, ctypes.POINTER(_Result))
|
|
512
|
+
processed_results = []
|
|
513
|
+
|
|
514
|
+
for i in range(count):
|
|
515
|
+
result = results_array[i]
|
|
516
|
+
request_id = result.id
|
|
517
|
+
result_type = result.type # RES_SUCCESS=0, RES_ERROR=1
|
|
518
|
+
|
|
519
|
+
success = (result_type == 0) # RES_SUCCESS
|
|
520
|
+
error_msg = None
|
|
521
|
+
data = None
|
|
522
|
+
|
|
523
|
+
if not success: # RES_ERROR
|
|
524
|
+
error_msg = result.union_data.error.message.decode("utf-8", errors="replace") if result.union_data.error.message else "Unknown error"
|
|
525
|
+
else:
|
|
526
|
+
# For now, assume all success results are boolean (scan/remove/update operations)
|
|
527
|
+
# TODO: Implement proper request type tracking to handle different result types
|
|
528
|
+
data = result.union_data.success_bool.success
|
|
529
|
+
|
|
530
|
+
processed_results.append(AsyncResult(
|
|
531
|
+
request_id=request_id,
|
|
532
|
+
success=success,
|
|
533
|
+
error_message=error_msg,
|
|
534
|
+
data=data
|
|
535
|
+
))
|
|
536
|
+
|
|
537
|
+
return processed_results
|
|
538
|
+
|
|
539
|
+
|
|
332
540
|
|
|
333
541
|
# --- Backend Implementations ---
|
|
334
542
|
|
|
@@ -336,8 +544,8 @@ class _BaseSymbolDatabase:
|
|
|
336
544
|
"""Base class for code intelligence operations (not for direct use)."""
|
|
337
545
|
def __init__(self):
|
|
338
546
|
self._handle = _lib.symbol_db_create()
|
|
339
|
-
if
|
|
340
|
-
|
|
547
|
+
# Note: _handle may be NULL if creation fails, but all API functions
|
|
548
|
+
# are designed to handle NULL gracefully by returning empty/stub results
|
|
341
549
|
|
|
342
550
|
def __del__(self):
|
|
343
551
|
if hasattr(self, "_handle") and self._handle:
|
|
@@ -349,13 +557,48 @@ class _BaseSymbolDatabase:
|
|
|
349
557
|
|
|
350
558
|
Frees the current native database state and reinitializes it. Any
|
|
351
559
|
previously scanned files, symbols, and project root are discarded.
|
|
352
|
-
|
|
560
|
+
|
|
353
561
|
Args:
|
|
354
562
|
reuse_memory: If True (default), keep allocated arena memory for reuse.
|
|
355
563
|
If False, free all memory and start fresh.
|
|
356
564
|
"""
|
|
357
|
-
|
|
358
|
-
|
|
565
|
+
_lib.symbol_db_clear(self._handle, ctypes.c_int(1 if reuse_memory else 0))
|
|
566
|
+
|
|
567
|
+
def dump_stats(self):
|
|
568
|
+
"""If CI_PROFILE=1, print internal timing counters to stderr."""
|
|
569
|
+
_lib.symbol_db_dump_stats(self._handle)
|
|
570
|
+
|
|
571
|
+
def get_stats(self) -> list[dict]:
|
|
572
|
+
"""Return structured per-language profiling stats when built with CI_PROFILE=1.
|
|
573
|
+
|
|
574
|
+
If profiling is disabled or CI_PROFILE env is not set, returns an empty list.
|
|
575
|
+
"""
|
|
576
|
+
# Query count first
|
|
577
|
+
count = _lib.symbol_db_get_stats(self._handle, None, 0)
|
|
578
|
+
if count == 0:
|
|
579
|
+
return []
|
|
580
|
+
buf = (_PerfStat * count)()
|
|
581
|
+
written = _lib.symbol_db_get_stats(self._handle, buf, ctypes.c_uint32(count))
|
|
582
|
+
out: list[dict] = []
|
|
583
|
+
for i in range(int(written)):
|
|
584
|
+
s = buf[i]
|
|
585
|
+
lang_id = int(s.language)
|
|
586
|
+
lang_name = Language(lang_id).name if lang_id in Language._value2member_map_ else f"LANG_{lang_id}"
|
|
587
|
+
out.append({
|
|
588
|
+
"language": lang_name,
|
|
589
|
+
"files": int(s.files),
|
|
590
|
+
"symbols": int(s.symbols),
|
|
591
|
+
"bytes_read": int(s.bytes_read),
|
|
592
|
+
"read_ns": int(s.read_ns),
|
|
593
|
+
"parse_ns": int(s.parse_ns),
|
|
594
|
+
"extract_ns": int(s.extract_ns),
|
|
595
|
+
"alloc_read_inc_bytes": int(s.alloc_read_inc_bytes),
|
|
596
|
+
"alloc_parse_inc_bytes": int(s.alloc_parse_inc_bytes),
|
|
597
|
+
"alloc_extract_inc_bytes": int(s.alloc_extract_inc_bytes),
|
|
598
|
+
"rss_max_bytes": int(s.rss_max_bytes),
|
|
599
|
+
"nodes_visited": int(s.nodes_visited),
|
|
600
|
+
})
|
|
601
|
+
return out
|
|
359
602
|
|
|
360
603
|
def set_project_root(self, path: Path_Like):
|
|
361
604
|
_lib.symbol_db_set_project_root(self._handle, os.fsencode(path))
|
|
@@ -412,8 +655,50 @@ class _BaseSymbolDatabase:
|
|
|
412
655
|
)
|
|
413
656
|
return _to_py_symbol(out) if ok else None
|
|
414
657
|
|
|
658
|
+
def find_symbols_in_text(self, text: str, *, classes: bool = True, functions: bool = True, other: bool = True) -> list[SymbolLink]:
|
|
659
|
+
"""Find all symbol references in the given text and return clickable links.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
text: Text to search for symbol references
|
|
663
|
+
classes: Include class symbols (default: True)
|
|
664
|
+
functions: Include function/method symbols (default: True)
|
|
665
|
+
other: Include other symbol types (default: True)
|
|
666
|
+
"""
|
|
667
|
+
# Build filter mask using enum constants
|
|
668
|
+
filter_mask = SymbolFilter.NONE
|
|
669
|
+
if classes:
|
|
670
|
+
filter_mask |= SymbolFilter.CLASSES
|
|
671
|
+
if functions:
|
|
672
|
+
filter_mask |= SymbolFilter.FUNCTIONS
|
|
673
|
+
if other:
|
|
674
|
+
filter_mask |= SymbolFilter.OTHER
|
|
675
|
+
|
|
676
|
+
count = ctypes.c_uint32(0)
|
|
677
|
+
ptr = _lib.symbol_db_find_symbols_in_text_filtered(
|
|
678
|
+
self._handle,
|
|
679
|
+
text.encode("utf-8"),
|
|
680
|
+
ctypes.c_uint32(filter_mask),
|
|
681
|
+
ctypes.byref(count),
|
|
682
|
+
)
|
|
683
|
+
return _convert_symbol_link_array(ptr, int(count.value))
|
|
684
|
+
|
|
685
|
+
def export_symbols_to_json(self) -> str:
|
|
686
|
+
"""Export all symbols to JSON format grouped by type (classes, methods, functions).
|
|
415
687
|
|
|
416
|
-
|
|
688
|
+
Returns:
|
|
689
|
+
JSON string containing all symbols organized by type, or empty string if no symbols.
|
|
690
|
+
"""
|
|
691
|
+
result = _lib.symbol_db_export_symbols_to_json(self._handle)
|
|
692
|
+
if result:
|
|
693
|
+
# Convert C string to Python string and free the C memory
|
|
694
|
+
json_str = result.decode("utf-8", errors="replace")
|
|
695
|
+
# Note: The C function returns malloc'd memory, but ctypes automatically
|
|
696
|
+
# handles the memory management for c_char_p return types
|
|
697
|
+
return json_str
|
|
698
|
+
return "{}"
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
class ImmediateSymbolDatabase(_BaseSymbolDatabase):
|
|
417
702
|
"""Symbol database using copying (eager) backend."""
|
|
418
703
|
def find_definitions(self, symbol_name: str) -> list[Symbol]:
|
|
419
704
|
count = ctypes.c_uint32(0)
|
|
@@ -441,6 +726,15 @@ class ImmideateSymbolDatabase(_BaseSymbolDatabase):
|
|
|
441
726
|
ptr = _lib.symbol_db_get_all_symbols(self._handle, ctypes.byref(count))
|
|
442
727
|
return _convert_symbol_array(ptr, int(count.value))
|
|
443
728
|
|
|
729
|
+
def get_file_symbols(self, path: Path_Like) -> list[Symbol]:
|
|
730
|
+
count = ctypes.c_uint32(0)
|
|
731
|
+
ptr = _lib.symbol_db_get_file_symbols(
|
|
732
|
+
self._handle,
|
|
733
|
+
os.fsencode(path),
|
|
734
|
+
ctypes.byref(count),
|
|
735
|
+
)
|
|
736
|
+
return _convert_symbol_array(ptr, int(count.value))
|
|
737
|
+
|
|
444
738
|
|
|
445
739
|
class LazySymbolDatabase(_BaseSymbolDatabase):
|
|
446
740
|
"""Symbol database using lazy backend."""
|
|
@@ -470,3 +764,119 @@ class LazySymbolDatabase(_BaseSymbolDatabase):
|
|
|
470
764
|
ptr = _lib.symbol_db_get_all_symbols(self._handle, ctypes.byref(count))
|
|
471
765
|
return _convert_symbol_array_lazy(ptr, int(count.value))
|
|
472
766
|
|
|
767
|
+
def get_file_symbols(self, path: Path_Like) -> SymbolArrayView:
|
|
768
|
+
count = ctypes.c_uint32(0)
|
|
769
|
+
ptr = _lib.symbol_db_get_file_symbols(
|
|
770
|
+
self._handle,
|
|
771
|
+
os.fsencode(path),
|
|
772
|
+
ctypes.byref(count),
|
|
773
|
+
)
|
|
774
|
+
return _convert_symbol_array_lazy(ptr, int(count.value))
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
class AsyncSymbolDatabase:
|
|
778
|
+
"""Async symbol database using worker thread architecture."""
|
|
779
|
+
|
|
780
|
+
def __init__(self):
|
|
781
|
+
# Worker thread is initialized automatically when library loads
|
|
782
|
+
self._pending_requests = {} # request_id -> callback
|
|
783
|
+
self._next_callback_id = 1
|
|
784
|
+
|
|
785
|
+
def is_async_available(self) -> bool:
|
|
786
|
+
"""Check if async API is available."""
|
|
787
|
+
return True
|
|
788
|
+
|
|
789
|
+
def poll_results(self, timeout_ms: int = 0) -> list[AsyncResult]:
|
|
790
|
+
"""Poll for completed results from worker thread.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
timeout_ms: Timeout in milliseconds (0 = non-blocking)
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
List of completed AsyncResult objects
|
|
797
|
+
"""
|
|
798
|
+
count = ctypes.c_uint32(0)
|
|
799
|
+
results_ptr = _lib.poll_results(ctypes.byref(count))
|
|
800
|
+
|
|
801
|
+
if not results_ptr or count.value == 0:
|
|
802
|
+
return []
|
|
803
|
+
|
|
804
|
+
try:
|
|
805
|
+
return _process_results(results_ptr, count.value)
|
|
806
|
+
finally:
|
|
807
|
+
_lib.result_array_free(results_ptr)
|
|
808
|
+
|
|
809
|
+
# Async API methods
|
|
810
|
+
def async_scan_file(self, path: Path_Like, language: Language) -> int:
|
|
811
|
+
"""Asynchronously scan a file. Returns request ID."""
|
|
812
|
+
return _lib.enqueue_scan_file(os.fsencode(path), int(language))
|
|
813
|
+
|
|
814
|
+
def async_remove_file(self, path: Path_Like) -> int:
|
|
815
|
+
"""Asynchronously remove a file. Returns request ID."""
|
|
816
|
+
return _lib.enqueue_remove_file(os.fsencode(path))
|
|
817
|
+
|
|
818
|
+
def async_update_file(self, path: Path_Like, language: Language) -> int:
|
|
819
|
+
"""Asynchronously update a file. Returns request ID."""
|
|
820
|
+
return _lib.enqueue_update_file(os.fsencode(path), int(language))
|
|
821
|
+
|
|
822
|
+
def async_find_definitions(self, symbol_name: str) -> int:
|
|
823
|
+
"""Asynchronously find symbol definitions. Returns request ID."""
|
|
824
|
+
return _lib.enqueue_find_definitions(symbol_name.encode("utf-8"))
|
|
825
|
+
|
|
826
|
+
def async_find_references(self, symbol_name: str) -> int:
|
|
827
|
+
"""Asynchronously find symbol references. Returns request ID."""
|
|
828
|
+
return _lib.enqueue_find_references(symbol_name.encode("utf-8"))
|
|
829
|
+
|
|
830
|
+
def async_get_symbol(self, path: Path_Like, line: int, column: int) -> int:
|
|
831
|
+
"""Asynchronously get symbol at position. Returns request ID."""
|
|
832
|
+
return _lib.enqueue_get_symbol(os.fsencode(path), ctypes.c_uint32(line), ctypes.c_uint32(column))
|
|
833
|
+
|
|
834
|
+
def async_get_all_symbols(self) -> int:
|
|
835
|
+
"""Asynchronously get all symbols. Returns request ID."""
|
|
836
|
+
return _lib.enqueue_get_all_symbols()
|
|
837
|
+
|
|
838
|
+
def async_get_file_symbols(self, path: Path_Like) -> int:
|
|
839
|
+
"""Asynchronously get symbols from a file. Returns request ID."""
|
|
840
|
+
return _lib.enqueue_get_file_symbols(os.fsencode(path))
|
|
841
|
+
|
|
842
|
+
def async_find_symbols_in_text(self, text: str, *, classes: bool = True, functions: bool = True, other: bool = True) -> int:
|
|
843
|
+
"""Asynchronously find symbols in text. Returns request ID."""
|
|
844
|
+
# Build filter mask
|
|
845
|
+
filter_mask = SymbolFilter.NONE
|
|
846
|
+
if classes:
|
|
847
|
+
filter_mask |= SymbolFilter.CLASSES
|
|
848
|
+
if functions:
|
|
849
|
+
filter_mask |= SymbolFilter.FUNCTIONS
|
|
850
|
+
if other:
|
|
851
|
+
filter_mask |= SymbolFilter.OTHER
|
|
852
|
+
|
|
853
|
+
return _lib.enqueue_find_symbols_in_text(text.encode("utf-8"), ctypes.c_uint32(filter_mask))
|
|
854
|
+
|
|
855
|
+
def async_export_json(self) -> int:
|
|
856
|
+
"""Asynchronously export symbols to JSON. Returns request ID."""
|
|
857
|
+
return _lib.enqueue_export_json()
|
|
858
|
+
|
|
859
|
+
# Convenience methods for blocking operations with polling
|
|
860
|
+
|
|
861
|
+
def _wait_for_result(self, request_id: int, timeout_ms: int):
|
|
862
|
+
"""Wait for a specific request to complete."""
|
|
863
|
+
import time
|
|
864
|
+
start_time = time.time()
|
|
865
|
+
|
|
866
|
+
while True:
|
|
867
|
+
results = self.poll_results()
|
|
868
|
+
for result in results:
|
|
869
|
+
if result.request_id == request_id:
|
|
870
|
+
if result.success:
|
|
871
|
+
return result.data
|
|
872
|
+
else:
|
|
873
|
+
raise RuntimeError(f"Request {request_id} failed: {result.error_message}")
|
|
874
|
+
|
|
875
|
+
# Check timeout
|
|
876
|
+
elapsed_ms = (time.time() - start_time) * 1000
|
|
877
|
+
if elapsed_ms >= timeout_ms:
|
|
878
|
+
raise TimeoutError(f"Request {request_id} timed out after {timeout_ms}ms")
|
|
879
|
+
|
|
880
|
+
# Small sleep to avoid busy waiting
|
|
881
|
+
time.sleep(0.001) # 1ms
|
|
882
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
code_intelligence-2.0.0.dist-info/METADATA,sha256=EDXSspHEEoYpLkTJ52CazS6p4HaznuARbkNMI_6UVIs,100
|
|
2
|
+
code_intelligence-2.0.0.dist-info/RECORD,,
|
|
3
|
+
code_intelligence-2.0.0.dist-info/WHEEL,sha256=aD7J6E7Slys9i0J65v6Ehfi0DHYa5rImRhUDp62Iwh0,98
|
|
4
|
+
code_intelligence/__init__.py,sha256=EOoNrQHv474BaMTfhwgUrwgeE781tD0YmMip-_0WxX4,491
|
|
5
|
+
code_intelligence/_code_intelligence.dll,sha256=AGjvz9T_rvRZqKiCeY_ICqWgGfBY-mZ7Gposte5fXLA,6655488
|
|
6
|
+
code_intelligence/symdb.py,sha256=zjI9NgTD-nCMoEgFPeZpOVa8rC_Vxh2GTgY8yy4Jskk,32086
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
code_intelligence-1.2.0.dist-info/METADATA,sha256=hKTZsfBd4PDEY2n4goorqCM_HwqJ8figLIksMS0xueE,100
|
|
2
|
-
code_intelligence-1.2.0.dist-info/RECORD,,
|
|
3
|
-
code_intelligence-1.2.0.dist-info/WHEEL,sha256=aD7J6E7Slys9i0J65v6Ehfi0DHYa5rImRhUDp62Iwh0,98
|
|
4
|
-
code_intelligence/__init__.py,sha256=gruQfSmjho6PUbWU7ZW7OZP2KZ1fsvx5Z-9vAwfnM1g,475
|
|
5
|
-
code_intelligence/_code_intelligence.dll,sha256=LNP8bT3rTDsLKrG-1OHbTIdJN7-VNd3MRi0PI2HzHIQ,6474240
|
|
6
|
-
code_intelligence/symdb.py,sha256=r8fkV4YeKKM4Tajr82L389g7Mv4ftMt_K_I7y8LxEZs,15850
|
|
File without changes
|