code-intelligence 1.3.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 +5 -4
- code_intelligence/_code_intelligence.dll +0 -0
- code_intelligence/symdb.py +429 -22
- {code_intelligence-1.3.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.3.0.dist-info/RECORD +0 -6
- {code_intelligence-1.3.0.dist-info → code_intelligence-2.0.0.dist-info}/WHEEL +0 -0
code_intelligence/__init__.py
CHANGED
|
@@ -5,12 +5,11 @@ from .symdb import (
|
|
|
5
5
|
Location,
|
|
6
6
|
ImmediateSymbolDatabase,
|
|
7
7
|
LazySymbolDatabase,
|
|
8
|
+
AsyncSymbolDatabase,
|
|
9
|
+
AsyncResult,
|
|
8
10
|
)
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
SymbolDatabase = ImmediateSymbolDatabase
|
|
12
|
-
|
|
13
|
-
__version__ = "1.3.0"
|
|
12
|
+
__version__ = "2.0.0"
|
|
14
13
|
__version_info__ = tuple(int(i) for i in __version__.split('.'))
|
|
15
14
|
|
|
16
15
|
__all__ = [
|
|
@@ -20,5 +19,7 @@ __all__ = [
|
|
|
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
|
|
|
@@ -10,7 +10,7 @@ class Language(IntEnum):
|
|
|
10
10
|
# UNKNOWN = 0
|
|
11
11
|
PYTHON = 1
|
|
12
12
|
JAVASCRIPT = 2 # Not implemented yet - enum exists but no parser
|
|
13
|
-
TYPESCRIPT = 3 # 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
16
|
JAVA = 6 # Not implemented yet - enum exists but no parser
|
|
@@ -29,6 +29,15 @@ class SymbolType(IntEnum):
|
|
|
29
29
|
IMPORT = 6
|
|
30
30
|
FROM_IMPORT = 7
|
|
31
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
|
|
40
|
+
|
|
32
41
|
Path_Like = str | Path
|
|
33
42
|
|
|
34
43
|
@dataclass(slots=True)
|
|
@@ -74,6 +83,16 @@ class Symbol:
|
|
|
74
83
|
def location(self) -> "Location":
|
|
75
84
|
return self.definition
|
|
76
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
|
+
|
|
77
96
|
# --- Private ctypes Implementation ---
|
|
78
97
|
|
|
79
98
|
class _Location(ctypes.Structure):
|
|
@@ -97,6 +116,82 @@ class _Symbol(ctypes.Structure):
|
|
|
97
116
|
("_target_module", ctypes.c_char_p),
|
|
98
117
|
]
|
|
99
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),
|
|
124
|
+
]
|
|
125
|
+
|
|
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
|
+
|
|
100
195
|
|
|
101
196
|
CURRENT_OS = platform.system().lower()
|
|
102
197
|
|
|
@@ -130,6 +225,49 @@ _lib = _load_library("_code_intelligence")
|
|
|
130
225
|
_lib.symbol_db_create.argtypes = []
|
|
131
226
|
_lib.symbol_db_create.restype = ctypes.c_void_p
|
|
132
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
|
+
|
|
133
271
|
_lib.symbol_db_destroy.argtypes = [ctypes.c_void_p]
|
|
134
272
|
_lib.symbol_db_destroy.restype = None
|
|
135
273
|
|
|
@@ -177,6 +315,21 @@ _lib.ci_flush_coverage.restype = None
|
|
|
177
315
|
_lib.symbol_db_clear.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
178
316
|
_lib.symbol_db_clear.restype = None
|
|
179
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
|
+
|
|
180
333
|
def _flush_coverage():
|
|
181
334
|
try:
|
|
182
335
|
_lib.ci_flush_coverage()
|
|
@@ -188,11 +341,11 @@ def _flush_coverage():
|
|
|
188
341
|
class CStringView:
|
|
189
342
|
"""A view into a C string that delays decoding until needed."""
|
|
190
343
|
__slots__ = ('_ptr', '_decoded')
|
|
191
|
-
|
|
344
|
+
|
|
192
345
|
def __init__(self, c_char_p):
|
|
193
346
|
self._ptr = c_char_p
|
|
194
347
|
self._decoded = None
|
|
195
|
-
|
|
348
|
+
|
|
196
349
|
def __str__(self):
|
|
197
350
|
if self._decoded is None:
|
|
198
351
|
if self._ptr:
|
|
@@ -200,13 +353,13 @@ class CStringView:
|
|
|
200
353
|
else:
|
|
201
354
|
self._decoded = ""
|
|
202
355
|
return self._decoded
|
|
203
|
-
|
|
356
|
+
|
|
204
357
|
def __repr__(self):
|
|
205
358
|
return f"CStringView({str(self)!r})"
|
|
206
|
-
|
|
359
|
+
|
|
207
360
|
def __bool__(self):
|
|
208
361
|
return bool(self._ptr)
|
|
209
|
-
|
|
362
|
+
|
|
210
363
|
def __eq__(self, other):
|
|
211
364
|
if isinstance(other, CStringView):
|
|
212
365
|
return str(self) == str(other)
|
|
@@ -254,39 +407,39 @@ def _to_py_symbol(c_symbol: _Symbol) -> Symbol:
|
|
|
254
407
|
|
|
255
408
|
class SymbolArrayView:
|
|
256
409
|
"""A view into a C symbol array that manages the C memory and provides lazy access."""
|
|
257
|
-
|
|
410
|
+
|
|
258
411
|
def __init__(self, ptr: ctypes.POINTER(_Symbol), count: int):
|
|
259
412
|
self._ptr = ptr
|
|
260
413
|
self._count = count
|
|
261
414
|
self._symbols = None # Lazy converted symbols
|
|
262
|
-
|
|
415
|
+
|
|
263
416
|
def __len__(self):
|
|
264
417
|
return self._count
|
|
265
|
-
|
|
418
|
+
|
|
266
419
|
def __getitem__(self, index):
|
|
267
420
|
if isinstance(index, slice):
|
|
268
421
|
# Handle slice objects
|
|
269
422
|
start, stop, step = index.indices(self._count)
|
|
270
423
|
return [self[i] for i in range(start, stop, step)]
|
|
271
|
-
|
|
424
|
+
|
|
272
425
|
if index < 0:
|
|
273
426
|
index = self._count + index
|
|
274
427
|
if index < 0 or index >= self._count:
|
|
275
428
|
raise IndexError("Symbol array index out of range")
|
|
276
|
-
|
|
429
|
+
|
|
277
430
|
# Lazy conversion - convert only accessed symbols
|
|
278
431
|
if self._symbols is None:
|
|
279
432
|
self._symbols = [None] * self._count
|
|
280
|
-
|
|
433
|
+
|
|
281
434
|
if self._symbols[index] is None:
|
|
282
435
|
self._symbols[index] = _to_py_symbol_lazy(self._ptr[index])
|
|
283
|
-
|
|
436
|
+
|
|
284
437
|
return self._symbols[index]
|
|
285
|
-
|
|
438
|
+
|
|
286
439
|
def __iter__(self):
|
|
287
440
|
for i in range(self._count):
|
|
288
441
|
yield self[i]
|
|
289
|
-
|
|
442
|
+
|
|
290
443
|
def __del__(self):
|
|
291
444
|
# Free the C array when Python object is garbage collected
|
|
292
445
|
if hasattr(self, '_ptr') and self._ptr:
|
|
@@ -310,10 +463,80 @@ def _convert_symbol_array_lazy(ptr: ctypes.POINTER(_Symbol), count: int) -> Symb
|
|
|
310
463
|
if ptr:
|
|
311
464
|
_lib.symbol_array_free(ptr) # Free empty array
|
|
312
465
|
return SymbolArrayView(None, 0)
|
|
313
|
-
|
|
466
|
+
|
|
314
467
|
return SymbolArrayView(ptr, count)
|
|
315
468
|
|
|
316
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
|
+
|
|
317
540
|
|
|
318
541
|
# --- Backend Implementations ---
|
|
319
542
|
|
|
@@ -321,8 +544,8 @@ class _BaseSymbolDatabase:
|
|
|
321
544
|
"""Base class for code intelligence operations (not for direct use)."""
|
|
322
545
|
def __init__(self):
|
|
323
546
|
self._handle = _lib.symbol_db_create()
|
|
324
|
-
if
|
|
325
|
-
|
|
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
|
|
326
549
|
|
|
327
550
|
def __del__(self):
|
|
328
551
|
if hasattr(self, "_handle") and self._handle:
|
|
@@ -334,13 +557,48 @@ class _BaseSymbolDatabase:
|
|
|
334
557
|
|
|
335
558
|
Frees the current native database state and reinitializes it. Any
|
|
336
559
|
previously scanned files, symbols, and project root are discarded.
|
|
337
|
-
|
|
560
|
+
|
|
338
561
|
Args:
|
|
339
562
|
reuse_memory: If True (default), keep allocated arena memory for reuse.
|
|
340
563
|
If False, free all memory and start fresh.
|
|
341
564
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
344
602
|
|
|
345
603
|
def set_project_root(self, path: Path_Like):
|
|
346
604
|
_lib.symbol_db_set_project_root(self._handle, os.fsencode(path))
|
|
@@ -397,6 +655,48 @@ class _BaseSymbolDatabase:
|
|
|
397
655
|
)
|
|
398
656
|
return _to_py_symbol(out) if ok else None
|
|
399
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).
|
|
687
|
+
|
|
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
|
+
|
|
400
700
|
|
|
401
701
|
class ImmediateSymbolDatabase(_BaseSymbolDatabase):
|
|
402
702
|
"""Symbol database using copying (eager) backend."""
|
|
@@ -473,3 +773,110 @@ class LazySymbolDatabase(_BaseSymbolDatabase):
|
|
|
473
773
|
)
|
|
474
774
|
return _convert_symbol_array_lazy(ptr, int(count.value))
|
|
475
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.3.0.dist-info/METADATA,sha256=___8D0t8oEl2VRhVidjmJX1B84UDJkFHPhHm5w8mews,100
|
|
2
|
-
code_intelligence-1.3.0.dist-info/RECORD,,
|
|
3
|
-
code_intelligence-1.3.0.dist-info/WHEEL,sha256=aD7J6E7Slys9i0J65v6Ehfi0DHYa5rImRhUDp62Iwh0,98
|
|
4
|
-
code_intelligence/__init__.py,sha256=2br2823E1Mi9gejTIr_lRhPtv0tZKFlyjVebV_tuxJg,475
|
|
5
|
-
code_intelligence/_code_intelligence.dll,sha256=2f_7kGXznmc3NBbSn7H6HvaA_Mp9sKK8xRAQ4XJ39Tw,6475264
|
|
6
|
-
code_intelligence/symdb.py,sha256=eIcyCsbXNBfpmHsEJmHtOSgtMW8GdrnIryYDSfafGH4,16529
|
|
File without changes
|