pyqt-code-editor 0.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.
Files changed (73) hide show
  1. pyqt_code_editor/__init__.py +5 -0
  2. pyqt_code_editor/_settings.py +232 -0
  3. pyqt_code_editor/app.py +438 -0
  4. pyqt_code_editor/code_editors/__init__.py +1 -0
  5. pyqt_code_editor/code_editors/code_editors.py +33 -0
  6. pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  7. pyqt_code_editor/code_editors/languages/generic.py +11 -0
  8. pyqt_code_editor/code_editors/languages/markdown.py +11 -0
  9. pyqt_code_editor/code_editors/languages/python.py +13 -0
  10. pyqt_code_editor/code_editors/languages/text.py +9 -0
  11. pyqt_code_editor/components/__init__.py +0 -0
  12. pyqt_code_editor/components/editor_panel.py +399 -0
  13. pyqt_code_editor/components/find_in_files.py +199 -0
  14. pyqt_code_editor/components/jupyter_console.py +487 -0
  15. pyqt_code_editor/components/project_explorer.py +515 -0
  16. pyqt_code_editor/components/settings_panel.py +162 -0
  17. pyqt_code_editor/components/sigmund.py +75 -0
  18. pyqt_code_editor/components/workspace_explorer.py +162 -0
  19. pyqt_code_editor/mixins/__init__.py +15 -0
  20. pyqt_code_editor/mixins/auto_pair.py +201 -0
  21. pyqt_code_editor/mixins/base.py +142 -0
  22. pyqt_code_editor/mixins/check.py +94 -0
  23. pyqt_code_editor/mixins/comment.py +91 -0
  24. pyqt_code_editor/mixins/complete.py +482 -0
  25. pyqt_code_editor/mixins/execute.py +182 -0
  26. pyqt_code_editor/mixins/file_link.py +166 -0
  27. pyqt_code_editor/mixins/line_number.py +206 -0
  28. pyqt_code_editor/mixins/python_auto_indent.py +239 -0
  29. pyqt_code_editor/mixins/search_replace.py +396 -0
  30. pyqt_code_editor/mixins/shortcuts.py +228 -0
  31. pyqt_code_editor/mixins/smart_backspace_delete.py +120 -0
  32. pyqt_code_editor/mixins/symbols.py +45 -0
  33. pyqt_code_editor/mixins/theme.py +99 -0
  34. pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  35. pyqt_code_editor/mixins/zoom.py +51 -0
  36. pyqt_code_editor/python_utils.py +936 -0
  37. pyqt_code_editor/signal_router.py +116 -0
  38. pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  39. pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  40. pyqt_code_editor/syntax_highlighters/languages/generic.py +86 -0
  41. pyqt_code_editor/syntax_highlighters/languages/python.py +62 -0
  42. pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +26 -0
  43. pyqt_code_editor/themes.py +36 -0
  44. pyqt_code_editor/utils.py +96 -0
  45. pyqt_code_editor/watchdog.py +141 -0
  46. pyqt_code_editor/widgets/__init__.py +6 -0
  47. pyqt_code_editor/widgets/calltip_widget.py +44 -0
  48. pyqt_code_editor/widgets/completion_popup.py +89 -0
  49. pyqt_code_editor/widgets/dock.py +15 -0
  50. pyqt_code_editor/widgets/quick_open_dialog.py +196 -0
  51. pyqt_code_editor/widgets/quick_open_file_dialog.py +32 -0
  52. pyqt_code_editor/widgets/quick_symbol_dialog.py +13 -0
  53. pyqt_code_editor/widgets/search_replace_frame.py +128 -0
  54. pyqt_code_editor/widgets/tab_splitter.py +22 -0
  55. pyqt_code_editor/widgets/tabbed_editor.py +351 -0
  56. pyqt_code_editor/worker/__init__.py +0 -0
  57. pyqt_code_editor/worker/languages/__init__.py +0 -0
  58. pyqt_code_editor/worker/languages/generic.py +15 -0
  59. pyqt_code_editor/worker/languages/python.py +17 -0
  60. pyqt_code_editor/worker/manager.py +113 -0
  61. pyqt_code_editor/worker/process.py +128 -0
  62. pyqt_code_editor/worker/providers/__init__.py +1 -0
  63. pyqt_code_editor/worker/providers/codestral.py +56 -0
  64. pyqt_code_editor/worker/providers/jedi.py +115 -0
  65. pyqt_code_editor/worker/providers/ruff.py +45 -0
  66. pyqt_code_editor/worker/providers/sigmund.py +29 -0
  67. pyqt_code_editor/worker/providers/symbol.py +78 -0
  68. pyqt_code_editor/worker/settings.py +1 -0
  69. pyqt_code_editor-0.0.1.dist-info/METADATA +69 -0
  70. pyqt_code_editor-0.0.1.dist-info/RECORD +73 -0
  71. pyqt_code_editor-0.0.1.dist-info/WHEEL +4 -0
  72. pyqt_code_editor-0.0.1.dist-info/entry_points.txt +4 -0
  73. pyqt_code_editor-0.0.1.dist-info/licenses/COPYING +674 -0
