sqlshell 0.2.3__py3-none-any.whl → 0.3.0__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.
Potentially problematic release.
This version of sqlshell might be problematic. Click here for more details.
- sqlshell/__init__.py +34 -4
- sqlshell/db/__init__.py +2 -1
- sqlshell/db/database_manager.py +336 -23
- sqlshell/db/export_manager.py +188 -0
- sqlshell/editor_integration.py +127 -0
- sqlshell/execution_handler.py +421 -0
- sqlshell/main.py +570 -140
- sqlshell/query_tab.py +592 -7
- sqlshell/ui/filter_header.py +22 -1
- sqlshell/utils/profile_column.py +1586 -170
- sqlshell/utils/profile_foreign_keys.py +103 -11
- sqlshell/utils/profile_ohe.py +631 -0
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.0.dist-info}/METADATA +126 -7
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.0.dist-info}/RECORD +17 -13
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.0.dist-info}/WHEEL +1 -1
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.0.dist-info}/entry_points.txt +0 -0
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration module to add F5/F9 execution functionality to SQLEditor.
|
|
3
|
+
This module provides a clean way to enhance the editor without modifying the original code.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from PyQt6.QtCore import Qt
|
|
7
|
+
from PyQt6.QtWidgets import QPlainTextEdit
|
|
8
|
+
from .execution_handler import SQLExecutionHandler, ExecutionKeyHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EditorExecutionIntegration:
|
|
12
|
+
"""
|
|
13
|
+
Integration class to add F5/F9 execution functionality to SQLEditor.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, editor: QPlainTextEdit, execute_callback=None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the integration.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
editor: The SQLEditor instance
|
|
22
|
+
execute_callback: Function to call to execute queries
|
|
23
|
+
"""
|
|
24
|
+
self.editor = editor
|
|
25
|
+
self.execution_handler = SQLExecutionHandler(execute_callback)
|
|
26
|
+
self.key_handler = ExecutionKeyHandler(self.execution_handler)
|
|
27
|
+
|
|
28
|
+
# Store original keyPressEvent to preserve existing functionality
|
|
29
|
+
self.original_key_press_event = editor.keyPressEvent
|
|
30
|
+
|
|
31
|
+
# Replace keyPressEvent with our enhanced version
|
|
32
|
+
editor.keyPressEvent = self.enhanced_key_press_event
|
|
33
|
+
|
|
34
|
+
def set_execute_callback(self, callback):
|
|
35
|
+
"""Set the execution callback function."""
|
|
36
|
+
self.execution_handler.set_execute_callback(callback)
|
|
37
|
+
|
|
38
|
+
def enhanced_key_press_event(self, event):
|
|
39
|
+
"""Enhanced keyPressEvent that handles F5/F9 while preserving original functionality."""
|
|
40
|
+
|
|
41
|
+
# First, try to handle F5/F9 keys
|
|
42
|
+
if self.key_handler.handle_key_press(self.editor, event.key(), event.modifiers()):
|
|
43
|
+
# Key was handled by our execution handler
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# If not F5/F9, use the original keyPressEvent
|
|
47
|
+
self.original_key_press_event(event)
|
|
48
|
+
|
|
49
|
+
def execute_all_statements(self):
|
|
50
|
+
"""Execute all statements in the editor (F5 functionality)."""
|
|
51
|
+
try:
|
|
52
|
+
return self.execution_handler.execute_from_editor(self.editor, "all")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Error executing all statements: {e}")
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def execute_current_statement(self):
|
|
58
|
+
"""Execute the current statement (F9 functionality)."""
|
|
59
|
+
try:
|
|
60
|
+
return self.execution_handler.execute_from_editor(self.editor, "current")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Error executing current statement: {e}")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def get_current_statement_info(self):
|
|
66
|
+
"""Get information about the current statement at cursor position."""
|
|
67
|
+
text = self.editor.toPlainText()
|
|
68
|
+
cursor = self.editor.textCursor()
|
|
69
|
+
cursor_position = cursor.position()
|
|
70
|
+
|
|
71
|
+
current_stmt = self.execution_handler.get_current_statement(text, cursor_position)
|
|
72
|
+
if current_stmt:
|
|
73
|
+
stmt_text, start_pos, end_pos = current_stmt
|
|
74
|
+
return {
|
|
75
|
+
'text': stmt_text,
|
|
76
|
+
'start': start_pos,
|
|
77
|
+
'end': end_pos,
|
|
78
|
+
'cursor_position': cursor_position
|
|
79
|
+
}
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def get_all_statements_info(self):
|
|
83
|
+
"""Get information about all statements in the editor."""
|
|
84
|
+
text = self.editor.toPlainText()
|
|
85
|
+
statements = self.execution_handler.parse_sql_statements(text)
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
'text': stmt_text,
|
|
90
|
+
'start': start_pos,
|
|
91
|
+
'end': end_pos,
|
|
92
|
+
'index': i
|
|
93
|
+
}
|
|
94
|
+
for i, (stmt_text, start_pos, end_pos) in enumerate(statements)
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def integrate_execution_functionality(editor: QPlainTextEdit, execute_callback=None):
|
|
99
|
+
"""
|
|
100
|
+
Convenience function to integrate F5/F9 execution functionality into an editor.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
editor: The SQLEditor instance
|
|
104
|
+
execute_callback: Function to call to execute queries
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
EditorExecutionIntegration instance for further customization
|
|
108
|
+
"""
|
|
109
|
+
integration = EditorExecutionIntegration(editor, execute_callback)
|
|
110
|
+
|
|
111
|
+
# Store the integration instance on the editor for later access
|
|
112
|
+
editor._execution_integration = integration
|
|
113
|
+
|
|
114
|
+
return integration
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_execution_integration(editor: QPlainTextEdit):
|
|
118
|
+
"""
|
|
119
|
+
Get the execution integration instance from an editor.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
editor: The SQLEditor instance
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
EditorExecutionIntegration instance or None if not integrated
|
|
126
|
+
"""
|
|
127
|
+
return getattr(editor, '_execution_integration', None)
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modular SQL execution handler for SQLShell.
|
|
3
|
+
Provides F5 (execute all statements) and F9 (execute current statement) functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import List, Tuple, Optional, Callable
|
|
8
|
+
from PyQt6.QtWidgets import QPlainTextEdit
|
|
9
|
+
from PyQt6.QtGui import QTextCursor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SQLExecutionHandler:
|
|
13
|
+
"""
|
|
14
|
+
Handles SQL statement parsing and execution for different execution modes.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- F5: Execute all statements in the editor
|
|
18
|
+
- F9: Execute the current statement (statement containing cursor)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, execute_callback: Callable[[str], None] = None):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the execution handler.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
execute_callback: Function to call to execute a SQL query string
|
|
27
|
+
"""
|
|
28
|
+
self.execute_callback = execute_callback
|
|
29
|
+
|
|
30
|
+
def set_execute_callback(self, callback: Callable[[str], None]):
|
|
31
|
+
"""Set the callback function for executing queries."""
|
|
32
|
+
self.execute_callback = callback
|
|
33
|
+
|
|
34
|
+
def parse_sql_statements(self, text: str) -> List[Tuple[str, int, int]]:
|
|
35
|
+
"""
|
|
36
|
+
Parse SQL text into individual statements.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
text: SQL text to parse
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List of tuples: (statement_text, start_position, end_position)
|
|
43
|
+
"""
|
|
44
|
+
statements = []
|
|
45
|
+
if not text.strip():
|
|
46
|
+
return statements
|
|
47
|
+
|
|
48
|
+
# Create position mapping between original text and comment-removed text
|
|
49
|
+
original_to_clean, clean_to_original = self._create_position_mapping(text)
|
|
50
|
+
|
|
51
|
+
# Remove comments while preserving position information
|
|
52
|
+
text_without_comments = self._remove_comments(text)
|
|
53
|
+
|
|
54
|
+
# Split by semicolons, but be smart about it
|
|
55
|
+
# Handle string literals and quoted identifiers properly
|
|
56
|
+
current_statement = ""
|
|
57
|
+
statement_start_pos = 0
|
|
58
|
+
i = 0
|
|
59
|
+
in_string = False
|
|
60
|
+
string_char = None
|
|
61
|
+
escaped = False
|
|
62
|
+
|
|
63
|
+
# Find the first non-whitespace character to start
|
|
64
|
+
while statement_start_pos < len(text_without_comments) and text_without_comments[statement_start_pos].isspace():
|
|
65
|
+
statement_start_pos += 1
|
|
66
|
+
|
|
67
|
+
while i < len(text_without_comments):
|
|
68
|
+
char = text_without_comments[i]
|
|
69
|
+
|
|
70
|
+
if escaped:
|
|
71
|
+
escaped = False
|
|
72
|
+
current_statement += char
|
|
73
|
+
i += 1
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if char == '\\':
|
|
77
|
+
escaped = True
|
|
78
|
+
current_statement += char
|
|
79
|
+
i += 1
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
if not in_string and char in ('"', "'"):
|
|
83
|
+
in_string = True
|
|
84
|
+
string_char = char
|
|
85
|
+
current_statement += char
|
|
86
|
+
elif in_string and char == string_char:
|
|
87
|
+
# Check for escaped quotes (double quotes)
|
|
88
|
+
if i + 1 < len(text_without_comments) and text_without_comments[i + 1] == string_char:
|
|
89
|
+
current_statement += char + string_char
|
|
90
|
+
i += 2
|
|
91
|
+
continue
|
|
92
|
+
else:
|
|
93
|
+
in_string = False
|
|
94
|
+
string_char = None
|
|
95
|
+
current_statement += char
|
|
96
|
+
elif not in_string and char == ';':
|
|
97
|
+
# End of statement
|
|
98
|
+
statement_text = current_statement.strip()
|
|
99
|
+
if statement_text:
|
|
100
|
+
# Convert clean text positions back to original text positions
|
|
101
|
+
original_start = clean_to_original.get(statement_start_pos, statement_start_pos)
|
|
102
|
+
original_end = clean_to_original.get(i + 1, len(text))
|
|
103
|
+
statements.append((statement_text, original_start, original_end))
|
|
104
|
+
|
|
105
|
+
# Find next non-whitespace character for next statement start
|
|
106
|
+
j = i + 1
|
|
107
|
+
while j < len(text_without_comments) and text_without_comments[j].isspace():
|
|
108
|
+
j += 1
|
|
109
|
+
statement_start_pos = j
|
|
110
|
+
current_statement = ""
|
|
111
|
+
else:
|
|
112
|
+
current_statement += char
|
|
113
|
+
|
|
114
|
+
i += 1
|
|
115
|
+
|
|
116
|
+
# Handle the last statement if it doesn't end with semicolon
|
|
117
|
+
statement_text = current_statement.strip()
|
|
118
|
+
if statement_text:
|
|
119
|
+
original_start = clean_to_original.get(statement_start_pos, statement_start_pos)
|
|
120
|
+
original_end = len(text)
|
|
121
|
+
statements.append((statement_text, original_start, original_end))
|
|
122
|
+
|
|
123
|
+
return statements
|
|
124
|
+
|
|
125
|
+
def _create_position_mapping(self, text: str) -> tuple[dict, dict]:
|
|
126
|
+
"""
|
|
127
|
+
Create bidirectional position mapping between original text and comment-removed text.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
text: Original SQL text
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Tuple of (original_to_clean, clean_to_original) position mappings
|
|
134
|
+
"""
|
|
135
|
+
original_to_clean = {}
|
|
136
|
+
clean_to_original = {}
|
|
137
|
+
clean_pos = 0
|
|
138
|
+
i = 0
|
|
139
|
+
in_string = False
|
|
140
|
+
string_char = None
|
|
141
|
+
|
|
142
|
+
while i < len(text):
|
|
143
|
+
char = text[i]
|
|
144
|
+
|
|
145
|
+
if not in_string:
|
|
146
|
+
# Check for string start
|
|
147
|
+
if char in ('"', "'"):
|
|
148
|
+
in_string = True
|
|
149
|
+
string_char = char
|
|
150
|
+
original_to_clean[i] = clean_pos
|
|
151
|
+
clean_to_original[clean_pos] = i
|
|
152
|
+
clean_pos += 1
|
|
153
|
+
# Check for line comment
|
|
154
|
+
elif char == '-' and i + 1 < len(text) and text[i + 1] == '-':
|
|
155
|
+
# Skip to end of line, but preserve newline
|
|
156
|
+
while i < len(text) and text[i] != '\n':
|
|
157
|
+
original_to_clean[i] = clean_pos
|
|
158
|
+
i += 1
|
|
159
|
+
if i < len(text): # Add the newline
|
|
160
|
+
original_to_clean[i] = clean_pos
|
|
161
|
+
clean_to_original[clean_pos] = i
|
|
162
|
+
clean_pos += 1
|
|
163
|
+
continue
|
|
164
|
+
# Check for block comment
|
|
165
|
+
elif char == '/' and i + 1 < len(text) and text[i + 1] == '*':
|
|
166
|
+
# Skip to end of block comment
|
|
167
|
+
start_i = i
|
|
168
|
+
i += 2
|
|
169
|
+
while i + 1 < len(text):
|
|
170
|
+
original_to_clean[i] = clean_pos
|
|
171
|
+
if text[i] == '*' and text[i + 1] == '/':
|
|
172
|
+
original_to_clean[i + 1] = clean_pos
|
|
173
|
+
i += 2
|
|
174
|
+
break
|
|
175
|
+
i += 1
|
|
176
|
+
continue
|
|
177
|
+
else:
|
|
178
|
+
original_to_clean[i] = clean_pos
|
|
179
|
+
clean_to_original[clean_pos] = i
|
|
180
|
+
clean_pos += 1
|
|
181
|
+
else:
|
|
182
|
+
# In string
|
|
183
|
+
if char == string_char:
|
|
184
|
+
# Check for escaped quote
|
|
185
|
+
if i + 1 < len(text) and text[i + 1] == string_char:
|
|
186
|
+
original_to_clean[i] = clean_pos
|
|
187
|
+
original_to_clean[i + 1] = clean_pos + 1
|
|
188
|
+
clean_to_original[clean_pos] = i
|
|
189
|
+
clean_to_original[clean_pos + 1] = i + 1
|
|
190
|
+
clean_pos += 2
|
|
191
|
+
i += 2
|
|
192
|
+
continue
|
|
193
|
+
else:
|
|
194
|
+
in_string = False
|
|
195
|
+
string_char = None
|
|
196
|
+
original_to_clean[i] = clean_pos
|
|
197
|
+
clean_to_original[clean_pos] = i
|
|
198
|
+
clean_pos += 1
|
|
199
|
+
else:
|
|
200
|
+
original_to_clean[i] = clean_pos
|
|
201
|
+
clean_to_original[clean_pos] = i
|
|
202
|
+
clean_pos += 1
|
|
203
|
+
|
|
204
|
+
i += 1
|
|
205
|
+
|
|
206
|
+
return original_to_clean, clean_to_original
|
|
207
|
+
|
|
208
|
+
def _remove_comments(self, text: str) -> str:
|
|
209
|
+
"""
|
|
210
|
+
Remove SQL comments while preserving string literals.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
text: SQL text
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Text with comments removed
|
|
217
|
+
"""
|
|
218
|
+
result = []
|
|
219
|
+
i = 0
|
|
220
|
+
in_string = False
|
|
221
|
+
string_char = None
|
|
222
|
+
|
|
223
|
+
while i < len(text):
|
|
224
|
+
char = text[i]
|
|
225
|
+
|
|
226
|
+
if not in_string:
|
|
227
|
+
# Check for string start
|
|
228
|
+
if char in ('"', "'"):
|
|
229
|
+
in_string = True
|
|
230
|
+
string_char = char
|
|
231
|
+
result.append(char)
|
|
232
|
+
# Check for line comment
|
|
233
|
+
elif char == '-' and i + 1 < len(text) and text[i + 1] == '-':
|
|
234
|
+
# Skip to end of line
|
|
235
|
+
while i < len(text) and text[i] != '\n':
|
|
236
|
+
i += 1
|
|
237
|
+
if i < len(text):
|
|
238
|
+
result.append('\n') # Preserve newline
|
|
239
|
+
continue
|
|
240
|
+
# Check for block comment
|
|
241
|
+
elif char == '/' and i + 1 < len(text) and text[i + 1] == '*':
|
|
242
|
+
# Skip to end of block comment
|
|
243
|
+
i += 2
|
|
244
|
+
while i + 1 < len(text):
|
|
245
|
+
if text[i] == '*' and text[i + 1] == '/':
|
|
246
|
+
i += 2
|
|
247
|
+
break
|
|
248
|
+
i += 1
|
|
249
|
+
continue
|
|
250
|
+
else:
|
|
251
|
+
result.append(char)
|
|
252
|
+
else:
|
|
253
|
+
# In string
|
|
254
|
+
if char == string_char:
|
|
255
|
+
# Check for escaped quote
|
|
256
|
+
if i + 1 < len(text) and text[i + 1] == string_char:
|
|
257
|
+
result.append(char + string_char)
|
|
258
|
+
i += 2
|
|
259
|
+
continue
|
|
260
|
+
else:
|
|
261
|
+
in_string = False
|
|
262
|
+
string_char = None
|
|
263
|
+
result.append(char)
|
|
264
|
+
else:
|
|
265
|
+
result.append(char)
|
|
266
|
+
|
|
267
|
+
i += 1
|
|
268
|
+
|
|
269
|
+
return ''.join(result)
|
|
270
|
+
|
|
271
|
+
def get_current_statement(self, text: str, cursor_position: int) -> Optional[Tuple[str, int, int]]:
|
|
272
|
+
"""
|
|
273
|
+
Get the statement that contains the cursor position.
|
|
274
|
+
If cursor is not inside any statement, returns the closest statement before the cursor.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
text: SQL text
|
|
278
|
+
cursor_position: Current cursor position
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Tuple of (statement_text, start_position, end_position) or None
|
|
282
|
+
"""
|
|
283
|
+
statements = self.parse_sql_statements(text)
|
|
284
|
+
|
|
285
|
+
# First try to find a statement containing the cursor
|
|
286
|
+
for statement_text, start_pos, end_pos in statements:
|
|
287
|
+
if start_pos <= cursor_position <= end_pos:
|
|
288
|
+
return (statement_text, start_pos, end_pos)
|
|
289
|
+
|
|
290
|
+
# If no statement contains the cursor, find the closest statement before the cursor
|
|
291
|
+
closest_statement = None
|
|
292
|
+
closest_distance = float('inf')
|
|
293
|
+
|
|
294
|
+
for statement_text, start_pos, end_pos in statements:
|
|
295
|
+
if end_pos <= cursor_position: # Statement is before cursor
|
|
296
|
+
distance = cursor_position - end_pos
|
|
297
|
+
if distance < closest_distance:
|
|
298
|
+
closest_distance = distance
|
|
299
|
+
closest_statement = (statement_text, start_pos, end_pos)
|
|
300
|
+
|
|
301
|
+
return closest_statement
|
|
302
|
+
|
|
303
|
+
def execute_all_statements(self, text: str) -> List[str]:
|
|
304
|
+
"""
|
|
305
|
+
Execute all statements in the text (F5 functionality).
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
text: SQL text containing one or more statements
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
List of executed statement texts
|
|
312
|
+
"""
|
|
313
|
+
if not self.execute_callback:
|
|
314
|
+
raise ValueError("No execute callback set")
|
|
315
|
+
|
|
316
|
+
statements = self.parse_sql_statements(text)
|
|
317
|
+
executed_statements = []
|
|
318
|
+
|
|
319
|
+
for statement_text, _, _ in statements:
|
|
320
|
+
if statement_text.strip():
|
|
321
|
+
self.execute_callback(statement_text)
|
|
322
|
+
executed_statements.append(statement_text)
|
|
323
|
+
|
|
324
|
+
return executed_statements
|
|
325
|
+
|
|
326
|
+
def execute_current_statement(self, text: str, cursor_position: int) -> Optional[str]:
|
|
327
|
+
"""
|
|
328
|
+
Execute the statement containing the cursor (F9 functionality).
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
text: SQL text
|
|
332
|
+
cursor_position: Current cursor position
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Executed statement text or None if no statement found
|
|
336
|
+
"""
|
|
337
|
+
if not self.execute_callback:
|
|
338
|
+
raise ValueError("No execute callback set")
|
|
339
|
+
|
|
340
|
+
current_statement = self.get_current_statement(text, cursor_position)
|
|
341
|
+
|
|
342
|
+
if current_statement:
|
|
343
|
+
statement_text, _, _ = current_statement
|
|
344
|
+
if statement_text.strip():
|
|
345
|
+
self.execute_callback(statement_text)
|
|
346
|
+
return statement_text
|
|
347
|
+
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def execute_from_editor(self, editor: QPlainTextEdit, mode: str = "current") -> Optional[str]:
|
|
351
|
+
"""
|
|
352
|
+
Execute statements from a QPlainTextEdit widget.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
editor: The text editor widget
|
|
356
|
+
mode: "current" for F9 or "all" for F5
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Executed statement(s) or None
|
|
360
|
+
"""
|
|
361
|
+
text = editor.toPlainText()
|
|
362
|
+
cursor = editor.textCursor()
|
|
363
|
+
cursor_position = cursor.position()
|
|
364
|
+
|
|
365
|
+
if mode == "all":
|
|
366
|
+
executed = self.execute_all_statements(text)
|
|
367
|
+
return "; ".join(executed) if executed else None
|
|
368
|
+
elif mode == "current":
|
|
369
|
+
return self.execute_current_statement(text, cursor_position)
|
|
370
|
+
else:
|
|
371
|
+
raise ValueError(f"Invalid mode: {mode}. Use 'current' or 'all'")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class ExecutionKeyHandler:
|
|
375
|
+
"""
|
|
376
|
+
Key handler for F5 and F9 execution functionality.
|
|
377
|
+
Integrates with QPlainTextEdit widgets.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
def __init__(self, execution_handler: SQLExecutionHandler):
|
|
381
|
+
"""
|
|
382
|
+
Initialize the key handler.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
execution_handler: The execution handler to use
|
|
386
|
+
"""
|
|
387
|
+
self.execution_handler = execution_handler
|
|
388
|
+
|
|
389
|
+
def handle_key_press(self, editor: QPlainTextEdit, key: int, modifiers: int) -> bool:
|
|
390
|
+
"""
|
|
391
|
+
Handle key press events for execution shortcuts.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
editor: The text editor widget
|
|
395
|
+
key: Key code
|
|
396
|
+
modifiers: Keyboard modifiers
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
True if the key was handled, False otherwise
|
|
400
|
+
"""
|
|
401
|
+
from PyQt6.QtCore import Qt
|
|
402
|
+
|
|
403
|
+
# F5 - Execute all statements
|
|
404
|
+
if key == Qt.Key.Key_F5:
|
|
405
|
+
try:
|
|
406
|
+
executed = self.execution_handler.execute_from_editor(editor, "all")
|
|
407
|
+
return True
|
|
408
|
+
except Exception as e:
|
|
409
|
+
print(f"Error executing all statements: {e}")
|
|
410
|
+
return True
|
|
411
|
+
|
|
412
|
+
# F9 - Execute current statement
|
|
413
|
+
elif key == Qt.Key.Key_F9:
|
|
414
|
+
try:
|
|
415
|
+
executed = self.execution_handler.execute_from_editor(editor, "current")
|
|
416
|
+
return True
|
|
417
|
+
except Exception as e:
|
|
418
|
+
print(f"Error executing current statement: {e}")
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
return False
|