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.
@@ -3,14 +3,13 @@ from .symdb import (
3
3
  SymbolType,
4
4
  Symbol,
5
5
  Location,
6
- ImmideateSymbolDatabase,
6
+ ImmediateSymbolDatabase,
7
7
  LazySymbolDatabase,
8
+ AsyncSymbolDatabase,
9
+ AsyncResult,
8
10
  )
9
11
 
10
- # Backward compatibility alias
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
- "ImmideateSymbolDatabase",
20
+ "ImmediateSymbolDatabase",
22
21
  "LazySymbolDatabase",
22
+ "AsyncSymbolDatabase",
23
+ "AsyncResult",
23
24
  "SymbolDatabase",
24
25
  ]
Binary file
@@ -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
- CONSTANT = 8
39
- PROPERTY = 9
40
- INTERFACE = 10
41
- STRUCT = 11
42
- ENUM = 12
43
- NAMESPACE = 13
44
- PACKAGE = 14
45
- FIELD = 15
46
- PARAMETER = 16
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.c_int),
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.c_int]
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.c_int]
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.c_int]
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 not self._handle:
340
- raise RuntimeError("Failed to create symbol database")
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
- if self._handle:
358
- _lib.symbol_db_clear(self._handle, ctypes.c_int(1 if reuse_memory else 0))
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
- class ImmideateSymbolDatabase(_BaseSymbolDatabase):
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
+
@@ -1,4 +1,4 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code_intelligence
3
- Version: 1.2.0
3
+ Version: 2.0.0
4
4
  Summary: Code intelligence library
@@ -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