chartflow 0.1__py3-none-any.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.
- chartflow/__init__.py +28 -0
- chartflow/_verify_importexport.py +169 -0
- chartflow/edit/__init__.py +119 -0
- chartflow/edit/code_editor.py +1007 -0
- chartflow/edit/code_folder.py +523 -0
- chartflow/edit/completer.py +2652 -0
- chartflow/edit/context_analyzer.py +521 -0
- chartflow/edit/demo.py +469 -0
- chartflow/edit/line_number_area.py +548 -0
- chartflow/edit/minimap.py +581 -0
- chartflow/edit/python_editor.py +509 -0
- chartflow/edit/syntax_highlighter.py +291 -0
- chartflow/edit/test_editor.py +190 -0
- chartflow/flow/__init__.py +31 -0
- chartflow/flow/_demo.py +98 -0
- chartflow/flow/chart.py +1101 -0
- chartflow/flow/editor.py +375 -0
- chartflow/flow/flow.py +6 -0
- chartflow/flow/qchart.py +1250 -0
- chartflow/flow/test/__init__.py +0 -0
- chartflow/flow/test/test_chart.py +537 -0
- chartflow/flow/test_decorator_sync.py +102 -0
- chartflow/fsm/__init__.py +84 -0
- chartflow/fsm/decorators.py +395 -0
- chartflow/fsm/demo.py +381 -0
- chartflow/fsm/demo_pyqt6.py +881 -0
- chartflow/fsm/demo_tree.py +46 -0
- chartflow/fsm/logic.py +447 -0
- chartflow/fsm/meta.py +569 -0
- chartflow/fsm/scheduler.py +427 -0
- chartflow/fsm/stage.py +2079 -0
- chartflow/fsm/stdio.py +66 -0
- chartflow/fsm/test/__init__.py +7 -0
- chartflow/fsm/test/test_decorators.py +210 -0
- chartflow/fsm/test/test_events.py +202 -0
- chartflow/fsm/test/test_integration.py +596 -0
- chartflow/fsm/test/test_logic.py +371 -0
- chartflow/fsm/test/test_meta.py +192 -0
- chartflow/fsm/test/test_runtime_param.py +336 -0
- chartflow/fsm/test/test_scheduler.py +280 -0
- chartflow/fsm/test/test_stage.py +314 -0
- chartflow/fsm/test_behavior_events.py +263 -0
- chartflow/fsm/test_nested_parallel.py +80 -0
- chartflow/fsm/test_stage_spec.py +448 -0
- chartflow/fsm/utils.py +34 -0
- chartflow/node/__init__.py +66 -0
- chartflow/node/_node_base.py +727 -0
- chartflow/node/_node_interaction.py +488 -0
- chartflow/node/_node_interface.py +426 -0
- chartflow/node/_node_markers.py +648 -0
- chartflow/node/_node_ports.py +536 -0
- chartflow/node/_port_interface.py +111 -0
- chartflow/node/arrow_item.py +287 -0
- chartflow/node/color_utils.py +113 -0
- chartflow/node/connection_item.py +939 -0
- chartflow/node/demo.py +258 -0
- chartflow/node/demo_arrow_drag.py +46 -0
- chartflow/node/demo_decorator.py +63 -0
- chartflow/node/demo_ports.py +69 -0
- chartflow/node/enums.py +35 -0
- chartflow/node/example.py +84 -0
- chartflow/node/layout_utils.py +307 -0
- chartflow/node/main_window.py +196 -0
- chartflow/node/menu_bar.py +240 -0
- chartflow/node/node_canvas.py +1730 -0
- chartflow/node/node_item.py +754 -0
- chartflow/node/popmenu.py +472 -0
- chartflow/node/port_item.py +591 -0
- chartflow/node/qnode_editor.py +73 -0
- chartflow/node/selection_rect.py +53 -0
- chartflow/node/style_panel.py +615 -0
- chartflow/node/styles.py +63 -0
- chartflow/node/test_qnode.py +2336 -0
- chartflow/showcase.py +528 -0
- chartflow/theme.py +508 -0
- chartflow-0.1.dist-info/METADATA +23 -0
- chartflow-0.1.dist-info/RECORD +79 -0
- chartflow-0.1.dist-info/WHEEL +5 -0
- chartflow-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"""
|
|
2
|
+
上下文分析器
|
|
3
|
+
分析代码上下文,支持隐藏的上下文代码(CA和CB)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from PyQt6.QtCore import QObject, pyqtSignal
|
|
7
|
+
from typing import List, Dict, Set, Optional, Tuple, Any
|
|
8
|
+
import ast
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ContextInfo:
|
|
13
|
+
"""上下文信息"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.classes: Dict[str, Dict] = {} # 类名 -> 类信息
|
|
16
|
+
self.functions: Dict[str, Dict] = {} # 函数名 -> 函数信息
|
|
17
|
+
self.variables: Dict[str, str] = {} # 变量名 -> 类型
|
|
18
|
+
self.imports: Dict[str, str] = {} # 导入的模块/名称
|
|
19
|
+
self.current_class: Optional[str] = None # 当前类
|
|
20
|
+
self.current_function: Optional[str] = None # 当前函数
|
|
21
|
+
self.indent_level: int = 0 # 当前缩进级别
|
|
22
|
+
self.scope_stack: List[str] = [] # 作用域栈
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CodeContext:
|
|
26
|
+
"""代码上下文 - 包含隐藏代码和可见代码"""
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.code_a: str = "" # 类定义代码 (CA)
|
|
29
|
+
self.code_b: str = "" # 函数定义代码 (CB)
|
|
30
|
+
self.prefix: str = "" # 前缀缩进
|
|
31
|
+
self.visible_code: str = "" # 可见代码 (CC)
|
|
32
|
+
|
|
33
|
+
def get_full_code(self) -> str:
|
|
34
|
+
"""获取完整代码(用于分析)"""
|
|
35
|
+
lines = []
|
|
36
|
+
|
|
37
|
+
if self.code_a:
|
|
38
|
+
lines.append(self.code_a)
|
|
39
|
+
lines.append("")
|
|
40
|
+
|
|
41
|
+
if self.code_b:
|
|
42
|
+
lines.append(self.code_b)
|
|
43
|
+
lines.append("")
|
|
44
|
+
|
|
45
|
+
# 添加可见代码(带缩进)
|
|
46
|
+
if self.prefix:
|
|
47
|
+
for line in self.visible_code.split('\n'):
|
|
48
|
+
lines.append(self.prefix + line)
|
|
49
|
+
else:
|
|
50
|
+
lines.append(self.visible_code)
|
|
51
|
+
|
|
52
|
+
return '\n'.join(lines)
|
|
53
|
+
|
|
54
|
+
def get_visible_line_number(self, full_line: int) -> int:
|
|
55
|
+
"""将完整代码行号转换为可见代码行号"""
|
|
56
|
+
offset = 0
|
|
57
|
+
if self.code_a:
|
|
58
|
+
offset += len(self.code_a.split('\n')) + 1
|
|
59
|
+
if self.code_b:
|
|
60
|
+
offset += len(self.code_b.split('\n')) + 1
|
|
61
|
+
|
|
62
|
+
visible_line = full_line - offset
|
|
63
|
+
return max(0, visible_line)
|
|
64
|
+
|
|
65
|
+
def get_full_line_number(self, visible_line: int) -> int:
|
|
66
|
+
"""将可见代码行号转换为完整代码行号"""
|
|
67
|
+
offset = 0
|
|
68
|
+
if self.code_a:
|
|
69
|
+
offset += len(self.code_a.split('\n')) + 1
|
|
70
|
+
if self.code_b:
|
|
71
|
+
offset += len(self.code_b.split('\n')) + 1
|
|
72
|
+
|
|
73
|
+
return visible_line + offset
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ContextAnalyzer(QObject):
|
|
77
|
+
"""上下文分析器"""
|
|
78
|
+
|
|
79
|
+
analysis_complete = pyqtSignal(object) # 分析完成信号
|
|
80
|
+
|
|
81
|
+
def __init__(self, parent=None):
|
|
82
|
+
super().__init__(parent)
|
|
83
|
+
self._context = CodeContext()
|
|
84
|
+
self._analysis_result: Optional[ContextInfo] = None
|
|
85
|
+
|
|
86
|
+
def set_context(self, code_a: str = "", code_b: str = "",
|
|
87
|
+
prefix: str = "", visible_code: str = ""):
|
|
88
|
+
"""
|
|
89
|
+
设置代码上下文
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
code_a: 类定义代码 (CA)
|
|
93
|
+
code_b: 函数定义代码 (CB)
|
|
94
|
+
prefix: 前缀缩进
|
|
95
|
+
visible_code: 可见代码 (CC)
|
|
96
|
+
"""
|
|
97
|
+
self._context.code_a = code_a
|
|
98
|
+
self._context.code_b = code_b
|
|
99
|
+
self._context.prefix = prefix
|
|
100
|
+
self._context.visible_code = visible_code
|
|
101
|
+
|
|
102
|
+
# 重新分析
|
|
103
|
+
self.analyze()
|
|
104
|
+
|
|
105
|
+
def analyze(self) -> ContextInfo:
|
|
106
|
+
"""分析代码上下文"""
|
|
107
|
+
full_code = self._context.get_full_code()
|
|
108
|
+
self._analysis_result = self._analyze_code(full_code)
|
|
109
|
+
self.analysis_complete.emit(self._analysis_result)
|
|
110
|
+
return self._analysis_result
|
|
111
|
+
|
|
112
|
+
def _analyze_code(self, code: str) -> ContextInfo:
|
|
113
|
+
"""分析代码"""
|
|
114
|
+
info = ContextInfo()
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
tree = ast.parse(code)
|
|
118
|
+
|
|
119
|
+
# 分析导入
|
|
120
|
+
self._analyze_imports(tree, info)
|
|
121
|
+
|
|
122
|
+
# 分析类和函数
|
|
123
|
+
self._analyze_definitions(tree, info)
|
|
124
|
+
|
|
125
|
+
# 分析变量
|
|
126
|
+
self._analyze_variables(tree, info)
|
|
127
|
+
|
|
128
|
+
except SyntaxError:
|
|
129
|
+
# 语法错误时返回空信息
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
return info
|
|
133
|
+
|
|
134
|
+
def _analyze_imports(self, tree: ast.AST, info: ContextInfo):
|
|
135
|
+
"""分析导入语句"""
|
|
136
|
+
for node in ast.walk(tree):
|
|
137
|
+
if isinstance(node, ast.Import):
|
|
138
|
+
for alias in node.names:
|
|
139
|
+
name = alias.asname if alias.asname else alias.name
|
|
140
|
+
info.imports[name] = alias.name
|
|
141
|
+
|
|
142
|
+
elif isinstance(node, ast.ImportFrom):
|
|
143
|
+
module = node.module or ""
|
|
144
|
+
for alias in node.names:
|
|
145
|
+
name = alias.asname if alias.asname else alias.name
|
|
146
|
+
info.imports[name] = f"{module}.{alias.name}"
|
|
147
|
+
|
|
148
|
+
def _analyze_definitions(self, tree: ast.AST, info: ContextInfo):
|
|
149
|
+
"""分析类和函数定义"""
|
|
150
|
+
for node in ast.walk(tree):
|
|
151
|
+
if isinstance(node, ast.ClassDef):
|
|
152
|
+
class_info = {
|
|
153
|
+
'name': node.name,
|
|
154
|
+
'bases': [self._get_name(base) for base in node.bases],
|
|
155
|
+
'methods': {},
|
|
156
|
+
'attributes': {},
|
|
157
|
+
'line': node.lineno,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# 分析方法
|
|
161
|
+
for item in node.body:
|
|
162
|
+
if isinstance(item, ast.FunctionDef):
|
|
163
|
+
method_info = self._analyze_function(item)
|
|
164
|
+
class_info['methods'][item.name] = method_info
|
|
165
|
+
elif isinstance(item, ast.Assign):
|
|
166
|
+
for target in item.targets:
|
|
167
|
+
if isinstance(target, ast.Name):
|
|
168
|
+
class_info['attributes'][target.id] = {
|
|
169
|
+
'line': item.lineno,
|
|
170
|
+
'value': self._get_value_repr(item.value)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
info.classes[node.name] = class_info
|
|
174
|
+
|
|
175
|
+
elif isinstance(node, ast.FunctionDef):
|
|
176
|
+
# 顶层函数
|
|
177
|
+
func_info = self._analyze_function(node)
|
|
178
|
+
info.functions[node.name] = func_info
|
|
179
|
+
|
|
180
|
+
def _analyze_function(self, node: ast.FunctionDef) -> Dict:
|
|
181
|
+
"""分析函数定义"""
|
|
182
|
+
return {
|
|
183
|
+
'name': node.name,
|
|
184
|
+
'args': [arg.arg for arg in node.args.args],
|
|
185
|
+
'defaults': len(node.args.defaults),
|
|
186
|
+
'vararg': node.args.vararg.arg if node.args.vararg else None,
|
|
187
|
+
'kwarg': node.args.kwarg.arg if node.args.kwarg else None,
|
|
188
|
+
'decorators': [self._get_name(d) for d in node.decorator_list],
|
|
189
|
+
'line': node.lineno,
|
|
190
|
+
'docstring': ast.get_docstring(node),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
def _analyze_variables(self, tree: ast.AST, info: ContextInfo):
|
|
194
|
+
"""分析变量定义"""
|
|
195
|
+
for node in ast.walk(tree):
|
|
196
|
+
if isinstance(node, ast.Assign):
|
|
197
|
+
for target in node.targets:
|
|
198
|
+
if isinstance(target, ast.Name):
|
|
199
|
+
var_type = self._infer_type(node.value)
|
|
200
|
+
info.variables[target.id] = var_type
|
|
201
|
+
|
|
202
|
+
elif isinstance(node, ast.AnnAssign):
|
|
203
|
+
if isinstance(node.target, ast.Name):
|
|
204
|
+
annotation = self._get_name(node.annotation)
|
|
205
|
+
info.variables[node.target.id] = annotation
|
|
206
|
+
|
|
207
|
+
def _infer_type(self, node: Optional[ast.expr]) -> str:
|
|
208
|
+
"""推断表达式类型"""
|
|
209
|
+
if node is None:
|
|
210
|
+
return "Any"
|
|
211
|
+
|
|
212
|
+
if isinstance(node, ast.Num):
|
|
213
|
+
if isinstance(node.n, int):
|
|
214
|
+
return "int"
|
|
215
|
+
return "float"
|
|
216
|
+
|
|
217
|
+
elif isinstance(node, ast.Str):
|
|
218
|
+
return "str"
|
|
219
|
+
|
|
220
|
+
elif isinstance(node, ast.List):
|
|
221
|
+
return "list"
|
|
222
|
+
|
|
223
|
+
elif isinstance(node, ast.Dict):
|
|
224
|
+
return "dict"
|
|
225
|
+
|
|
226
|
+
elif isinstance(node, ast.Set):
|
|
227
|
+
return "set"
|
|
228
|
+
|
|
229
|
+
elif isinstance(node, ast.Tuple):
|
|
230
|
+
return "tuple"
|
|
231
|
+
|
|
232
|
+
elif isinstance(node, ast.Call):
|
|
233
|
+
if isinstance(node.func, ast.Name):
|
|
234
|
+
return node.func.id
|
|
235
|
+
|
|
236
|
+
return "Any"
|
|
237
|
+
|
|
238
|
+
def _get_name(self, node: ast.AST) -> str:
|
|
239
|
+
"""获取节点名称"""
|
|
240
|
+
if isinstance(node, ast.Name):
|
|
241
|
+
return node.id
|
|
242
|
+
elif isinstance(node, ast.Attribute):
|
|
243
|
+
return f"{self._get_name(node.value)}.{node.attr}"
|
|
244
|
+
elif isinstance(node, ast.Subscript):
|
|
245
|
+
return self._get_name(node.value)
|
|
246
|
+
return str(node)
|
|
247
|
+
|
|
248
|
+
def _get_value_repr(self, node: ast.expr) -> str:
|
|
249
|
+
"""获取值的字符串表示"""
|
|
250
|
+
if isinstance(node, ast.Num):
|
|
251
|
+
return str(node.n)
|
|
252
|
+
elif isinstance(node, ast.Str):
|
|
253
|
+
return repr(node.s)
|
|
254
|
+
elif isinstance(node, ast.NameConstant):
|
|
255
|
+
return str(node.value)
|
|
256
|
+
elif isinstance(node, ast.Name):
|
|
257
|
+
return node.id
|
|
258
|
+
return "..."
|
|
259
|
+
|
|
260
|
+
def get_class_info(self, class_name: str) -> Optional[Dict]:
|
|
261
|
+
"""获取类信息"""
|
|
262
|
+
if self._analysis_result:
|
|
263
|
+
return self._analysis_result.classes.get(class_name)
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
def get_function_info(self, func_name: str) -> Optional[Dict]:
|
|
267
|
+
"""获取函数信息"""
|
|
268
|
+
if self._analysis_result:
|
|
269
|
+
return self._analysis_result.functions.get(func_name)
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
def get_variable_type(self, var_name: str) -> Optional[str]:
|
|
273
|
+
"""获取变量类型"""
|
|
274
|
+
if self._analysis_result:
|
|
275
|
+
return self._analysis_result.variables.get(var_name)
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
def get_available_names(self, line: int = None) -> List[str]:
|
|
279
|
+
"""获取可用名称列表"""
|
|
280
|
+
names = []
|
|
281
|
+
|
|
282
|
+
if self._analysis_result:
|
|
283
|
+
names.extend(self._analysis_result.imports.keys())
|
|
284
|
+
names.extend(self._analysis_result.classes.keys())
|
|
285
|
+
names.extend(self._analysis_result.functions.keys())
|
|
286
|
+
names.extend(self._analysis_result.variables.keys())
|
|
287
|
+
|
|
288
|
+
return sorted(set(names))
|
|
289
|
+
|
|
290
|
+
def get_context_at_line(self, line: int) -> Dict[str, Any]:
|
|
291
|
+
"""获取指定行号的上下文信息"""
|
|
292
|
+
result = {
|
|
293
|
+
'in_class': None,
|
|
294
|
+
'in_function': None,
|
|
295
|
+
'scope': 'module',
|
|
296
|
+
'available_names': [],
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if not self._analysis_result:
|
|
300
|
+
return result
|
|
301
|
+
|
|
302
|
+
# 查找当前所在的类和函数
|
|
303
|
+
for class_name, class_info in self._analysis_result.classes.items():
|
|
304
|
+
if class_info['line'] <= line:
|
|
305
|
+
result['in_class'] = class_name
|
|
306
|
+
for method_name, method_info in class_info['methods'].items():
|
|
307
|
+
if method_info['line'] <= line:
|
|
308
|
+
result['in_function'] = method_name
|
|
309
|
+
|
|
310
|
+
for func_name, func_info in self._analysis_result.functions.items():
|
|
311
|
+
if func_info['line'] <= line:
|
|
312
|
+
result['in_function'] = func_name
|
|
313
|
+
|
|
314
|
+
# 确定作用域
|
|
315
|
+
if result['in_class'] and result['in_function']:
|
|
316
|
+
result['scope'] = 'method'
|
|
317
|
+
elif result['in_function']:
|
|
318
|
+
result['scope'] = 'function'
|
|
319
|
+
elif result['in_class']:
|
|
320
|
+
result['scope'] = 'class'
|
|
321
|
+
|
|
322
|
+
# 获取可用名称
|
|
323
|
+
result['available_names'] = self._get_available_names_for_scope(result)
|
|
324
|
+
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
def _get_available_names_for_scope(self, context: Dict) -> List[str]:
|
|
328
|
+
"""获取指定作用域的可用名称"""
|
|
329
|
+
names = []
|
|
330
|
+
|
|
331
|
+
if self._analysis_result:
|
|
332
|
+
# 所有作用域都可用
|
|
333
|
+
names.extend(self._analysis_result.imports.keys())
|
|
334
|
+
names.extend(self._analysis_result.classes.keys())
|
|
335
|
+
names.extend(self._analysis_result.functions.keys())
|
|
336
|
+
|
|
337
|
+
# 在方法中,添加self的属性和方法
|
|
338
|
+
if context['scope'] == 'method' and context['in_class']:
|
|
339
|
+
class_info = self._analysis_result.classes.get(context['in_class'])
|
|
340
|
+
if class_info:
|
|
341
|
+
names.append('self')
|
|
342
|
+
names.extend(class_info['methods'].keys())
|
|
343
|
+
names.extend(class_info['attributes'].keys())
|
|
344
|
+
|
|
345
|
+
return sorted(set(names))
|
|
346
|
+
|
|
347
|
+
def get_completion_context(self, code: str, cursor_pos: int) -> Dict[str, Any]:
|
|
348
|
+
"""获取补全上下文"""
|
|
349
|
+
full_code = self._context.get_full_code()
|
|
350
|
+
|
|
351
|
+
# 计算光标在完整代码中的位置
|
|
352
|
+
visible_prefix = code[:cursor_pos]
|
|
353
|
+
full_prefix = self._context.code_a + '\n\n' + self._context.code_b + '\n\n'
|
|
354
|
+
if self._context.prefix:
|
|
355
|
+
for line in visible_prefix.split('\n'):
|
|
356
|
+
full_prefix += self._context.prefix + line + '\n'
|
|
357
|
+
else:
|
|
358
|
+
full_prefix += visible_prefix
|
|
359
|
+
|
|
360
|
+
full_cursor_pos = len(full_prefix)
|
|
361
|
+
|
|
362
|
+
# 获取当前词
|
|
363
|
+
current_word = ""
|
|
364
|
+
for i in range(cursor_pos - 1, -1, -1):
|
|
365
|
+
if i < 0 or not (code[i].isalnum() or code[i] == '_'):
|
|
366
|
+
break
|
|
367
|
+
current_word = code[i] + current_word
|
|
368
|
+
|
|
369
|
+
# 检查是否在点号后
|
|
370
|
+
after_dot = False
|
|
371
|
+
object_name = ""
|
|
372
|
+
if cursor_pos > 0:
|
|
373
|
+
# 跳过空白
|
|
374
|
+
check_pos = cursor_pos - 1
|
|
375
|
+
while check_pos >= 0 and code[check_pos].isspace():
|
|
376
|
+
check_pos -= 1
|
|
377
|
+
|
|
378
|
+
if check_pos >= 0 and code[check_pos] == '.':
|
|
379
|
+
after_dot = True
|
|
380
|
+
# 获取对象名
|
|
381
|
+
obj_end = check_pos
|
|
382
|
+
obj_start = obj_end - 1
|
|
383
|
+
while obj_start >= 0 and (code[obj_start].isalnum() or code[obj_start] == '_'):
|
|
384
|
+
obj_start -= 1
|
|
385
|
+
obj_start += 1
|
|
386
|
+
object_name = code[obj_start:obj_end].strip()
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
'current_word': current_word,
|
|
390
|
+
'after_dot': after_dot,
|
|
391
|
+
'object_name': object_name,
|
|
392
|
+
'full_code': full_code,
|
|
393
|
+
'cursor_pos': full_cursor_pos,
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class ScopeAnalyzer:
|
|
398
|
+
"""作用域分析器 - 分析代码作用域"""
|
|
399
|
+
|
|
400
|
+
def __init__(self):
|
|
401
|
+
self.scopes: List[Dict] = []
|
|
402
|
+
|
|
403
|
+
def analyze(self, code: str) -> List[Dict]:
|
|
404
|
+
"""分析代码作用域"""
|
|
405
|
+
self.scopes = []
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
tree = ast.parse(code)
|
|
409
|
+
self._analyze_node(tree, 0)
|
|
410
|
+
except SyntaxError:
|
|
411
|
+
pass
|
|
412
|
+
|
|
413
|
+
return self.scopes
|
|
414
|
+
|
|
415
|
+
def _analyze_node(self, node: ast.AST, indent: int):
|
|
416
|
+
"""递归分析节点"""
|
|
417
|
+
if isinstance(node, ast.ClassDef):
|
|
418
|
+
self.scopes.append({
|
|
419
|
+
'type': 'class',
|
|
420
|
+
'name': node.name,
|
|
421
|
+
'line_start': node.lineno,
|
|
422
|
+
'line_end': self._get_end_line(node),
|
|
423
|
+
'indent': indent,
|
|
424
|
+
})
|
|
425
|
+
for item in node.body:
|
|
426
|
+
self._analyze_node(item, indent + 1)
|
|
427
|
+
|
|
428
|
+
elif isinstance(node, ast.FunctionDef):
|
|
429
|
+
self.scopes.append({
|
|
430
|
+
'type': 'function',
|
|
431
|
+
'name': node.name,
|
|
432
|
+
'line_start': node.lineno,
|
|
433
|
+
'line_end': self._get_end_line(node),
|
|
434
|
+
'indent': indent,
|
|
435
|
+
})
|
|
436
|
+
for item in node.body:
|
|
437
|
+
self._analyze_node(item, indent + 1)
|
|
438
|
+
|
|
439
|
+
elif isinstance(node, ast.If):
|
|
440
|
+
self.scopes.append({
|
|
441
|
+
'type': 'if',
|
|
442
|
+
'line_start': node.lineno,
|
|
443
|
+
'line_end': self._get_end_line(node),
|
|
444
|
+
'indent': indent,
|
|
445
|
+
})
|
|
446
|
+
for item in node.body:
|
|
447
|
+
self._analyze_node(item, indent + 1)
|
|
448
|
+
for item in node.orelse:
|
|
449
|
+
self._analyze_node(item, indent + 1)
|
|
450
|
+
|
|
451
|
+
elif isinstance(node, ast.For):
|
|
452
|
+
self.scopes.append({
|
|
453
|
+
'type': 'for',
|
|
454
|
+
'line_start': node.lineno,
|
|
455
|
+
'line_end': self._get_end_line(node),
|
|
456
|
+
'indent': indent,
|
|
457
|
+
})
|
|
458
|
+
for item in node.body:
|
|
459
|
+
self._analyze_node(item, indent + 1)
|
|
460
|
+
|
|
461
|
+
elif isinstance(node, ast.While):
|
|
462
|
+
self.scopes.append({
|
|
463
|
+
'type': 'while',
|
|
464
|
+
'line_start': node.lineno,
|
|
465
|
+
'line_end': self._get_end_line(node),
|
|
466
|
+
'indent': indent,
|
|
467
|
+
})
|
|
468
|
+
for item in node.body:
|
|
469
|
+
self._analyze_node(item, indent + 1)
|
|
470
|
+
|
|
471
|
+
elif isinstance(node, ast.With):
|
|
472
|
+
self.scopes.append({
|
|
473
|
+
'type': 'with',
|
|
474
|
+
'line_start': node.lineno,
|
|
475
|
+
'line_end': self._get_end_line(node),
|
|
476
|
+
'indent': indent,
|
|
477
|
+
})
|
|
478
|
+
for item in node.body:
|
|
479
|
+
self._analyze_node(item, indent + 1)
|
|
480
|
+
|
|
481
|
+
elif isinstance(node, ast.Try):
|
|
482
|
+
self.scopes.append({
|
|
483
|
+
'type': 'try',
|
|
484
|
+
'line_start': node.lineno,
|
|
485
|
+
'line_end': self._get_end_line(node),
|
|
486
|
+
'indent': indent,
|
|
487
|
+
})
|
|
488
|
+
for item in node.body:
|
|
489
|
+
self._analyze_node(item, indent + 1)
|
|
490
|
+
for handler in node.handlers:
|
|
491
|
+
for item in handler.body:
|
|
492
|
+
self._analyze_node(item, indent + 1)
|
|
493
|
+
for item in node.orelse:
|
|
494
|
+
self._analyze_node(item, indent + 1)
|
|
495
|
+
for item in node.finalbody:
|
|
496
|
+
self._analyze_node(item, indent + 1)
|
|
497
|
+
|
|
498
|
+
def _get_end_line(self, node: ast.AST) -> int:
|
|
499
|
+
"""获取节点的结束行号"""
|
|
500
|
+
max_line = getattr(node, 'lineno', 0)
|
|
501
|
+
for child in ast.walk(node):
|
|
502
|
+
if hasattr(child, 'lineno'):
|
|
503
|
+
max_line = max(max_line, child.lineno)
|
|
504
|
+
if hasattr(child, 'end_lineno') and child.end_lineno:
|
|
505
|
+
max_line = max(max_line, child.end_lineno)
|
|
506
|
+
return max_line
|
|
507
|
+
|
|
508
|
+
def get_scope_at_line(self, line: int) -> Optional[Dict]:
|
|
509
|
+
"""获取指定行号的作用域"""
|
|
510
|
+
for scope in self.scopes:
|
|
511
|
+
if scope['line_start'] <= line <= scope['line_end']:
|
|
512
|
+
return scope
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
def get_foldable_regions(self) -> List[Tuple[int, int]]:
|
|
516
|
+
"""获取可折叠区域列表"""
|
|
517
|
+
regions = []
|
|
518
|
+
for scope in self.scopes:
|
|
519
|
+
if scope['line_end'] > scope['line_start']:
|
|
520
|
+
regions.append((scope['line_start'], scope['line_end']))
|
|
521
|
+
return regions
|