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.
- code_intelligence/__init__.py +1 -1
- code_intelligence/goober.py +30 -27
- code_intelligence/libcode_intelligence.so +0 -0
- {code_intelligence-4.0.1.dist-info → code_intelligence-4.2.1.dist-info}/METADATA +1 -1
- code_intelligence-4.2.1.dist-info/RECORD +6 -0
- code_intelligence/symdb.py +0 -886
- code_intelligence-4.0.1.dist-info/RECORD +0 -7
- {code_intelligence-4.0.1.dist-info → code_intelligence-4.2.1.dist-info}/WHEEL +0 -0
code_intelligence/__init__.py
CHANGED
code_intelligence/goober.py
CHANGED
|
@@ -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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
#
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
@@ -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
|
code_intelligence/symdb.py
DELETED
|
@@ -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
|
|
File without changes
|