code-intelligence 4.0.1__py3-none-win_amd64.whl → 4.2.1__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.
@@ -13,7 +13,7 @@ from .goober import (
13
13
  # SHAME - Bump when fixing things too embarrassing to admit.
14
14
  # PROUD.DEFAULT.SHAME
15
15
  # \ | /
16
- __version__ = "4.0.1"
16
+ __version__ = "4.2.1"
17
17
  __version_info__ = tuple(int(i) for i in __version__.split('.'))
18
18
 
19
19
  __all__ = [
@@ -1,8 +1,8 @@
1
- import ctypes as C
2
1
  import os
3
2
  import sys
4
3
  import time
5
4
  import platform
5
+ import ctypes as C
6
6
  from pathlib import Path
7
7
  from dataclasses import dataclass, fields
8
8
  from enum import IntEnum, IntFlag
@@ -57,6 +57,11 @@ class Language(IntEnum):
57
57
  UNKNOWN = 0
58
58
  PYTHON = 1
59
59
  JAVASCRIPT = 2
60
+ GOLANG = 3
61
+ PHP = 4
62
+ RUST = 5
63
+ CPP = 6
64
+
60
65
 
61
66
  @dataclass
62
67
  @positional_repr
@@ -68,41 +73,39 @@ class Definition_Link:
68
73
  column_0_based : int = 0
69
74
 
70
75
 
76
+ # Using ["..."] syntax to create a copy independant of
77
+ # whether any other library sets the signature for those functions.
78
+ Py_IncRef = C.pythonapi["Py_IncRef"]
79
+ Py_IncRef.argtypes = [C.py_object]
80
+ Py_IncRef.restype = None
81
+
82
+ Py_DecRef = C.pythonapi["Py_DecRef"]
83
+ Py_DecRef.argtypes = [C.py_object]
84
+ Py_DecRef.restype = None
85
+
86
+
71
87
  # IMPORTANT: KEEP THIS ALIVE FOR GO THREAD WORKER
72
88
  C_NO_CALLBACK = SCAN_CALLBACK(0)
73
- _KEEPALIVE = dict[int, tuple]()
74
89
 
75
90
 
76
91
  def _wrap_data_ptr(tup: tuple):
77
- ptr = id(tup)
78
- _KEEPALIVE[ptr] = tup
79
- return ptr
92
+ # MANUALLY INCREMENT THE REFERENCE COUNT
93
+ # We are now responsible for its lifetime
94
+ C.pythonapi.Py_IncRef(C.py_object(tup))
80
95
 
96
+ return id(tup)
81
97
 
82
- def _unwrap_data_ptr(ptr) -> tuple:
83
- tup = _KEEPALIVE.pop(ptr, None)
84
- assert tup is not None, "keepalive missing for callback"
85
- return tup
86
98
 
99
+ def _unwrap_data_ptr(data_ptr) -> tuple:
100
+ assert data_ptr, "Received a NULL pointer"
101
+
102
+ tup = C.cast(data_ptr, C.py_object).value
87
103
 
88
- # def _wrap_data_ptr(tup: tuple):
89
- # # MANUALLY INCREMENT THE REFERENCE COUNT
90
- # # We are now responsible for its lifetime
91
- # C.pythonapi.Py_IncRef(C.py_object(tup))
92
- #
93
- # return id(tup) # C.cast(obj, C.c_void_p)
94
- #
95
- #
96
- # def _unwrap_data_ptr(data_ptr) -> tuple:
97
- # assert data_ptr != 0, "we got NULL"
98
- #
99
- # tup = C.cast(data_ptr, C.py_object).value
100
- #
101
- # # MANUALLY DECREMENT THE REFERENCE COUNT.
102
- # # We are fulfilling our promise
103
- # C.pythonapi.Py_DecRef(C.py_object(tup))
104
- #
105
- # return tup
104
+ # MANUALLY DECREMENT THE REFERENCE COUNT.
105
+ # We are fulfilling our promise
106
+ C.pythonapi.Py_DecRef(C.py_object(tup))
107
+
108
+ return tup
106
109
 
107
110
 
108
111
  def scan_file(file, language: Language, callback=None, data=None) -> bool:
Binary file
@@ -1,4 +1,4 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code_intelligence
3
- Version: 4.0.1
3
+ Version: 4.2.1
4
4
  Summary: Code intelligence library
@@ -0,0 +1,6 @@
1
+ code_intelligence-4.2.1.dist-info/METADATA,sha256=VzjyuaBp11QaHBuUssUUPd-2mTQz-05N6Suvj4m6tJM,100
2
+ code_intelligence-4.2.1.dist-info/RECORD,,
3
+ code_intelligence-4.2.1.dist-info/WHEEL,sha256=aD7J6E7Slys9i0J65v6Ehfi0DHYa5rImRhUDp62Iwh0,98
4
+ code_intelligence/__init__.py,sha256=v0Jvs3-Kcyoqc9wW8oOI9tAR7QK0QZecdTCZ2kZC_-8,587
5
+ code_intelligence/goober.py,sha256=b662i4Lqog2aH1D7caP3tmdklpO7JUhi-6GbqNHUzLk,6825
6
+ code_intelligence/libcode_intelligence.so,sha256=UYQOzn3rIxg61n4EE0tfPfN_tW3nP_inOkneS8AXQhs,8276992
@@ -1,886 +0,0 @@
1
- import os
2
- import ctypes
3
- import platform
4
- from enum import IntEnum, IntFlag
5
- from pathlib import Path
6
- from dataclasses import dataclass, field
7
-
8
-
9
- class Language(IntEnum):
10
- # UNKNOWN = 0
11
- PYTHON = 1
12
- JAVASCRIPT = 2 # Not implemented yet - enum exists but no parser
13
- TYPESCRIPT = 3 # Not implemented yet - enum exists but no parser
14
- CPP = 4
15
- C = 5
16
- JAVA = 6 # Not implemented yet - enum exists but no parser
17
- RUST = 7
18
- GO = 8
19
- PHP = 9
20
-
21
-
22
- class SymbolType(IntEnum):
23
- UNKNOWN = 0
24
- CLASS = 1
25
- FUNCTION = 2
26
- METHOD = 3
27
- VARIABLE = 4
28
- MODULE = 5
29
- IMPORT = 6
30
- FROM_IMPORT = 7
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
-
41
- Path_Like = str | Path
42
-
43
- @dataclass(slots=True)
44
- class Location:
45
- """Source code span."""
46
- path: str = ""
47
- line: int = 0
48
- column: int = 0
49
- end_line: int = 0
50
- end_column: int = 0
51
-
52
- def __repr__(self) -> str:
53
- return f"Location({self.path}:{self.line}:{self.column})"
54
-
55
- @dataclass(slots=True)
56
- class Symbol:
57
- """Language-agnostic symbol record."""
58
- name: str = ""
59
- symbol_type: SymbolType = SymbolType.UNKNOWN
60
- definition: Location = field(default_factory=Location)
61
- parent: str | None = None
62
- signature: str | None = None
63
- documentation: str | None = None
64
-
65
- def __repr__(self) -> str:
66
- return f"Symbol({self.name}, {self.symbol_type.name}, {self.definition})"
67
-
68
- # Convenience properties for backward compatibility with TASK.md API
69
- @property
70
- def filename(self) -> str:
71
- return self.definition.path
72
-
73
- @property
74
- def line(self) -> int:
75
- return self.definition.line
76
-
77
- @property
78
- def column(self) -> int:
79
- return self.definition.column
80
-
81
- # Keep location as alias for backward compatibility
82
- @property
83
- def location(self) -> "Location":
84
- return self.definition
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
-
96
- # --- Private ctypes Implementation ---
97
-
98
- class _Location(ctypes.Structure):
99
- _fields_ = [
100
- ("path", ctypes.c_char_p),
101
- ("line", ctypes.c_uint32),
102
- ("column", ctypes.c_uint32),
103
- ("end_line", ctypes.c_uint32),
104
- ("end_column", ctypes.c_uint32),
105
- ]
106
-
107
- class _Symbol(ctypes.Structure):
108
- _fields_ = [
109
- ("name", ctypes.c_char_p),
110
- ("symbol_type", ctypes.c_uint8),
111
- ("definition", _Location),
112
- ("parent", ctypes.c_char_p),
113
- ("signature", ctypes.c_char_p),
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),
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
-
195
-
196
- CURRENT_OS = platform.system().lower()
197
-
198
- if CURRENT_OS == "darwin":
199
- LIB_EXT = ".dylib"
200
- elif CURRENT_OS == "windows":
201
- LIB_EXT = ".dll"
202
- else:
203
- LIB_EXT = ".so"
204
-
205
- def _load_library(name: str):
206
- # Try module directory first (where nob.py now puts the library)
207
- lib_path = Path(__file__).parent / f"{name}{LIB_EXT}"
208
- if not lib_path.exists():
209
- # Fallback to build directory for backward compatibility
210
- lib_path = Path(__file__).parent.parent / "build" / f"{name}{LIB_EXT}"
211
- if not lib_path.exists():
212
- # Final fallback to project root
213
- lib_path = Path(__file__).parent.parent / f"{name}{LIB_EXT}"
214
- if not lib_path.exists():
215
- raise ImportError(
216
- f"Cannot find compiled library at {lib_path}. Please build it first using 'python nob.py lib'."
217
- )
218
-
219
- lib = ctypes.CDLL(os.fsdecode(lib_path))
220
- return lib
221
-
222
- _lib = _load_library("_code_intelligence")
223
-
224
- # C function signatures (match code_intelligence.c)
225
- _lib.symbol_db_create.argtypes = []
226
- _lib.symbol_db_create.restype = ctypes.c_void_p
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, ctypes.c_uint32, ctypes.c_uint32]
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
-
271
- _lib.symbol_db_destroy.argtypes = [ctypes.c_void_p]
272
- _lib.symbol_db_destroy.restype = None
273
-
274
- _lib.symbol_db_set_project_root.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
275
- _lib.symbol_db_set_project_root.restype = None
276
-
277
- _lib.symbol_db_scan_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint8]
278
- _lib.symbol_db_scan_file.restype = ctypes.c_bool
279
-
280
- # Batch scan
281
- _lib.symbol_db_scan_files.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p), ctypes.c_uint32, ctypes.c_uint8]
282
- _lib.symbol_db_scan_files.restype = ctypes.c_bool
283
-
284
- _lib.symbol_db_remove_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
285
- _lib.symbol_db_remove_file.restype = ctypes.c_bool
286
-
287
- # Update file (re-scan and replace)
288
- _lib.symbol_db_update_file.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint8]
289
- _lib.symbol_db_update_file.restype = ctypes.c_bool
290
-
291
- # Arrays with out_count
292
- _lib.symbol_db_find_definitions.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32)]
293
- _lib.symbol_db_find_definitions.restype = ctypes.POINTER(_Symbol)
294
-
295
- _lib.symbol_db_find_references.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32)]
296
- _lib.symbol_db_find_references.restype = ctypes.POINTER(_Symbol)
297
-
298
- _lib.symbol_db_get_all_symbols.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32)]
299
- _lib.symbol_db_get_all_symbols.restype = ctypes.POINTER(_Symbol)
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
-
304
- # bool return + out _Symbol
305
- _lib.symbol_db_get_symbol.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.POINTER(_Symbol)]
306
- _lib.symbol_db_get_symbol.restype = ctypes.c_bool
307
-
308
- # Free for arrays (void*)
309
- _lib.symbol_array_free.argtypes = [ctypes.c_void_p]
310
- _lib.symbol_array_free.restype = None
311
-
312
- _lib.ci_flush_coverage.argtypes = []
313
- _lib.ci_flush_coverage.restype = None
314
-
315
- _lib.symbol_db_clear.argtypes = [ctypes.c_void_p, ctypes.c_int]
316
- _lib.symbol_db_clear.restype = None
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
-
333
- def _flush_coverage():
334
- try:
335
- _lib.ci_flush_coverage()
336
- except Exception:
337
- pass
338
-
339
- # Helper conversions
340
-
341
- class CStringView:
342
- """A view into a C string that delays decoding until needed."""
343
- __slots__ = ('_ptr', '_decoded')
344
-
345
- def __init__(self, c_char_p):
346
- self._ptr = c_char_p
347
- self._decoded = None
348
-
349
- def __str__(self):
350
- if self._decoded is None:
351
- if self._ptr:
352
- self._decoded = self._ptr.decode("utf-8", errors="replace")
353
- else:
354
- self._decoded = ""
355
- return self._decoded
356
-
357
- def __repr__(self):
358
- return f"CStringView({str(self)!r})"
359
-
360
- def __bool__(self):
361
- return bool(self._ptr)
362
-
363
- def __eq__(self, other):
364
- if isinstance(other, CStringView):
365
- return str(self) == str(other)
366
- elif isinstance(other, str):
367
- return str(self) == other
368
- return False
369
-
370
-
371
- def _to_py_symbol_lazy(c_symbol: _Symbol) -> Symbol:
372
- """Convert C symbol to Python symbol with lazy string decoding."""
373
- definition = Location(
374
- path=str(CStringView(c_symbol.definition.path)), # Decode immediately for file paths (needed for path operations)
375
- line=c_symbol.definition.line,
376
- column=c_symbol.definition.column,
377
- end_line=c_symbol.definition.end_line,
378
- end_column=c_symbol.definition.end_column,
379
- )
380
- return Symbol(
381
- name=str(CStringView(c_symbol.name)), # Most accessed field, decode immediately
382
- symbol_type=SymbolType(c_symbol.symbol_type),
383
- definition=definition,
384
- parent=str(CStringView(c_symbol.parent)) if c_symbol.parent else None,
385
- signature=CStringView(c_symbol.signature) if c_symbol.signature else None, # Keep as view
386
- documentation=CStringView(c_symbol.documentation) if c_symbol.documentation else None, # Keep as view
387
- )
388
-
389
-
390
- def _to_py_symbol(c_symbol: _Symbol) -> Symbol:
391
- definition = Location(
392
- path=c_symbol.definition.path.decode("utf-8", errors="replace") if c_symbol.definition.path else "",
393
- line=c_symbol.definition.line,
394
- column=c_symbol.definition.column,
395
- end_line=c_symbol.definition.end_line,
396
- end_column=c_symbol.definition.end_column,
397
- )
398
- return Symbol(
399
- name=c_symbol.name.decode("utf-8", errors="replace") if c_symbol.name else "",
400
- symbol_type=SymbolType(c_symbol.symbol_type),
401
- definition=definition,
402
- parent=c_symbol.parent.decode("utf-8", errors="replace") if c_symbol.parent else None,
403
- signature=c_symbol.signature.decode("utf-8", errors="replace") if c_symbol.signature else None,
404
- documentation=c_symbol.documentation.decode("utf-8", errors="replace") if c_symbol.documentation else None,
405
- )
406
-
407
-
408
- class SymbolArrayView:
409
- """A view into a C symbol array that manages the C memory and provides lazy access."""
410
-
411
- def __init__(self, ptr: ctypes.POINTER(_Symbol), count: int):
412
- self._ptr = ptr
413
- self._count = count
414
- self._symbols = None # Lazy converted symbols
415
-
416
- def __len__(self):
417
- return self._count
418
-
419
- def __getitem__(self, index):
420
- if isinstance(index, slice):
421
- # Handle slice objects
422
- start, stop, step = index.indices(self._count)
423
- return [self[i] for i in range(start, stop, step)]
424
-
425
- if index < 0:
426
- index = self._count + index
427
- if index < 0 or index >= self._count:
428
- raise IndexError("Symbol array index out of range")
429
-
430
- # Lazy conversion - convert only accessed symbols
431
- if self._symbols is None:
432
- self._symbols = [None] * self._count
433
-
434
- if self._symbols[index] is None:
435
- self._symbols[index] = _to_py_symbol_lazy(self._ptr[index])
436
-
437
- return self._symbols[index]
438
-
439
- def __iter__(self):
440
- for i in range(self._count):
441
- yield self[i]
442
-
443
- def __del__(self):
444
- # Free the C array when Python object is garbage collected
445
- if hasattr(self, '_ptr') and self._ptr:
446
- _lib.symbol_array_free(self._ptr)
447
- self._ptr = None
448
-
449
-
450
- def _convert_symbol_array(ptr: ctypes.POINTER(_Symbol), count: int) -> list[Symbol]:
451
- if not ptr or count <= 0:
452
- return []
453
- try:
454
- return [_to_py_symbol(ptr[i]) for i in range(count)]
455
- finally:
456
- # Free the shallow-copy array buffer
457
- _lib.symbol_array_free(ptr)
458
-
459
-
460
- def _convert_symbol_array_lazy(ptr: ctypes.POINTER(_Symbol), count: int) -> SymbolArrayView:
461
- """Convert C symbol array to a lazy Python view without immediate copying."""
462
- if not ptr or count <= 0:
463
- if ptr:
464
- _lib.symbol_array_free(ptr) # Free empty array
465
- return SymbolArrayView(None, 0)
466
-
467
- return SymbolArrayView(ptr, count)
468
-
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
-
540
-
541
- # --- Backend Implementations ---
542
-
543
- class _BaseSymbolDatabase:
544
- """Base class for code intelligence operations (not for direct use)."""
545
- def __init__(self):
546
- self._handle = _lib.symbol_db_create()
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
549
-
550
- def __del__(self):
551
- if hasattr(self, "_handle") and self._handle:
552
- _lib.symbol_db_destroy(self._handle)
553
- self._handle = None
554
-
555
- def clear(self, *, reuse_memory: bool = True):
556
- """Reset the database state while keeping the same Python object.
557
-
558
- Frees the current native database state and reinitializes it. Any
559
- previously scanned files, symbols, and project root are discarded.
560
-
561
- Args:
562
- reuse_memory: If True (default), keep allocated arena memory for reuse.
563
- If False, free all memory and start fresh.
564
- """
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
602
-
603
- def set_project_root(self, path: Path_Like):
604
- _lib.symbol_db_set_project_root(self._handle, os.fsencode(path))
605
-
606
- def scan_file(self, path: Path_Like, language: Language) -> bool:
607
- return _lib.symbol_db_scan_file(
608
- self._handle,
609
- os.fsencode(path),
610
- int(language),
611
- )
612
-
613
- def scan_files(self, paths: list[Path_Like], language: Language) -> bool:
614
- arr = (ctypes.c_char_p * len(paths))()
615
- for i, p in enumerate(paths):
616
- arr[i] = os.fsencode(p)
617
- return _lib.symbol_db_scan_files(
618
- self._handle,
619
- arr,
620
- ctypes.c_uint32(len(paths)),
621
- int(language),
622
- )
623
-
624
- def init_project(self, files_by_language: dict[Language, list[Path_Like]]) -> bool:
625
- all_ok = True
626
- for lang, files in files_by_language.items():
627
- if not files:
628
- continue
629
- ok = self.scan_files(files, lang)
630
- if not ok:
631
- all_ok = False
632
- return all_ok
633
-
634
- def remove_file(self, path: Path_Like) -> bool:
635
- return _lib.symbol_db_remove_file(
636
- self._handle,
637
- os.fsencode(path),
638
- )
639
-
640
- def update_file(self, path: Path_Like, language: Language) -> bool:
641
- return _lib.symbol_db_update_file(
642
- self._handle,
643
- os.fsencode(path),
644
- int(language),
645
- )
646
-
647
- def get_symbol(self, path: Path_Like, line: int, column: int) -> Symbol | None:
648
- out = _Symbol()
649
- ok = _lib.symbol_db_get_symbol(
650
- self._handle,
651
- os.fsencode(path),
652
- ctypes.c_uint32(line),
653
- ctypes.c_uint32(column),
654
- ctypes.byref(out),
655
- )
656
- return _to_py_symbol(out) if ok else None
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
-
700
-
701
- class ImmediateSymbolDatabase(_BaseSymbolDatabase):
702
- """Symbol database using copying (eager) backend."""
703
- def find_definitions(self, symbol_name: str) -> list[Symbol]:
704
- count = ctypes.c_uint32(0)
705
- ptr = _lib.symbol_db_find_definitions(
706
- self._handle,
707
- symbol_name.encode("utf-8"),
708
- ctypes.byref(count),
709
- )
710
- return _convert_symbol_array(ptr, int(count.value))
711
-
712
- def find_symbols(self, symbol_name: str) -> list[Symbol]:
713
- return self.find_definitions(symbol_name)
714
-
715
- def find_references(self, path: Path_Like, line: int, column: int) -> list[Symbol]:
716
- count = ctypes.c_uint32(0)
717
- ptr = _lib.symbol_db_find_references(
718
- self._handle,
719
- os.fsencode(path),
720
- ctypes.c_uint32(line),
721
- ctypes.c_uint32(column),
722
- ctypes.byref(count),
723
- )
724
- return _convert_symbol_array(ptr, int(count.value))
725
-
726
- def get_all_symbols(self) -> list[Symbol]:
727
- count = ctypes.c_uint32(0)
728
- ptr = _lib.symbol_db_get_all_symbols(self._handle, ctypes.byref(count))
729
- return _convert_symbol_array(ptr, int(count.value))
730
-
731
- def get_file_symbols(self, path: Path_Like) -> list[Symbol]:
732
- count = ctypes.c_uint32(0)
733
- ptr = _lib.symbol_db_get_file_symbols(
734
- self._handle,
735
- os.fsencode(path),
736
- ctypes.byref(count),
737
- )
738
- return _convert_symbol_array(ptr, int(count.value))
739
-
740
-
741
- class LazySymbolDatabase(_BaseSymbolDatabase):
742
- """Symbol database using lazy backend."""
743
- def find_definitions(self, symbol_name: str) -> SymbolArrayView:
744
- count = ctypes.c_uint32(0)
745
- ptr = _lib.symbol_db_find_definitions(
746
- self._handle,
747
- symbol_name.encode("utf-8"),
748
- ctypes.byref(count),
749
- )
750
- return _convert_symbol_array_lazy(ptr, int(count.value))
751
-
752
- def find_symbols(self, symbol_name: str) -> SymbolArrayView:
753
- return self.find_definitions(symbol_name)
754
-
755
- def find_references(self, path: Path_Like, line: int, column: int) -> SymbolArrayView:
756
- count = ctypes.c_uint32(0)
757
- ptr = _lib.symbol_db_find_references(
758
- self._handle,
759
- os.fsencode(path),
760
- ctypes.c_uint32(line),
761
- ctypes.c_uint32(column),
762
- ctypes.byref(count),
763
- )
764
- return _convert_symbol_array_lazy(ptr, int(count.value))
765
-
766
- def get_all_symbols(self) -> SymbolArrayView:
767
- count = ctypes.c_uint32(0)
768
- ptr = _lib.symbol_db_get_all_symbols(self._handle, ctypes.byref(count))
769
- return _convert_symbol_array_lazy(ptr, int(count.value))
770
-
771
- def get_file_symbols(self, path: Path_Like) -> SymbolArrayView:
772
- count = ctypes.c_uint32(0)
773
- ptr = _lib.symbol_db_get_file_symbols(
774
- self._handle,
775
- os.fsencode(path),
776
- ctypes.byref(count),
777
- )
778
- return _convert_symbol_array_lazy(ptr, int(count.value))
779
-
780
-
781
- class AsyncSymbolDatabase:
782
- """Async symbol database using worker thread architecture."""
783
-
784
- def __init__(self):
785
- # Worker thread is initialized automatically when library loads
786
- self._pending_requests = {} # request_id -> callback
787
- self._next_callback_id = 1
788
-
789
- def is_async_available(self) -> bool:
790
- """Check if async API is available."""
791
- return True
792
-
793
- def poll_results(self, timeout_ms: int = 0) -> list[AsyncResult]:
794
- """Poll for completed results from worker thread.
795
-
796
- Args:
797
- timeout_ms: Timeout in milliseconds (0 = non-blocking)
798
-
799
- Returns:
800
- List of completed AsyncResult objects
801
- """
802
- count = ctypes.c_uint32(0)
803
- results_ptr = _lib.poll_results(ctypes.byref(count))
804
-
805
- if not results_ptr or count.value == 0:
806
- return []
807
-
808
- try:
809
- return _process_results(results_ptr, count.value)
810
- finally:
811
- _lib.result_array_free(results_ptr)
812
-
813
- # Async API methods
814
- def async_scan_file(self, path: Path_Like, language: Language) -> int:
815
- """Asynchronously scan a file. Returns request ID."""
816
- return _lib.enqueue_scan_file(os.fsencode(path), int(language))
817
-
818
- def async_remove_file(self, path: Path_Like) -> int:
819
- """Asynchronously remove a file. Returns request ID."""
820
- return _lib.enqueue_remove_file(os.fsencode(path))
821
-
822
- def async_update_file(self, path: Path_Like, language: Language) -> int:
823
- """Asynchronously update a file. Returns request ID."""
824
- return _lib.enqueue_update_file(os.fsencode(path), int(language))
825
-
826
- def async_find_definitions(self, symbol_name: str) -> int:
827
- """Asynchronously find symbol definitions. Returns request ID."""
828
- return _lib.enqueue_find_definitions(symbol_name.encode("utf-8"))
829
-
830
- def async_find_references(self, filename: Path_Like, line: int, column: int) -> int:
831
- """Asynchronously find references for a symbol at filename:line:column. Returns request ID."""
832
- return _lib.enqueue_find_references(os.fsencode(filename), ctypes.c_uint32(line), ctypes.c_uint32(column))
833
-
834
- def async_get_symbol(self, path: Path_Like, line: int, column: int) -> int:
835
- """Asynchronously get symbol at position. Returns request ID."""
836
- return _lib.enqueue_get_symbol(os.fsencode(path), ctypes.c_uint32(line), ctypes.c_uint32(column))
837
-
838
- def async_get_all_symbols(self) -> int:
839
- """Asynchronously get all symbols. Returns request ID."""
840
- return _lib.enqueue_get_all_symbols()
841
-
842
- def async_get_file_symbols(self, path: Path_Like) -> int:
843
- """Asynchronously get symbols from a file. Returns request ID."""
844
- return _lib.enqueue_get_file_symbols(os.fsencode(path))
845
-
846
- def async_find_symbols_in_text(self, text: str, *, classes: bool = True, functions: bool = True, other: bool = True) -> int:
847
- """Asynchronously find symbols in text. Returns request ID."""
848
- # Build filter mask
849
- filter_mask = SymbolFilter.NONE
850
- if classes:
851
- filter_mask |= SymbolFilter.CLASSES
852
- if functions:
853
- filter_mask |= SymbolFilter.FUNCTIONS
854
- if other:
855
- filter_mask |= SymbolFilter.OTHER
856
-
857
- return _lib.enqueue_find_symbols_in_text(text.encode("utf-8"), ctypes.c_uint32(filter_mask))
858
-
859
- def async_export_json(self) -> int:
860
- """Asynchronously export symbols to JSON. Returns request ID."""
861
- return _lib.enqueue_export_json()
862
-
863
- # Convenience methods for blocking operations with polling
864
-
865
- def _wait_for_result(self, request_id: int, timeout_ms: int):
866
- """Wait for a specific request to complete."""
867
- import time
868
- start_time = time.time()
869
-
870
- while True:
871
- results = self.poll_results()
872
- for result in results:
873
- if result.request_id == request_id:
874
- if result.success:
875
- return result.data
876
- else:
877
- raise RuntimeError(f"Request {request_id} failed: {result.error_message}")
878
-
879
- # Check timeout
880
- elapsed_ms = (time.time() - start_time) * 1000
881
- if elapsed_ms >= timeout_ms:
882
- raise TimeoutError(f"Request {request_id} timed out after {timeout_ms}ms")
883
-
884
- # Small sleep to avoid busy waiting
885
- time.sleep(0.001) # 1ms
886
-
@@ -1,7 +0,0 @@
1
- code_intelligence-4.0.1.dist-info/METADATA,sha256=q7_4zFfnkkpLx68-sJ7UkNd4S9KqYXzfiaRGmROdGhg,100
2
- code_intelligence-4.0.1.dist-info/RECORD,,
3
- code_intelligence-4.0.1.dist-info/WHEEL,sha256=aD7J6E7Slys9i0J65v6Ehfi0DHYa5rImRhUDp62Iwh0,98
4
- code_intelligence/__init__.py,sha256=W57alh3hyEoygOeI-d1dvVisqC1kSMKojqbTXqvYaKw,587
5
- code_intelligence/goober.py,sha256=mTth4uxufIKlIn7PVDA0MOIaL7_TrfRRJho5AD9_n_M,6749
6
- code_intelligence/libcode_intelligence.so,sha256=So59f71LIl_khhNQ69mMT6PNpepDqsGv0ecTlYKB3SM,2549760
7
- code_intelligence/symdb.py,sha256=gRq3-xr2oYIzudgwDwMj3Zl5Pb0B8tDdnGKUB0WIV_o,32427