@@ -0,0 +1,487 @@
1
+ from qtpy.QtWidgets import (QTabWidget, QWidget, QVBoxLayout, QToolButton, QMenu,
2
+ QAction, QHBoxLayout)
3
+ from qtpy.QtCore import Signal, QTimer
4
+ from qtconsole.rich_jupyter_widget import RichJupyterWidget
5
+ from qtconsole.manager import QtKernelManager
6
+ from jupyter_client.kernelspec import KernelSpecManager
7
+ import qtawesome as qta
8
+ import os
9
+ import logging
10
+ import uuid
11
+ import json
12
+ from concurrent.futures import Future
13
+ from .. import settings
14
+ from ..themes import THEMES, OUTER_CONTENT_MARGINS, HORIZONTAL_SPACING
15
+ from ..widgets import Dock
16
+ from .. import watchdog
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class JupyterConsoleTab(QWidget):
21
+ """Individual tab containing a Jupyter console with its own kernel"""
22
+
23
+ execution_complete = Signal(str, object) # Signal for output interception
24
+ workspace_updated = Signal(dict) # Signal for workspace updates
25
+
26
+ def __init__(self, kernel_name=None, parent=None):
27
+ super().__init__(parent)
28
+ self.kernel_name = kernel_name or 'python3'
29
+ self.layout = QVBoxLayout(self)
30
+ self.layout.setContentsMargins(*OUTER_CONTENT_MARGINS)
31
+
32
+ # Dictionary to track pending message results
33
+ self._pending_messages = {}
34
+ # Set of internal message IDs (for workspace queries, etc.)
35
+ self._internal_messages = set()
36
+ # Set of message IDs where output should be hidden
37
+ self._silent_messages = set()
38
+
39
+ # Create Jupyter console widget
40
+ self.jupyter_widget = RichJupyterWidget()
41
+ self.layout.addWidget(self.jupyter_widget)
42
+
43
+ # Set up kernel - using out-of-process kernel
44
+ self.kernel_manager = QtKernelManager(kernel_name=self.kernel_name)
45
+ self.kernel_manager.start_kernel()
46
+
47
+ self.kernel_client = self.kernel_manager.client()
48
+ self.kernel_client.start_channels()
49
+
50
+ # Connect the console to the kernel
51
+ self.jupyter_widget.kernel_manager = self.kernel_manager
52
+ self.jupyter_widget.kernel_client = self.kernel_client
53
+
54
+ # Set up output capture
55
+ self._setup_output_interception()
56
+ self.jupyter_widget.set_default_style(colors='linux')
57
+ background_color = THEMES[settings.color_scheme]['background_color']
58
+ stylesheet = f'''QPlainTextEdit, QTextEdit {{
59
+ background-color: '{background_color}';
60
+ background-clip: padding;
61
+ color: white;
62
+ font-size: {settings.font_size}pt;
63
+ font-family: '{settings.font_family}';
64
+ selection-background-color: #555;
65
+ }}
66
+ .inverted {{
67
+ background-color: white;
68
+ color: black;
69
+ }}
70
+ .error {{ color: red; }}
71
+ .in-prompt-number {{ font-weight: bold; }}
72
+ .out-prompt-number {{ font-weight: bold; }}
73
+ .in-prompt,
74
+ .in-prompt-number {{ color: lime; }}
75
+ .out-prompt,
76
+ .out-prompt-number {{ color: red; }}
77
+ '''
78
+ self.jupyter_widget.setStyleSheet(stylesheet)
79
+
80
+ # Connect execution_complete to auto-update workspace
81
+ self.execution_complete.connect(self._on_execution_complete)
82
+
83
+ def _setup_output_interception(self):
84
+ """Set up output interception to capture kernel output"""
85
+ # Save reference to the original handler
86
+ self._original_iopub_handler = self.jupyter_widget._dispatch
87
+ # Disconnect the original handler
88
+ self.jupyter_widget.kernel_client.iopub_channel.message_received.disconnect(
89
+ self._original_iopub_handler)
90
+ # Connect our handler first
91
+ self.jupyter_widget.kernel_client.iopub_channel.message_received.connect(
92
+ self._handle_iopub_message)
93
+
94
+ def _handle_iopub_message(self, msg):
95
+ """Handle messages from the kernel's IOPub channel"""
96
+ msg_type = msg.get('msg_type', '')
97
+ content = msg.get('content', {})
98
+ parent_header = msg.get('parent_header', {})
99
+ msg_id = parent_header.get('msg_id')
100
+
101
+ # Check if this is a silent execution (output should be hidden)
102
+ is_silent = msg_id in self._silent_messages
103
+
104
+ # Check if this message is a response to a tracked request
105
+ if msg_id in self._pending_messages:
106
+ # Handle responses for workspace queries
107
+ if msg_type == 'stream' and 'text' in content:
108
+ future = self._pending_messages[msg_id]
109
+ if not future.done():
110
+ try:
111
+ # Try to parse JSON output for workspace queries
112
+ output = content['text'].strip()
113
+ if output:
114
+ try:
115
+ data = json.loads(output)
116
+ future.set_result(data)
117
+ except json.JSONDecodeError:
118
+ future.set_result(output)
119
+ except Exception as e:
120
+ logger.error(f"Error processing message: {e}")
121
+ future.set_exception(e)
122
+
123
+ # Check if response is complete
124
+ elif msg_type == 'status' and content.get('execution_state') == 'idle':
125
+ # Message processing is complete
126
+ if msg_id in self._pending_messages:
127
+ future = self._pending_messages[msg_id]
128
+ # If the future is still not done, set an empty result
129
+ if not future.done():
130
+ future.set_result({})
131
+ # Clean up
132
+ self._cleanup_message_ids(msg_id)
133
+
134
+ # Only emit execution_complete for non-internal messages
135
+ if msg_id not in self._internal_messages:
136
+ # Detect execution completion with status messages
137
+ if not is_silent and msg_type == 'status' and content.get('execution_state') == 'idle':
138
+ # This indicates the execution is complete, regardless of output
139
+
140
+ self.execution_complete.emit('', {})
141
+ # Capture execution results for regular code execution
142
+ if msg_type == 'execute_result':
143
+ data = content.get('data', {})
144
+ text_output = data.get('text/plain', '')
145
+ self.execution_complete.emit(text_output, content)
146
+
147
+ # Capture stdout/stderr
148
+ elif msg_type in ('stream', 'display_data', 'error'):
149
+ if msg_type == 'stream':
150
+ output = content.get('text', '')
151
+ elif msg_type == 'display_data':
152
+ output = str(content.get('data', {}).get('text/plain', ''))
153
+ else: # error
154
+ output = '\n'.join(content.get('traceback', []))
155
+
156
+ self.execution_complete.emit(output, content)
157
+
158
+ # IMPORTANT: Only forward non-silent messages to the widget
159
+ if not is_silent or msg_type not in ('execute_result', 'display_data',
160
+ 'stream', 'error'):
161
+ self._original_iopub_handler(msg)
162
+
163
+ def _cleanup_message_ids(self, msg_id):
164
+ """Clean up message IDs from tracking sets"""
165
+ self._pending_messages.pop(msg_id, None)
166
+ self._internal_messages.discard(msg_id)
167
+ self._silent_messages.discard(msg_id)
168
+
169
+ def _on_execution_complete(self, output, content):
170
+ """Automatically update workspace after regular code execution"""
171
+ # Use a small delay to ensure the kernel has processed everything
172
+ QTimer.singleShot(100, self.update_workspace)
173
+
174
+ def update_workspace(self):
175
+ """Trigger a workspace update and emit the signal when complete"""
176
+ future = self.get_workspace_async()
177
+ future.add_done_callback(self._on_workspace_update_complete)
178
+
179
+ def _on_workspace_update_complete(self, future):
180
+ """Handle completion of workspace update"""
181
+ try:
182
+ workspace_data = future.result()
183
+ logger.info("Workspace updated")
184
+ kernel_pid = workspace_data.pop('__kernel_pid', None)
185
+ if kernel_pid is not None:
186
+ watchdog.register_subprocess(kernel_pid)
187
+ self.workspace_updated.emit(workspace_data)
188
+ except Exception as e:
189
+ logger.error(f"Error updating workspace: {e}")
190
+ # Emit empty workspace on error
191
+ self.workspace_updated.emit({})
192
+
193
+ def execute_code(self, code):
194
+ """Execute a code snippet in this kernel"""
195
+ return self.jupyter_widget.execute(code)
196
+
197
+ def execute_silently(self, code, internal=False, hide_output=True):
198
+ """
199
+ Execute code silently without showing it in the console
200
+
201
+ Args:
202
+ code (str): The code to execute
203
+ internal (bool): If True, marks this as an internal query that
204
+ shouldn't trigger workspace updates
205
+ hide_output (bool): If True, hide any output generated by this execution
206
+ """
207
+ msg_id = self.kernel_client.execute(code, silent=True, store_history=False)
208
+ if msg_id:
209
+ if internal:
210
+ self._internal_messages.add(msg_id)
211
+ if hide_output:
212
+ self._silent_messages.add(msg_id)
213
+ return msg_id
214
+
215
+ def execute_and_return_result(self, code, timeout=5.0):
216
+ """Execute code silently and return the captured output synchronously"""
217
+ future = self.execute_and_get_future(code)
218
+ try:
219
+ return future.result(timeout=timeout)
220
+ except Exception as e:
221
+ logger.error(f"Failed to get result: {e}")
222
+ return None
223
+
224
+ def execute_and_get_future(self, code):
225
+ """Execute code silently and return a future for the result"""
226
+ future = Future()
227
+ msg_id = self.execute_silently(code, internal=True, hide_output=True)
228
+ if not msg_id:
229
+ future.set_result(None)
230
+ return future
231
+
232
+ self._pending_messages[msg_id] = future
233
+ return future
234
+
235
+ def get_workspace_async(self):
236
+ """Asynchronously get the variables in the kernel's workspace"""
237
+ # Create a unique variable name for results
238
+ result_var = f"_workspace_data_{uuid.uuid4().hex[:8]}"
239
+
240
+ # Define the code to execute in the kernel
241
+ code = f"""
242
+ import sys
243
+ import os
244
+ import json
245
+
246
+ {result_var} = {{'__kernel_pid': os.getpid()}}
247
+
248
+ # Get all variables from globals
249
+ for __var_name, __var_value in list(globals().items()):
250
+ # Skip internal variables and modules
251
+ if __var_name.startswith('_') or __var_name == '{result_var}' \
252
+ or __var_name in ('quit', 'exit', 'In', 'Out', 'get_ipython'):
253
+ continue
254
+ # Determine type
255
+ __var_type = type(__var_value).__name__
256
+ if __var_type in ('module', 'function', 'type'):
257
+ continue
258
+
259
+ # Create a preview based on type
260
+ try:
261
+ if __var_type == 'DataMatrix':
262
+ __preview = f"DataMatrix: {{__var_value.shape}}"
263
+ elif __var_type == 'DataFrame':
264
+ __preview = f"DataFrame: {{__var_value.shape[0]}}×{{__var_value.shape[1]}}"
265
+ elif __var_type == 'Series':
266
+ __preview = f"Series: {{len(__var_value)}}"
267
+ elif __var_type == 'ndarray':
268
+ __preview = f"Array: {{__var_value.shape}}"
269
+ elif __var_type in ('list', 'tuple', 'dict', 'set'):
270
+ __preview = f"{{__var_type}}[{{len(__var_value)}}]"
271
+ elif __var_type in ('str', 'int', 'float', 'bool'):
272
+ # For simple types, just use repr with limits
273
+ __preview = repr(__var_value)
274
+ if len(__preview) > 50:
275
+ preview = preview[:47] + '...'
276
+ else:
277
+ # For other types, just show the type
278
+ __preview = f"{{__var_type}} object"
279
+
280
+ {result_var}[__var_name] = {{'type': __var_type, 'preview': __preview}}
281
+ except Exception as e:
282
+ {result_var}[__var_name] = {{'type': __var_type, 'preview': f"<Error: {{str(e)}}>"}};
283
+
284
+ # Print as JSON for easy parsing
285
+ print(json.dumps({result_var}))
286
+ """
287
+ return self.execute_and_get_future(code)
288
+
289
+ def execute_file(self, filepath):
290
+ """Execute a file in this kernel"""
291
+ code = f"%run {filepath}"
292
+ self.execute_code(code)
293
+
294
+ def change_directory(self, directory):
295
+ """Change the kernel's working directory"""
296
+ if os.path.exists(directory):
297
+ code = f"import os\nos.chdir(r'{directory}')"
298
+ self.execute_silently(code)
299
+ return True
300
+ return False
301
+
302
+ def interrupt_kernel(self):
303
+ """Send interrupt (SIGINT) to the kernel"""
304
+ if self.kernel_manager.has_kernel:
305
+ logger.info(f"Sending interrupt to kernel {self.kernel_name}")
306
+ self.kernel_manager.interrupt_kernel()
307
+ return True
308
+ return False
309
+
310
+ def restart_kernel(self):
311
+ """Restart the kernel"""
312
+ if self.kernel_manager.has_kernel:
313
+ logger.info(f"Restarting kernel {self.kernel_name}")
314
+ self.jupyter_widget.request_restart_kernel()
315
+ return True
316
+ return False
317
+
318
+ def shutdown_kernel(self):
319
+ """Shutdown the kernel"""
320
+ self.kernel_client.stop_channels()
321
+ self.kernel_manager.shutdown_kernel()
322
+
323
+
324
+ class JupyterConsole(Dock):
325
+ """Dockable widget containing tabbed Jupyter consoles"""
326
+
327
+ execution_complete = Signal(str, object) # Signal for output interception
328
+ workspace_updated = Signal(dict) # Signal for workspace updates
329
+
330
+ def __init__(self, parent=None, default_kernel='python3'):
331
+ super().__init__("Jupyter Console", parent)
332
+ self.setObjectName('jupyter_console')
333
+ self.default_kernel = default_kernel
334
+
335
+ # Initialize the tab widget
336
+ self.tab_widget = QTabWidget()
337
+ self.tab_widget.setTabsClosable(True)
338
+ self.tab_widget.tabCloseRequested.connect(self.close_tab)
339
+ self.tab_widget.currentChanged.connect(self._on_tab_changed)
340
+ self.setWidget(self.tab_widget)
341
+
342
+ # Create corner widget with multiple buttons
343
+ corner_widget = QWidget()
344
+ corner_layout = QHBoxLayout(corner_widget)
345
+ corner_layout.setContentsMargins(0, 0, 0, 0)
346
+ corner_layout.setSpacing(HORIZONTAL_SPACING)
347
+
348
+ # Add kernel button (for new kernels)
349
+ self.kernel_button = QToolButton()
350
+ self.kernel_button.setIcon(qta.icon('mdi6.plus'))
351
+ self.kernel_button.setToolTip("Add new kernel")
352
+ self.kernel_button.setPopupMode(QToolButton.InstantPopup)
353
+ self.kernel_button.setAutoRaise(True) # Make the button flat
354
+ corner_layout.addWidget(self.kernel_button)
355
+
356
+ # Add restart kernel button
357
+ self.restart_button = QToolButton()
358
+ self.restart_button.setIcon(qta.icon('mdi6.restart'))
359
+ self.restart_button.setToolTip("Restart current kernel")
360
+ self.restart_button.clicked.connect(self.restart_current_kernel)
361
+ self.restart_button.setAutoRaise(True) # Make the button flat
362
+ corner_layout.addWidget(self.restart_button)
363
+
364
+ # Add interrupt kernel button
365
+ self.interrupt_button = QToolButton()
366
+ self.interrupt_button.setIcon(qta.icon('mdi6.stop'))
367
+ self.interrupt_button.setToolTip("Interrupt current kernel (Ctrl+C)")
368
+ self.interrupt_button.clicked.connect(self.interrupt_current_kernel)
369
+ self.interrupt_button.setAutoRaise(True) # Make the button flat
370
+ corner_layout.addWidget(self.interrupt_button)
371
+
372
+ # Set the corner widget
373
+ self.tab_widget.setCornerWidget(corner_widget)
374
+
375
+ # Create kernel menu
376
+ self.kernel_menu = QMenu(self.kernel_button)
377
+ self.kernel_button.setMenu(self.kernel_menu)
378
+ self.refresh_kernel_menu()
379
+
380
+ # Start with a default kernel
381
+ self.add_console_tab(self.default_kernel)
382
+
383
+ def _on_tab_changed(self, index):
384
+ console_tab = self.tab_widget.widget(index)
385
+ console_tab.update_workspace()
386
+
387
+ def refresh_kernel_menu(self):
388
+ """Refresh the list of available kernels"""
389
+ self.kernel_menu.clear()
390
+
391
+ # Get available kernelspecs
392
+ kernel_spec_manager = KernelSpecManager()
393
+ specs = kernel_spec_manager.get_all_specs()
394
+
395
+ for spec_name, spec in specs.items():
396
+ display_name = spec['spec']['display_name']
397
+ action = QAction(display_name, self)
398
+ action.setData(spec_name)
399
+ action.triggered.connect(self.kernel_menu_triggered)
400
+ self.kernel_menu.addAction(action)
401
+
402
+ def kernel_menu_triggered(self):
403
+ """Handle kernel menu item selection"""
404
+ action = self.sender()
405
+ if action:
406
+ kernel_name = action.data()
407
+ self.add_console_tab(kernel_name)
408
+
409
+ def add_console_tab(self, kernel_name):
410
+ """Add a new console tab with the specified kernel"""
411
+ # Create and add the new tab
412
+ console_tab = JupyterConsoleTab(kernel_name=kernel_name, parent=self)
413
+ console_tab.execution_complete.connect(self.handle_execution_complete)
414
+ console_tab.workspace_updated.connect(self.handle_workspace_updated)
415
+ index = self.tab_widget.addTab(console_tab, kernel_name)
416
+ self.tab_widget.setCurrentIndex(index)
417
+ return console_tab
418
+
419
+ def close_tab(self, index):
420
+ """Close a console tab and shut down its kernel"""
421
+ widget = self.tab_widget.widget(index)
422
+ if widget:
423
+ # Shut down the kernel
424
+ widget.shutdown_kernel()
425
+
426
+ # Remove the tab
427
+ self.tab_widget.removeTab(index)
428
+
429
+ # If that was the last tab, add a new one with the default kernel
430
+ if self.tab_widget.count() == 0:
431
+ self.add_console_tab(self.default_kernel)
432
+
433
+ def get_current_console(self):
434
+ """Get the currently active console tab"""
435
+ return self.tab_widget.currentWidget()
436
+
437
+ def restart_current_kernel(self):
438
+ """Restart the kernel in the current tab"""
439
+ console = self.get_current_console()
440
+ if console:
441
+ logger.info("Restarting current kernel")
442
+ success = console.restart_kernel()
443
+ if success:
444
+ logger.info("Kernel restart initiated")
445
+ else:
446
+ logger.warning("Failed to restart kernel")
447
+
448
+ def interrupt_current_kernel(self):
449
+ """Send interrupt signal to the current kernel"""
450
+ console = self.get_current_console()
451
+ if console:
452
+ logger.info("Interrupting current kernel")
453
+ success = console.interrupt_kernel()
454
+ if success:
455
+ logger.info("Interrupt signal sent to kernel")
456
+ else:
457
+ logger.warning("Failed to interrupt kernel")
458
+
459
+ def execute_code(self, code, silent=False):
460
+ """Execute code in the current console"""
461
+ console = self.get_current_console()
462
+ if console:
463
+ if silent:
464
+ console.execute_silently(code)
465
+ else:
466
+ console.execute_code(code)
467
+
468
+ def execute_file(self, filepath):
469
+ """Execute a file in the current console"""
470
+ console = self.get_current_console()
471
+ if console:
472
+ console.execute_file(filepath)
473
+
474
+ def change_directory(self, directory):
475
+ """Change working directory of the current console"""
476
+ console = self.get_current_console()
477
+ if console:
478
+ return console.change_directory(directory)
479
+ return False
480
+
481
+ def handle_execution_complete(self, output, result):
482
+ """Handle execution complete signal from a console tab"""
483
+ self.execution_complete.emit(output, result)
484
+
485
+ def handle_workspace_updated(self, dict):
486
+ """Handle workspace updated signal from a console tab"""
487
+ self.workspace_updated.emit(dict)