7 Module implementing a graphical Python shell. |
7 Module implementing a graphical Python shell. |
8 """ |
8 """ |
9 |
9 |
10 import sys |
10 import sys |
11 import re |
11 import re |
12 |
12 import contextlib |
13 from enum import Enum |
13 import enum |
14 |
14 |
15 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QFileInfo, Qt, QEvent |
15 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QFileInfo, Qt, QEvent |
16 from PyQt5.QtGui import QClipboard, QPalette, QFont |
16 from PyQt5.QtGui import QClipboard, QPalette, QFont |
17 from PyQt5.QtWidgets import ( |
17 from PyQt5.QtWidgets import ( |
18 QDialog, QInputDialog, QApplication, QMenu, QWidget, QHBoxLayout, |
18 QDialog, QInputDialog, QApplication, QMenu, QWidget, QHBoxLayout, |
50 @param horizontal flag indicating a horizontal layout |
50 @param horizontal flag indicating a horizontal layout |
51 @type bool |
51 @type bool |
52 @param parent parent widget |
52 @param parent parent widget |
53 @type QWidget |
53 @type QWidget |
54 """ |
54 """ |
55 super(ShellAssembly, self).__init__(parent) |
55 super().__init__(parent) |
56 |
56 |
57 self.__shell = Shell(dbs, vm, project, False, self) |
57 self.__shell = Shell(dbs, vm, project, False, self) |
58 |
58 |
59 from UI.SearchWidget import SearchWidget |
59 from UI.SearchWidget import SearchWidget |
60 self.__searchWidget = SearchWidget(self.__shell, self, horizontal) |
60 self.__searchWidget = SearchWidget(self.__shell, self, horizontal) |
240 dbs.clientSyntaxError.connect(self.__clientSyntaxError) |
240 dbs.clientSyntaxError.connect(self.__clientSyntaxError) |
241 dbs.clientSignal.connect(self.__clientSignal) |
241 dbs.clientSignal.connect(self.__clientSignal) |
242 dbs.mainClientExit.connect(self.__writePrompt) |
242 dbs.mainClientExit.connect(self.__writePrompt) |
243 self.dbs = dbs |
243 self.dbs = dbs |
244 |
244 |
245 self.__debugUI = None |
245 # will register a method to get the debugger ID to work with |
|
246 self.__getSelectedDebuggerId = None |
246 |
247 |
247 # Make sure we have prompts. |
248 # Make sure we have prompts. |
248 if self.passive: |
249 if self.passive: |
249 sys.ps1 = self.tr("Passive >>> ") |
250 sys.ps1 = self.tr("Passive >>> ") |
250 else: |
251 else: |
481 """ |
482 """ |
482 self.setTabWidth(Preferences.getEditor("TabWidth")) |
483 self.setTabWidth(Preferences.getEditor("TabWidth")) |
483 if Preferences.getEditor("ShowWhitespace"): |
484 if Preferences.getEditor("ShowWhitespace"): |
484 self.setWhitespaceVisibility( |
485 self.setWhitespaceVisibility( |
485 QsciScintilla.WhitespaceVisibility.WsVisible) |
486 QsciScintilla.WhitespaceVisibility.WsVisible) |
486 try: |
487 with contextlib.suppress(AttributeError): |
487 self.setWhitespaceForegroundColor( |
488 self.setWhitespaceForegroundColor( |
488 Preferences.getEditorColour("WhitespaceForeground")) |
489 Preferences.getEditorColour("WhitespaceForeground")) |
489 self.setWhitespaceBackgroundColor( |
490 self.setWhitespaceBackgroundColor( |
490 Preferences.getEditorColour("WhitespaceBackground")) |
491 Preferences.getEditorColour("WhitespaceBackground")) |
491 self.setWhitespaceSize( |
492 self.setWhitespaceSize( |
492 Preferences.getEditor("WhitespaceSize")) |
493 Preferences.getEditor("WhitespaceSize")) |
493 except AttributeError: |
|
494 # QScintilla before 2.5 doesn't support this |
|
495 pass |
|
496 else: |
494 else: |
497 self.setWhitespaceVisibility( |
495 self.setWhitespaceVisibility( |
498 QsciScintilla.WhitespaceVisibility.WsInvisible) |
496 QsciScintilla.WhitespaceVisibility.WsInvisible) |
499 self.setEolVisibility(Preferences.getEditor("ShowEOL")) |
497 self.setEolVisibility(Preferences.getEditor("ShowEOL")) |
500 if Preferences.getEditor("BraceHighlighting"): |
498 if Preferences.getEditor("BraceHighlighting"): |
612 Public method to set the debugger UI. |
610 Public method to set the debugger UI. |
613 |
611 |
614 @param ui reference to the debugger UI object (DebugUI) |
612 @param ui reference to the debugger UI object (DebugUI) |
615 """ |
613 """ |
616 ui.exceptionInterrupt.connect(self.__writePrompt) |
614 ui.exceptionInterrupt.connect(self.__writePrompt) |
617 self.__debugUI = ui |
615 self.registerDebuggerIdMethod(ui.getSelectedDebuggerId) |
|
616 |
|
617 def registerDebuggerIdMethod(self, method): |
|
618 """ |
|
619 Public method to register a method to get the debugger ID to send data |
|
620 to. |
|
621 |
|
622 @param method reference to the method |
|
623 @type function |
|
624 """ |
|
625 self.__getSelectedDebuggerId = method |
618 |
626 |
619 def __initialise(self): |
627 def __initialise(self): |
620 """ |
628 """ |
621 Private method to get ready for a new remote interpreter. |
629 Private method to get ready for a new remote interpreter. |
622 """ |
630 """ |
688 self.__histidx = index |
696 self.__histidx = index |
689 if self.__histidx >= len(self.__history): |
697 if self.__histidx >= len(self.__history): |
690 self.__histidx = -1 |
698 self.__histidx = -1 |
691 if ( |
699 if ( |
692 self.clientType and |
700 self.clientType and |
693 self.__historyStyle == ShellHistoryStyle.WindowsStyle |
701 self.__historyStyle == ShellHistoryStyle.WINDOWSSTYLE |
694 ): |
702 ): |
695 Preferences.Prefs.settings.setValue( |
703 Preferences.Prefs.settings.setValue( |
696 "Shell/HistoryIndexes/" + self.clientType, self.__histidx) |
704 "Shell/HistoryIndexes/" + self.clientType, self.__histidx) |
697 |
705 |
698 def __isHistoryIndexValid(self): |
706 def __isHistoryIndexValid(self): |
840 @param platform platform of the remote interpreter |
848 @param platform platform of the remote interpreter |
841 @type str |
849 @type str |
842 @param venvName name of the virtual environment |
850 @param venvName name of the virtual environment |
843 @type str |
851 @type str |
844 """ |
852 """ |
845 super(Shell, self).clear() |
853 super().clear() |
846 if self.passive and not self.dbs.isConnected(): |
854 if self.passive and not self.dbs.isConnected(): |
847 self.__write(self.tr('Passive Debug Mode')) |
855 self.__write(self.tr('Passive Debug Mode')) |
848 self.__write(self.tr('\nNot connected')) |
856 self.__write(self.tr('\nNot connected')) |
849 else: |
857 else: |
850 self.__currentVenv = venvName |
858 self.__currentVenv = venvName |
890 """ |
898 """ |
891 self .__clientError() |
899 self .__clientError() |
892 |
900 |
893 if ( |
901 if ( |
894 not self.__windowed and |
902 not self.__windowed and |
895 Preferences.getDebugger("ShowExceptionInShell") |
903 Preferences.getDebugger("ShowExceptionInShell") and |
|
904 exceptionType |
896 ): |
905 ): |
897 if exceptionType: |
906 if stackTrace: |
898 if stackTrace: |
907 self.__write( |
899 self.__write( |
908 self.tr('Exception "{0}"\n{1}\nFile: {2}, Line: {3}\n') |
900 self.tr('Exception "{0}"\n{1}\nFile: {2}, Line: {3}\n') |
909 .format( |
901 .format( |
910 exceptionType, |
902 exceptionType, |
911 exceptionMessage, |
903 exceptionMessage, |
912 stackTrace[0][0], |
904 stackTrace[0][0], |
913 stackTrace[0][1] |
905 stackTrace[0][1] |
|
906 ) |
|
907 ) |
914 ) |
908 else: |
915 ) |
909 self.__write( |
916 else: |
910 self.tr('Exception "{0}"\n{1}\n') |
917 self.__write( |
911 .format( |
918 self.tr('Exception "{0}"\n{1}\n') |
912 exceptionType, |
919 .format( |
913 exceptionMessage) |
920 exceptionType, |
914 ) |
921 exceptionMessage) |
|
922 ) |
915 |
923 |
916 def __clientSyntaxError(self, message, filename, lineNo, characterNo): |
924 def __clientSyntaxError(self, message, filename, lineNo, characterNo): |
917 """ |
925 """ |
918 Private method to handle a syntax error in the debugged program. |
926 Private method to handle a syntax error in the debugged program. |
919 |
927 |
1334 line, col = self.__getEndPos() |
1342 line, col = self.__getEndPos() |
1335 self.setCursorPosition(line, col) |
1343 self.setCursorPosition(line, col) |
1336 self.prline, self.prcol = self.getCursorPosition() |
1344 self.prline, self.prcol = self.getCursorPosition() |
1337 if self.__echoInput: |
1345 if self.__echoInput: |
1338 ac = self.isListActive() |
1346 ac = self.isListActive() |
1339 super(Shell, self).keyPressEvent(ev) |
1347 super().keyPressEvent(ev) |
1340 self.incrementalSearchActive = True |
1348 self.incrementalSearchActive = True |
1341 if ac and self.racEnabled: |
1349 if ac and self.racEnabled: |
1342 self.dbs.remoteCompletion( |
1350 self.dbs.remoteCompletion( |
1343 self.__debugUI.getSelectedDebuggerId(), |
1351 self.__getSelectedDebuggerId(), |
1344 self.completionText + txt |
1352 self.completionText + txt |
1345 ) |
1353 ) |
1346 else: |
1354 else: |
1347 self.__insertTextNoEcho(txt) |
1355 self.__insertTextNoEcho(txt) |
1348 else: |
1356 else: |
1373 buf = buf.replace(sys.ps2, "") |
1381 buf = buf.replace(sys.ps2, "") |
1374 if self.inContinue and not buf[:index - len(sys.ps2)].strip(): |
1382 if self.inContinue and not buf[:index - len(sys.ps2)].strip(): |
1375 self.SendScintilla(cmd) |
1383 self.SendScintilla(cmd) |
1376 elif self.racEnabled: |
1384 elif self.racEnabled: |
1377 self.dbs.remoteCompletion( |
1385 self.dbs.remoteCompletion( |
1378 self.__debugUI.getSelectedDebuggerId(), |
1386 self.__getSelectedDebuggerId(), |
1379 buf |
1387 buf |
1380 ) |
1388 ) |
1381 |
1389 |
1382 def __QScintillaLeftDeleteCommand(self, method): |
1390 def __QScintillaLeftDeleteCommand(self, method): |
1383 """ |
1391 """ |
1797 if cmd != "" and ( |
1805 if cmd != "" and ( |
1798 len(self.__history) == 0 or self.__history[-1] != cmd): |
1806 len(self.__history) == 0 or self.__history[-1] != cmd): |
1799 if len(self.__history) == self.__maxHistoryEntries: |
1807 if len(self.__history) == self.__maxHistoryEntries: |
1800 del self.__history[0] |
1808 del self.__history[0] |
1801 self.__history.append(cmd) |
1809 self.__history.append(cmd) |
1802 if self.__historyStyle == ShellHistoryStyle.LinuxStyle: |
1810 if self.__historyStyle == ShellHistoryStyle.LINUXSTYLE: |
1803 self.__setHistoryIndex(index=-1) |
1811 self.__setHistoryIndex(index=-1) |
1804 elif self.__historyStyle == ShellHistoryStyle.WindowsStyle: |
1812 elif self.__historyStyle == ShellHistoryStyle.WINDOWSSTYLE: |
1805 if historyIndex is None: |
1813 if historyIndex is None: |
1806 if ( |
1814 if ( |
1807 self.__histidx - 1 > 0 and |
1815 self.__histidx - 1 > 0 and |
1808 cmd != self.__history[self.__histidx - 1] |
1816 cmd != self.__history[self.__histidx - 1] |
1809 ): |
1817 ): |
1887 # call main window quit() |
1895 # call main window quit() |
1888 self.vm.quit() |
1896 self.vm.quit() |
1889 return |
1897 return |
1890 else: |
1898 else: |
1891 self.dbs.remoteStatement( |
1899 self.dbs.remoteStatement( |
1892 self.__debugUI.getSelectedDebuggerId(), cmd) |
1900 self.__getSelectedDebuggerId(), cmd) |
1893 while self.inCommandExecution: |
1901 while self.inCommandExecution: |
1894 try: |
1902 with contextlib.suppress(KeyboardInterrupt): |
1895 QApplication.processEvents() |
1903 QApplication.processEvents() |
1896 except KeyboardInterrupt: |
|
1897 pass |
|
1898 else: |
1904 else: |
1899 if not self.__echoInput: |
1905 if not self.__echoInput: |
1900 cmd = self.buff |
1906 cmd = self.buff |
1901 self.buff = "" |
1907 self.buff = "" |
1902 elif cmd: |
1908 elif cmd: |
2156 event.mimeData().hasText() |
2156 event.mimeData().hasText() |
2157 ) |
2157 ) |
2158 if self.inDragDrop: |
2158 if self.inDragDrop: |
2159 event.acceptProposedAction() |
2159 event.acceptProposedAction() |
2160 else: |
2160 else: |
2161 super(Shell, self).dragEnterEvent(event) |
2161 super().dragEnterEvent(event) |
2162 |
2162 |
2163 def dragMoveEvent(self, event): |
2163 def dragMoveEvent(self, event): |
2164 """ |
2164 """ |
2165 Protected method to handle the drag move event. |
2165 Protected method to handle the drag move event. |
2166 |
2166 |
2167 @param event the drag move event (QDragMoveEvent) |
2167 @param event the drag move event (QDragMoveEvent) |
2168 """ |
2168 """ |
2169 if self.inDragDrop: |
2169 if self.inDragDrop: |
2170 event.accept() |
2170 event.accept() |
2171 else: |
2171 else: |
2172 super(Shell, self).dragMoveEvent(event) |
2172 super().dragMoveEvent(event) |
2173 |
2173 |
2174 def dragLeaveEvent(self, event): |
2174 def dragLeaveEvent(self, event): |
2175 """ |
2175 """ |
2176 Protected method to handle the drag leave event. |
2176 Protected method to handle the drag leave event. |
2177 |
2177 |
2232 self.__searchNext, self.__searchNext) |
2232 self.__searchNext, self.__searchNext) |
2233 self.__searchPrevShortcut = QShortcut( |
2233 self.__searchPrevShortcut = QShortcut( |
2234 self.vm.searchPrevAct.shortcut(), self, |
2234 self.vm.searchPrevAct.shortcut(), self, |
2235 self.__searchPrev, self.__searchPrev) |
2235 self.__searchPrev, self.__searchPrev) |
2236 |
2236 |
2237 try: |
2237 with contextlib.suppress(AttributeError): |
2238 self.vm.editActGrp.setEnabled(False) |
2238 self.vm.editActGrp.setEnabled(False) |
2239 self.vm.editorActGrp.setEnabled(True) |
2239 self.vm.editorActGrp.setEnabled(True) |
2240 self.vm.copyActGrp.setEnabled(True) |
2240 self.vm.copyActGrp.setEnabled(True) |
2241 self.vm.viewActGrp.setEnabled(True) |
2241 self.vm.viewActGrp.setEnabled(True) |
2242 self.vm.searchActGrp.setEnabled(False) |
2242 self.vm.searchActGrp.setEnabled(False) |
2243 except AttributeError: |
|
2244 pass |
|
2245 if not self.__windowed: |
2243 if not self.__windowed: |
2246 self.__searchShortcut.setEnabled(True) |
2244 self.__searchShortcut.setEnabled(True) |
2247 self.__searchNextShortcut.setEnabled(True) |
2245 self.__searchNextShortcut.setEnabled(True) |
2248 self.__searchPrevShortcut.setEnabled(True) |
2246 self.__searchPrevShortcut.setEnabled(True) |
2249 self.setCaretWidth(self.caretWidth) |
2247 self.setCaretWidth(self.caretWidth) |
2250 self.setCursorFlashTime(QApplication.cursorFlashTime()) |
2248 self.setCursorFlashTime(QApplication.cursorFlashTime()) |
2251 |
2249 |
2252 super(Shell, self).focusInEvent(event) |
2250 super().focusInEvent(event) |
2253 |
2251 |
2254 def focusOutEvent(self, event): |
2252 def focusOutEvent(self, event): |
2255 """ |
2253 """ |
2256 Protected method called when the shell loses focus. |
2254 Protected method called when the shell loses focus. |
2257 |
2255 |
2258 @param event the event object (QFocusEvent) |
2256 @param event the event object (QFocusEvent) |
2259 """ |
2257 """ |
2260 try: |
2258 with contextlib.suppress(AttributeError): |
2261 self.vm.editorActGrp.setEnabled(False) |
2259 self.vm.editorActGrp.setEnabled(False) |
2262 except AttributeError: |
|
2263 pass |
|
2264 if not self.__windowed: |
2260 if not self.__windowed: |
2265 self.__searchShortcut.setEnabled(False) |
2261 self.__searchShortcut.setEnabled(False) |
2266 self.__searchNextShortcut.setEnabled(False) |
2262 self.__searchNextShortcut.setEnabled(False) |
2267 self.__searchPrevShortcut.setEnabled(False) |
2263 self.__searchPrevShortcut.setEnabled(False) |
2268 self.setCaretWidth(0) |
2264 self.setCaretWidth(0) |
2269 super(Shell, self).focusOutEvent(event) |
2265 super().focusOutEvent(event) |
2270 |
2266 |
2271 def insert(self, txt): |
2267 def insert(self, txt): |
2272 """ |
2268 """ |
2273 Public slot to insert text at the current cursor position. |
2269 Public slot to insert text at the current cursor position. |
2274 |
2270 |