src/eric7/QScintilla/Shell.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a graphical Python shell.
8 """
9
10 import sys
11 import re
12 import contextlib
13 import enum
14 import pathlib
15
16 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent
17 from PyQt6.QtGui import QClipboard, QPalette, QFont, QShortcut
18 from PyQt6.QtWidgets import (
19 QDialog, QInputDialog, QApplication, QMenu, QWidget, QHBoxLayout,
20 QVBoxLayout, QSizePolicy
21 )
22 from PyQt6.Qsci import QsciScintilla
23
24 from EricWidgets.EricApplication import ericApp
25 from EricWidgets import EricMessageBox, EricFileDialog
26
27 from .QsciScintillaCompat import QsciScintillaCompat
28
29 import Preferences
30 import Utilities
31
32 import UI.PixmapCache
33
34 from Debugger.DebugClientCapabilities import HasCompleter
35
36
37 class ShellAssembly(QWidget):
38 """
39 Class implementing the containing widget for the shell.
40 """
41 def __init__(self, dbs, vm, project, horizontal=True, parent=None):
42 """
43 Constructor
44
45 @param dbs reference to the debug server object
46 @type DebugServer
47 @param vm reference to the viewmanager object
48 @type ViewManager
49 @param project reference to the project object
50 @type Project
51 @param horizontal flag indicating a horizontal layout
52 @type bool
53 @param parent parent widget
54 @type QWidget
55 """
56 super().__init__(parent)
57
58 self.__shell = Shell(dbs, vm, project, False, self)
59
60 from UI.SearchWidget import SearchWidget
61 self.__searchWidget = SearchWidget(self.__shell, self, horizontal)
62 self.__searchWidget.setSizePolicy(QSizePolicy.Policy.Fixed,
63 QSizePolicy.Policy.Preferred)
64 self.__searchWidget.hide()
65
66 if horizontal:
67 self.__layout = QHBoxLayout(self)
68 else:
69 self.__layout = QVBoxLayout(self)
70 self.__layout.setContentsMargins(1, 1, 1, 1)
71 self.__layout.addWidget(self.__shell)
72 self.__layout.addWidget(self.__searchWidget)
73
74 self.__searchWidget.searchNext.connect(self.__shell.searchNext)
75 self.__searchWidget.searchPrevious.connect(self.__shell.searchPrev)
76 self.__shell.searchStringFound.connect(
77 self.__searchWidget.searchStringFound)
78
79 def showFind(self, txt=""):
80 """
81 Public method to display the search widget.
82
83 @param txt text to be shown in the combo (string)
84 """
85 self.__searchWidget.showFind(txt)
86
87 def shell(self):
88 """
89 Public method to get a reference to the shell widget.
90
91 @return reference to the shell widget (Shell)
92 """
93 return self.__shell
94
95
96 class ShellHistoryStyle(enum.Enum):
97 """
98 Class defining the shell history styles.
99 """
100 DISABLED = 0
101 LINUXSTYLE = 1
102 WINDOWSSTYLE = 2
103
104
105 class Shell(QsciScintillaCompat):
106 """
107 Class implementing a graphical Python shell.
108
109 A user can enter commands that are executed in the remote
110 Python interpreter.
111
112 @signal searchStringFound(bool) emitted to indicate the search
113 result
114 @signal historyStyleChanged(ShellHistoryStyle) emitted to indicate a
115 change of the history style
116 @signal queueText(str) emitted to queue some text for processing
117 @signal virtualEnvironmentChanged(str) emitted to signal the new virtual
118 environment of the shell
119 """
120 searchStringFound = pyqtSignal(bool)
121 historyStyleChanged = pyqtSignal(ShellHistoryStyle)
122 queueText = pyqtSignal(str)
123 virtualEnvironmentChanged = pyqtSignal(str)
124
125 def __init__(self, dbs, vm, project, windowedVariant, parent=None):
126 """
127 Constructor
128
129 @param dbs reference to the debug server object
130 @type DebugServer
131 @param vm reference to the viewmanager object
132 @type ViewManager
133 @param project reference to the project object
134 @type Project
135 @param windowedVariant flag indicating the shell window variant
136 @type bool
137 @param parent parent widget
138 @type QWidget
139 """
140 super().__init__(parent)
141 self.setUtf8(True)
142
143 self.vm = vm
144 self.__mainWindow = parent
145 self.__lastSearch = ()
146 self.__windowed = windowedVariant
147 self.__currentVenv = ""
148 self.__currentWorkingDirectory = ""
149
150 self.linesepRegExp = r"\r\n|\n|\r"
151
152 self.passive = ((not self.__windowed) and
153 Preferences.getDebugger("PassiveDbgEnabled"))
154 if self.passive:
155 self.setWindowTitle(self.tr('Shell - Passive'))
156 else:
157 self.setWindowTitle(self.tr('Shell'))
158
159 if self.__windowed:
160 self.setWhatsThis(self.tr(
161 """<b>The Shell Window</b>"""
162 """<p>You can use the cursor keys while entering commands."""
163 """ There is also a history of commands that can be recalled"""
164 """ using the up and down cursor keys while holding down the"""
165 """ Ctrl-key. This can be switched to just the up and down"""
166 """ cursor keys on the Shell page of the configuration"""
167 """ dialog. Pressing these keys after some text has been"""
168 """ entered will start an incremental search.</p>"""
169 """<p>The shell has some special commands. '%restart' kills"""
170 """ the shell and starts a new one. '%clear' clears the"""
171 """ display of the shell window. '%start' is used to start a"""
172 """ shell for a virtual environment and should be followed"""
173 """ by a virtual environment name. '%start' without a"""
174 """ virtual environment name starts the default shell."""
175 """ Available virtual environments may be listed with the"""
176 """ '%envs' or '%environments' commands. The active virtual"""
177 """ environment can be questioned by the '%which' command."""
178 """ '%quit' or '%exit' is used to exit the application."""
179 """ These commands (except '%environments', '%envs' and"""
180 """ '%which') are available through the window menus as"""
181 """ well.</p>"""
182 """<p>Pressing the Tab key after some text has been entered"""
183 """ will show a list of possible completions. The relevant"""
184 """ entry may be selected from this list. If only one entry"""
185 """ is available, this will be inserted automatically.</p>"""
186 ))
187 else:
188 self.setWhatsThis(self.tr(
189 """<b>The Shell Window</b>"""
190 """<p>This is simply an interpreter running in a window. The"""
191 """ interpreter is the one that is used to run the program"""
192 """ being debugged. This means that you can execute any"""
193 """ command while the program being debugged is running.</p>"""
194 """<p>You can use the cursor keys while entering commands."""
195 """ There is also a history of commands that can be recalled"""
196 """ using the up and down cursor keys while holding down the"""
197 """ Ctrl-key. This can be switched to just the up and down"""
198 """ cursor keys on the Shell page of the configuration"""
199 """ dialog. Pressing these keys after some text has been"""
200 """ entered will start an incremental search.</p>"""
201 """<p>The shell has some special commands. '%restart' kills"""
202 """ the shell and starts a new one. '%clear' clears the"""
203 """ display of the shell window. '%start' is used to start a"""
204 """ shell for a virtual environment and should be followed"""
205 """ by a virtual environment name. '%start' without a"""
206 """ virtual environment name starts the default shell."""
207 """ Available virtual environments may be listed with the"""
208 """ '%envs' or '%environments' commands. The active virtual"""
209 """ environment can be questioned by the '%which' command."""
210 """ These commands (except '%environments' and '%envs') are"""
211 """ available through the context menu as well.</p>"""
212 """<p>Pressing the Tab key after some text has been entered"""
213 """ will show a list of possible completions. The relevant"""
214 """ entry may be selected from this list. If only one entry"""
215 """ is available, this will be inserted automatically.</p>"""
216 """<p>In passive debugging mode the shell is only available"""
217 """ after the program to be debugged has connected to the"""
218 """ IDE until it has finished. This is indicated by a"""
219 """ different prompt and by an indication in the window"""
220 """ caption.</p>"""
221 ))
222
223 self.userListActivated.connect(self.__completionListSelected)
224 self.linesChanged.connect(self.__resizeLinenoMargin)
225
226 if self.__windowed:
227 self.__showStdOutErr = True
228 else:
229 self.__showStdOutErr = Preferences.getShell("ShowStdOutErr")
230 if self.__showStdOutErr:
231 dbs.clientProcessStdout.connect(self.__writeStdOut)
232 dbs.clientProcessStderr.connect(self.__writeStdErr)
233 dbs.clientOutput.connect(self.__writeQueued)
234 dbs.clientStatement.connect(self.__clientStatement)
235 dbs.clientGone.connect(self.__initialise)
236 dbs.clientRawInput.connect(self.__raw_input)
237 dbs.clientBanner.connect(self.__writeBanner)
238 dbs.clientCompletionList.connect(self.__showCompletions)
239 dbs.clientCapabilities.connect(self.__clientCapabilities)
240 dbs.clientException.connect(self.__clientException)
241 dbs.clientSyntaxError.connect(self.__clientSyntaxError)
242 dbs.clientSignal.connect(self.__clientSignal)
243 dbs.mainClientExit.connect(self.__writePrompt)
244 self.dbs = dbs
245
246 # will register a method to get the debugger ID to work with
247 self.__getSelectedDebuggerId = None
248
249 # Make sure we have prompts.
250 if self.passive:
251 sys.ps1 = self.tr("Passive >>> ")
252 else:
253 try:
254 sys.ps1
255 except AttributeError:
256 sys.ps1 = ">>> "
257 try:
258 sys.ps2
259 except AttributeError:
260 sys.ps2 = "... "
261
262 # Initialize instance variables.
263 self.__initialise()
264 self.prline = 0
265 self.prcol = 0
266 self.inDragDrop = False
267 self.lexer_ = None
268 self.completionText = ""
269
270 self.clientType = ''
271
272 # Initialize history
273 self.__historyLists = {}
274 self.__maxHistoryEntries = Preferences.getShell("MaxHistoryEntries")
275 self.__historyStyle = Preferences.getShell("HistoryStyle")
276 self.__historyWrap = Preferences.getShell("HistoryWrap")
277 self.__history = []
278 self.__setHistoryIndex()
279 # remove obsolete shell histories (Python and Ruby)
280 for clientType in ["Python", "Ruby"]:
281 Preferences.getSettings().remove("Shell/Histories/" + clientType)
282
283 # clear QScintilla defined keyboard commands
284 # we do our own handling through the view manager
285 self.clearAlternateKeys()
286 self.clearKeys()
287 self.__actionsAdded = False
288
289 if self.passive:
290 self.__getBanner()
291
292 if not self.__windowed:
293 # Create a little language context menu
294 self.lmenu = QMenu(self.tr('Start'))
295 self.lmenu.aboutToShow.connect(self.__showStartMenu)
296 self.lmenu.triggered.connect(self.__startDebugClient)
297
298 # Create the history context menu
299 self.hmenu = QMenu(self.tr('History'))
300 self.hmenu.addAction(self.tr('Select entry'), self.selectHistory)
301 self.hmenu.addAction(self.tr('Show'), self.showHistory)
302 self.hmenu.addAction(self.tr('Clear'), self.clearHistory)
303
304 # Create a little context menu
305 self.menu = QMenu(self)
306 self.menu.addAction(self.tr('Cut'), self.cut)
307 self.menu.addAction(self.tr('Copy'), self.copy)
308 self.menu.addAction(self.tr('Paste'), self.paste)
309 self.menu.addMenu(self.hmenu).setEnabled(self.isHistoryEnabled())
310
311 self.menu.addSeparator()
312 self.menu.addAction(self.tr('Find'), self.__find)
313 self.menu.addSeparator()
314 self.menu.addAction(self.tr('Clear'), self.clear)
315 self.menu.addAction(self.tr('Restart'), self.doRestart)
316 self.menu.addAction(
317 self.tr('Restart and Clear'), self.doClearRestart)
318 self.menu.addSeparator()
319 self.menu.addMenu(self.lmenu)
320 self.menu.addAction(self.tr('Active Name'), self.__showVenvName)
321 self.menu.addSeparator()
322 self.menu.addAction(self.tr("Save Contents..."), self.saveContents)
323 self.menu.addSeparator()
324 self.menu.addAction(self.tr("Configure..."), self.__configure)
325
326 self.__bindLexer()
327 self.__setTextDisplay()
328 self.__setMargin0()
329
330 # set the autocompletion and calltips function
331 self.__setAutoCompletion()
332 self.__setCallTips()
333
334 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
335
336 self.incrementalSearchString = ""
337 self.incrementalSearchActive = False
338
339 self.supportedEditorCommands = {
340 QsciScintilla.SCI_LINEDELETE: self.__clearCurrentLine,
341 QsciScintilla.SCI_TAB: self.__QScintillaTab,
342 QsciScintilla.SCI_NEWLINE: self.__QScintillaNewline,
343
344 QsciScintilla.SCI_DELETEBACK: self.__QScintillaDeleteBack,
345 QsciScintilla.SCI_CLEAR: self.__QScintillaDelete,
346 QsciScintilla.SCI_DELWORDLEFT: self.__QScintillaDeleteWordLeft,
347 QsciScintilla.SCI_DELWORDRIGHT: self.__QScintillaDeleteWordRight,
348 QsciScintilla.SCI_DELLINELEFT: self.__QScintillaDeleteLineLeft,
349 QsciScintilla.SCI_DELLINERIGHT: self.__QScintillaDeleteLineRight,
350
351 QsciScintilla.SCI_CHARLEFT: self.__QScintillaCharLeft,
352 QsciScintilla.SCI_CHARRIGHT: self.__QScintillaCharRight,
353 QsciScintilla.SCI_WORDLEFT: self.__QScintillaWordLeft,
354 QsciScintilla.SCI_WORDRIGHT: self.__QScintillaWordRight,
355 QsciScintilla.SCI_VCHOME: self.__QScintillaVCHome,
356 QsciScintilla.SCI_LINEEND: self.__QScintillaLineEnd,
357
358 QsciScintilla.SCI_LINEUP: self.__QScintillaCursorCommand,
359 QsciScintilla.SCI_LINEDOWN: self.__QScintillaCursorCommand,
360 QsciScintilla.SCI_LINESCROLLUP: self.__QScintillaCursorCommand,
361 QsciScintilla.SCI_LINESCROLLDOWN: self.__QScintillaCursorCommand,
362
363 QsciScintilla.SCI_PAGEUP: self.__QScintillaAutoCompletionCommand,
364 QsciScintilla.SCI_PAGEDOWN: self.__QScintillaAutoCompletionCommand,
365
366 QsciScintilla.SCI_CHARLEFTEXTEND: self.__QScintillaCharLeftExtend,
367 QsciScintilla.SCI_CHARRIGHTEXTEND: self.extendSelectionRight,
368 QsciScintilla.SCI_WORDLEFTEXTEND: self.__QScintillaWordLeftExtend,
369 QsciScintilla.SCI_WORDRIGHTEXTEND: self.extendSelectionWordRight,
370 QsciScintilla.SCI_VCHOMEEXTEND: self.__QScintillaVCHomeExtend,
371 QsciScintilla.SCI_LINEENDEXTEND: self.extendSelectionToEOL,
372
373 QsciScintilla.SCI_CANCEL: self.__QScintillaCancel,
374 }
375
376 self.__historyNavigateByCursor = (
377 Preferences.getShell("HistoryNavigateByCursor")
378 )
379
380 self.__queuedText = ''
381 self.__blockTextProcessing = False
382 self.queueText.connect(self.__concatenateText,
383 Qt.ConnectionType.QueuedConnection)
384
385 self.__project = project
386 if self.__project:
387 self.__project.projectOpened.connect(self.__projectOpened)
388 self.__project.projectClosed.connect(self.__projectClosed)
389
390 self.grabGesture(Qt.GestureType.PinchGesture)
391
392 def __showStartMenu(self):
393 """
394 Private slot to prepare the start submenu.
395 """
396 self.lmenu.clear()
397 venvManager = ericApp().getObject("VirtualEnvManager")
398 for venvName in sorted(venvManager.getVirtualenvNames()):
399 self.lmenu.addAction(venvName)
400 if self.__project.isOpen():
401 self.lmenu.addSeparator()
402 self.lmenu.addAction(self.tr("Project"))
403
404 def __resizeLinenoMargin(self):
405 """
406 Private slot to resize the line numbers margin.
407 """
408 linenoMargin = Preferences.getShell("LinenoMargin")
409 if linenoMargin:
410 self.setMarginWidth(0, '8' * (len(str(self.lines())) + 1))
411
412 def closeShell(self):
413 """
414 Public method to shutdown the shell.
415 """
416 for clientType in self.__historyLists:
417 self.saveHistory(clientType)
418
419 def __bindLexer(self, language='Python3'):
420 """
421 Private slot to set the lexer.
422
423 @param language lexer language to set (string)
424 """
425 self.language = language
426 if Preferences.getShell("SyntaxHighlightingEnabled"):
427 from . import Lexers
428 self.lexer_ = Lexers.getLexer(self.language, self)
429 else:
430 self.lexer_ = None
431
432 if self.lexer_ is None:
433 self.setLexer(None)
434 font = Preferences.getShell("MonospacedFont")
435 self.monospacedStyles(font)
436 return
437
438 # get the font for style 0 and set it as the default font
439 key = 'Scintilla/{0}/style0/font'.format(self.lexer_.language())
440 fdesc = Preferences.getSettings().value(key)
441 if fdesc is not None:
442 font = QFont([fdesc[0]], int(fdesc[1]))
443 self.lexer_.setDefaultFont(font)
444 self.setLexer(self.lexer_)
445 self.lexer_.readSettings(Preferences.getSettings(), "Scintilla")
446 if self.lexer_.hasSubstyles():
447 self.lexer_.readSubstyles(self)
448
449 # initialize the lexer APIs settings
450 api = self.vm.getAPIsManager().getAPIs(self.language)
451 if api is not None:
452 api = api.getQsciAPIs()
453 if api is not None:
454 self.lexer_.setAPIs(api)
455
456 self.lexer_.setDefaultColor(self.lexer_.color(0))
457 self.lexer_.setDefaultPaper(self.lexer_.paper(0))
458
459 def __setMargin0(self):
460 """
461 Private method to configure margin 0.
462 """
463 # set the settings for all margins
464 self.setMarginsFont(Preferences.getShell("MarginsFont"))
465 self.setMarginsForegroundColor(
466 Preferences.getEditorColour("MarginsForeground"))
467 self.setMarginsBackgroundColor(
468 Preferences.getEditorColour("MarginsBackground"))
469
470 # set margin 0 settings
471 linenoMargin = Preferences.getShell("LinenoMargin")
472 self.setMarginLineNumbers(0, linenoMargin)
473 if linenoMargin:
474 self.__resizeLinenoMargin()
475 else:
476 self.setMarginWidth(0, 0)
477
478 # disable margins 1 and 2
479 self.setMarginWidth(1, 0)
480 self.setMarginWidth(2, 0)
481
482 def __setTextDisplay(self):
483 """
484 Private method to configure the text display.
485 """
486 self.setTabWidth(Preferences.getEditor("TabWidth"))
487 if Preferences.getEditor("ShowWhitespace"):
488 self.setWhitespaceVisibility(
489 QsciScintilla.WhitespaceVisibility.WsVisible)
490 with contextlib.suppress(AttributeError):
491 self.setWhitespaceForegroundColor(
492 Preferences.getEditorColour("WhitespaceForeground"))
493 self.setWhitespaceBackgroundColor(
494 Preferences.getEditorColour("WhitespaceBackground"))
495 self.setWhitespaceSize(
496 Preferences.getEditor("WhitespaceSize"))
497 else:
498 self.setWhitespaceVisibility(
499 QsciScintilla.WhitespaceVisibility.WsInvisible)
500 self.setEolVisibility(Preferences.getEditor("ShowEOL"))
501 if Preferences.getEditor("BraceHighlighting"):
502 self.setBraceMatching(QsciScintilla.BraceMatch.SloppyBraceMatch)
503 else:
504 self.setBraceMatching(QsciScintilla.BraceMatch.NoBraceMatch)
505 self.setMatchedBraceForegroundColor(
506 Preferences.getEditorColour("MatchingBrace"))
507 self.setMatchedBraceBackgroundColor(
508 Preferences.getEditorColour("MatchingBraceBack"))
509 self.setUnmatchedBraceForegroundColor(
510 Preferences.getEditorColour("NonmatchingBrace"))
511 self.setUnmatchedBraceBackgroundColor(
512 Preferences.getEditorColour("NonmatchingBraceBack"))
513 if Preferences.getEditor("CustomSelectionColours"):
514 self.setSelectionBackgroundColor(
515 Preferences.getEditorColour("SelectionBackground"))
516 else:
517 self.setSelectionBackgroundColor(
518 QApplication.palette().color(QPalette.ColorRole.Highlight))
519 if Preferences.getEditor("ColourizeSelText"):
520 self.resetSelectionForegroundColor()
521 elif Preferences.getEditor("CustomSelectionColours"):
522 self.setSelectionForegroundColor(
523 Preferences.getEditorColour("SelectionForeground"))
524 else:
525 self.setSelectionForegroundColor(
526 QApplication.palette().color(
527 QPalette.ColorRole.HighlightedText))
528 self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol"))
529 self.setCaretForegroundColor(
530 Preferences.getEditorColour("CaretForeground"))
531 self.setCaretLineVisible(False)
532 self.caretWidth = Preferences.getEditor("CaretWidth")
533 self.setCaretWidth(self.caretWidth)
534 if Preferences.getShell("WrapEnabled"):
535 self.setWrapMode(QsciScintilla.WrapMode.WrapWord)
536 else:
537 self.setWrapMode(QsciScintilla.WrapMode.WrapNone)
538 self.useMonospaced = Preferences.getShell("UseMonospacedFont")
539 self.__setMonospaced(self.useMonospaced)
540
541 self.setCursorFlashTime(QApplication.cursorFlashTime())
542
543 if Preferences.getEditor("OverrideEditAreaColours"):
544 self.setColor(Preferences.getEditorColour("EditAreaForeground"))
545 self.setPaper(Preferences.getEditorColour("EditAreaBackground"))
546
547 def __setMonospaced(self, on):
548 """
549 Private method to set/reset a monospaced font.
550
551 @param on flag to indicate usage of a monospace font (boolean)
552 """
553 if on:
554 if not self.lexer_:
555 f = Preferences.getShell("MonospacedFont")
556 self.monospacedStyles(f)
557 else:
558 if not self.lexer_:
559 self.clearStyles()
560 self.__setMargin0()
561 self.setFont(Preferences.getShell("MonospacedFont"))
562
563 self.useMonospaced = on
564
565 def __setAutoCompletion(self, language='Python'):
566 """
567 Private method to configure the autocompletion function.
568
569 @param language of the autocompletion set to set (string)
570 """
571 self.setAutoCompletionCaseSensitivity(
572 Preferences.getEditor("AutoCompletionCaseSensitivity"))
573 self.setAutoCompletionThreshold(-1)
574
575 self.racEnabled = Preferences.getShell("AutoCompletionEnabled")
576
577 self.setAutoCompletionWidgetSize(
578 Preferences.getEditor("AutoCompletionMaxChars"),
579 Preferences.getEditor("AutoCompletionMaxLines")
580 )
581
582 def __setCallTips(self, language='Python'):
583 """
584 Private method to configure the calltips function.
585
586 @param language of the calltips set to set (string)
587 """
588 if Preferences.getShell("CallTipsEnabled"):
589 self.setCallTipsBackgroundColor(
590 Preferences.getEditorColour("CallTipsBackground"))
591 self.setCallTipsForegroundColor(
592 Preferences.getEditorColour("CallTipsForeground"))
593 self.setCallTipsHighlightColor(
594 Preferences.getEditorColour("CallTipsHighlight"))
595 self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
596 calltipsStyle = Preferences.getEditor("CallTipsStyle")
597 if calltipsStyle == QsciScintilla.CallTipsStyle.CallTipsNoContext:
598 self.setCallTipsStyle(
599 QsciScintilla.CallTipsStyle.CallTipsNoContext)
600 elif (
601 calltipsStyle ==
602 QsciScintilla.CallTipsStyle.CallTipsNoAutoCompletionContext
603 ):
604 self.setCallTipsStyle(
605 QsciScintilla.CallTipsStyle
606 .CallTipsNoAutoCompletionContext)
607 else:
608 self.setCallTipsStyle(
609 QsciScintilla.CallTipsStyle.CallTipsContext)
610 else:
611 self.setCallTipsStyle(QsciScintilla.CallTipsStyle.CallTipsNone)
612
613 def setDebuggerUI(self, ui):
614 """
615 Public method to set the debugger UI.
616
617 @param ui reference to the debugger UI object (DebugUI)
618 """
619 ui.exceptionInterrupt.connect(self.__writePrompt)
620 self.registerDebuggerIdMethod(ui.getSelectedDebuggerId)
621
622 def registerDebuggerIdMethod(self, method):
623 """
624 Public method to register a method to get the debugger ID to send data
625 to.
626
627 @param method reference to the method
628 @type function
629 """
630 self.__getSelectedDebuggerId = method
631
632 def __initialise(self):
633 """
634 Private method to get ready for a new remote interpreter.
635 """
636 self.buff = ""
637 self.inContinue = False
638 self.__inRawMode = False
639 self.__echoInput = True
640 self.__rawModeDebuggerId = None
641 self.__rawModeQueue = []
642 self.clientCapabilities = 0
643 self.inCommandExecution = False
644 self.interruptCommandExecution = False
645
646 def __clientCapabilities(self, cap, clType, venvName):
647 """
648 Private slot to handle the reporting of the clients capabilities.
649
650 @param cap client capabilities
651 @type int
652 @param clType type of the debug client
653 @type str
654 @param venvName name of the virtual environment
655 @type str
656 """
657 self.clientCapabilities = cap
658 self.__currentVenv = venvName
659 if clType != self.clientType:
660 self.clientType = clType
661 self.__bindLexer(self.clientType)
662 self.__setTextDisplay()
663 self.__setMargin0()
664 self.__setAutoCompletion(self.clientType)
665 self.__setCallTips(self.clientType)
666 self.racEnabled = (
667 Preferences.getShell("AutoCompletionEnabled") and
668 (cap & HasCompleter) > 0
669 )
670
671 if self.clientType not in self.__historyLists:
672 # load history list
673 self.loadHistory(self.clientType)
674 self.__history = self.__historyLists[self.clientType]
675 self.__setHistoryIndex()
676
677 self.virtualEnvironmentChanged.emit(venvName)
678 Preferences.setShell("LastVirtualEnvironment", venvName)
679
680 def __setHistoryIndex(self, index=None):
681 """
682 Private method to set the initial history index.
683
684 @param index index value to be set
685 @type int or None
686 """
687 if index is None:
688 # determine based on history style
689 if (
690 self.clientType and
691 self.__historyStyle == ShellHistoryStyle.WINDOWSSTYLE
692 ):
693 idx = int(Preferences.getSettings().value(
694 "Shell/HistoryIndexes/" + self.clientType, -1))
695 if idx >= len(self.__history):
696 idx = -1
697 self.__histidx = idx
698 else:
699 self.__histidx = -1
700 else:
701 self.__histidx = index
702 if self.__histidx >= len(self.__history):
703 self.__histidx = -1
704 if (
705 self.clientType and
706 self.__historyStyle == ShellHistoryStyle.WINDOWSSTYLE
707 ):
708 Preferences.getSettings().setValue(
709 "Shell/HistoryIndexes/" + self.clientType, self.__histidx)
710
711 def __isHistoryIndexValid(self):
712 """
713 Private method to test, if the history index is valid.
714
715 @return flag indicating validity
716 @rtype bool
717 """
718 return (0 <= self.__histidx < len(self.__history))
719
720 def getHistoryIndex(self):
721 """
722 Public method to get the current value of the history index.
723
724 @return history index
725 @rtype int
726 """
727 return self.__histidx
728
729 def loadHistory(self, clientType):
730 """
731 Public method to load the history for the given client type.
732
733 @param clientType type of the debug client (string)
734 """
735 hl = Preferences.getSettings().value("Shell/Histories/" + clientType)
736 if hl is not None:
737 self.__historyLists[clientType] = hl[-self.__maxHistoryEntries:]
738 else:
739 self.__historyLists[clientType] = []
740
741 def reloadHistory(self):
742 """
743 Public method to reload the history of the currently selected client
744 type.
745 """
746 self.loadHistory(self.clientType)
747 self.__history = self.__historyLists[self.clientType]
748 self.__setHistoryIndex()
749
750 def saveHistory(self, clientType):
751 """
752 Public method to save the history for the given client type.
753
754 @param clientType type of the debug client (string)
755 """
756 if clientType in self.__historyLists:
757 Preferences.getSettings().setValue(
758 "Shell/Histories/" + clientType,
759 self.__historyLists[clientType])
760
761 def getHistory(self, clientType):
762 """
763 Public method to get the history for the given client type.
764
765 @param clientType type of the debug client (string).
766 If it is None, the current history is returned.
767 @return reference to the history list (list of strings)
768 """
769 if clientType is None:
770 return self.__history
771 elif clientType in self.__historyLists:
772 return self.__historyLists[clientType]
773 else:
774 return []
775
776 def clearHistory(self):
777 """
778 Public slot to clear the current history.
779 """
780 if self.clientType:
781 self.__historyLists[self.clientType] = []
782 self.__history = self.__historyLists[self.clientType]
783 else:
784 self.__history = []
785 self.__setHistoryIndex(index=-1)
786
787 def selectHistory(self):
788 """
789 Public slot to select a history entry to execute.
790 """
791 current = self.__histidx
792 if current == -1:
793 current = len(self.__history) - 1
794 cmd, ok = QInputDialog.getItem(
795 self,
796 self.tr("Select History"),
797 self.tr("Select the history entry to execute"
798 " (most recent shown last)."),
799 self.__history,
800 current, False)
801 if ok:
802 self.__insertHistory(cmd)
803
804 def showHistory(self):
805 """
806 Public slot to show the shell history dialog.
807 """
808 from .ShellHistoryDialog import ShellHistoryDialog
809 dlg = ShellHistoryDialog(self.__history, self.vm, self)
810 if dlg.exec() == QDialog.DialogCode.Accepted:
811 self.__historyLists[self.clientType], idx = dlg.getHistory()
812 self.__history = self.__historyLists[self.clientType]
813 self.__setHistoryIndex(index=idx)
814
815 def clearAllHistories(self):
816 """
817 Public method to clear all available histories and sync them.
818 """
819 Preferences.getSettings().beginGroup("Shell/Histories")
820 for clientType in Preferences.getSettings().childKeys():
821 self.__historyLists[clientType] = []
822 self.saveHistory(clientType)
823 Preferences.getSettings().endGroup()
824
825 self.clearHistory()
826
827 def getClientType(self):
828 """
829 Public slot to get the clients type.
830
831 @return client type (string)
832 """
833 return self.clientType
834
835 def __getBanner(self):
836 """
837 Private method to get the banner for the remote interpreter.
838
839 It requests the interpreter version and platform running on the
840 debug client side.
841 """
842 if self.passive:
843 self.__writeBanner('', '', '', '')
844 else:
845 self.dbs.remoteBanner()
846
847 def __writeBanner(self, version, platform, venvName):
848 """
849 Private method to write a banner with info from the debug client.
850
851 @param version interpreter version string
852 @type str
853 @param platform platform of the remote interpreter
854 @type str
855 @param venvName name of the virtual environment
856 @type str
857 """
858 super().clear()
859 if self.passive and not self.dbs.isConnected():
860 self.__write(self.tr('Passive Debug Mode'))
861 self.__write(self.tr('\nNot connected'))
862 else:
863 self.__currentVenv = venvName
864 version = version.replace("#", self.tr("No."))
865 if platform != "":
866 self.__write(self.tr('{0} on {1}').format(version, platform))
867 else:
868 self.__write(version)
869 if venvName:
870 self.__write("\n[{0}]".format(venvName))
871
872 self.virtualEnvironmentChanged.emit(venvName)
873 Preferences.setShell("LastVirtualEnvironment", venvName)
874 self.__write('\n')
875
876 self.__write(sys.ps1)
877
878 def __writePrompt(self):
879 """
880 Private method to write the prompt using a write queue.
881 """
882 self.queueText.emit(self.inContinue and sys.ps2 or sys.ps1)
883
884 def __clientStatement(self, more):
885 """
886 Private method to handle the response from the debugger client.
887
888 @param more flag indicating that more user input is required
889 @type bool
890 """
891 if not self.__inRawMode:
892 self.inContinue = more
893 self.__writePrompt()
894 self.inCommandExecution = False
895
896 def __clientException(self, exceptionType, exceptionMessage, stackTrace):
897 """
898 Private method to handle an exception of the client.
899
900 @param exceptionType type of exception raised (string)
901 @param exceptionMessage message given by the exception (string)
902 @param stackTrace list of stack entries (list of string)
903 """
904 self .__clientError()
905
906 if (
907 not self.__windowed and
908 Preferences.getDebugger("ShowExceptionInShell") and
909 exceptionType
910 ):
911 if stackTrace:
912 self.__write(
913 self.tr('Exception "{0}"\n{1}\nFile: {2}, Line: {3}\n')
914 .format(
915 exceptionType,
916 exceptionMessage,
917 stackTrace[0][0],
918 stackTrace[0][1]
919 )
920 )
921 else:
922 self.__write(
923 self.tr('Exception "{0}"\n{1}\n')
924 .format(
925 exceptionType,
926 exceptionMessage)
927 )
928
929 def __clientSyntaxError(self, message, filename, lineNo, characterNo):
930 """
931 Private method to handle a syntax error in the debugged program.
932
933 @param message message of the syntax error (string)
934 @param filename translated filename of the syntax error position
935 (string)
936 @param lineNo line number of the syntax error position (integer)
937 @param characterNo character number of the syntax error position
938 (integer)
939 """
940 self .__clientError()
941
942 if (
943 not self.__windowed and
944 Preferences.getDebugger("ShowExceptionInShell")
945 ):
946 if message is None:
947 self.__write(self.tr("Unspecified syntax error.\n"))
948 else:
949 self.__write(
950 self.tr('Syntax error "{1}" in file {0} at line {2},'
951 ' character {3}.\n')
952 .format(filename, message, lineNo, characterNo)
953 )
954
955 def __clientSignal(self, message, filename, lineNo, funcName, funcArgs):
956 """
957 Private method to handle a signal generated on the client side.
958
959 @param message message of the syntax error
960 @type str
961 @param filename translated filename of the syntax error position
962 @type str
963 @param lineNo line number of the syntax error position
964 @type int
965 @param funcName name of the function causing the signal
966 @type str
967 @param funcArgs function arguments
968 @type str
969 """
970 self.__clientError()
971
972 self.__write(
973 self.tr("""Signal "{0}" generated in file {1} at line {2}.\n"""
974 """Function: {3}({4})""")
975 .format(message, filename, lineNo, funcName, funcArgs)
976 )
977
978 def __clientError(self):
979 """
980 Private method to handle an error in the client.
981 """
982 self.inCommandExecution = False
983 self.interruptCommandExecution = True
984 self.inContinue = False
985
986 def __getEndPos(self):
987 """
988 Private method to return the line and column of the last character.
989
990 @return tuple of two values (int, int) giving the line and column
991 """
992 line = self.lines() - 1
993 return (line, len(self.text(line)))
994
995 def __writeQueued(self, s):
996 """
997 Private method to display some text using a write queue.
998
999 @param s text to be displayed (string)
1000 """
1001 self.queueText.emit(s)
1002
1003 def __concatenateText(self, text):
1004 """
1005 Private slot to queue text and process it in one step.
1006
1007 @param text text to be appended
1008 @type str
1009 """
1010 self.__queuedText += text
1011 if self.__blockTextProcessing:
1012 return
1013
1014 self.__blockTextProcessing = True
1015 # Get all text which is still waiting for output
1016 QApplication.processEvents()
1017
1018 # Finally process the accumulated text
1019 self.__flushQueuedText()
1020
1021 def __flushQueuedText(self):
1022 """
1023 Private slot to flush the accumulated text output.
1024 """
1025 self.__write(self.__queuedText)
1026
1027 self.__queuedText = ''
1028 self.__blockTextProcessing = False
1029
1030 # little trick to get the cursor position registered within QScintilla
1031 self.SendScintilla(QsciScintilla.SCI_CHARLEFT)
1032 self.SendScintilla(QsciScintilla.SCI_CHARRIGHT)
1033
1034 def __write(self, s):
1035 """
1036 Private method to display some text without queuing.
1037
1038 @param s text to be displayed
1039 @type str
1040 """
1041 line, col = self.__getEndPos()
1042 self.setCursorPosition(line, col)
1043 self.insert(Utilities.filterAnsiSequences(s))
1044 self.prline, self.prcol = self.getCursorPosition()
1045 self.ensureCursorVisible()
1046 self.ensureLineVisible(self.prline)
1047
1048 def __writeStdOut(self, s):
1049 """
1050 Private method to display some text with StdOut label.
1051
1052 @param s text to be displayed (string)
1053 """
1054 self.__write(self.tr("StdOut: {0}").format(s))
1055
1056 def __writeStdErr(self, s):
1057 """
1058 Private method to display some text with StdErr label.
1059
1060 @param s text to be displayed (string)
1061 """
1062 self.__write(self.tr("StdErr: {0}").format(s))
1063
1064 def __raw_input(self, prompt, echo, debuggerId):
1065 """
1066 Private method to handle raw input.
1067
1068 @param prompt the input prompt
1069 @type str
1070 @param echo flag indicating an echoing of the input
1071 @type bool
1072 @param debuggerId ID of the debugger backend
1073 @type str
1074 """
1075 if self.__inRawMode:
1076 # we are processing another raw input event already
1077 self.__rawModeQueue.append((debuggerId, prompt, echo))
1078 else:
1079 self.setFocus()
1080 self.__inRawMode = True
1081 self.__echoInput = echo
1082 self.__rawModeDebuggerId = debuggerId
1083
1084 # Get all text which is still waiting for output
1085 QApplication.processEvents()
1086 self.__flushQueuedText()
1087
1088 self.__write(self.tr("<{0}> {1}").format(debuggerId, prompt))
1089 line, col = self.__getEndPos()
1090 self.setCursorPosition(line, col)
1091 buf = self.text(line)
1092 if buf.startswith(sys.ps1):
1093 buf = buf.replace(sys.ps1, "")
1094 if buf.startswith(sys.ps2):
1095 buf = buf.replace(sys.ps2, "")
1096 self.prompt = buf
1097 # move cursor to end of line
1098 self.moveCursorToEOL()
1099
1100 def paste(self, lines=None):
1101 """
1102 Public slot to handle the paste action.
1103
1104 @param lines list of lines to be inserted
1105 @type list of str
1106 """
1107 if self.__isCursorOnLastLine():
1108 line, col = self.getCursorPosition()
1109 lastLine = self.text(line)
1110 if lastLine.startswith(sys.ps1):
1111 lastLine = lastLine[len(sys.ps1):]
1112 col -= len(sys.ps1)
1113 prompt = sys.ps1
1114 elif lastLine.startswith(sys.ps2):
1115 lastLine = lastLine[len(sys.ps2):]
1116 col -= len(sys.ps2)
1117 prompt = sys.ps2
1118 else:
1119 prompt = ""
1120 if col < 0:
1121 col = 0
1122 prompt = ""
1123
1124 # Remove if text is selected
1125 if self.hasSelectedText():
1126 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1127 if self.text(lineFrom).startswith(sys.ps1):
1128 indexFrom -= len(sys.ps1)
1129 indexTo -= len(sys.ps1)
1130 elif self.text(lineFrom).startswith(sys.ps2):
1131 indexFrom -= len(sys.ps2)
1132 indexTo -= len(sys.ps2)
1133 if indexFrom < 0:
1134 indexFrom = 0
1135 lastLine = lastLine[:indexFrom] + lastLine[indexTo:]
1136 col = indexFrom
1137
1138 self.setCursorPosition(line, len(prompt))
1139 self.deleteLineRight()
1140
1141 if lines is None:
1142 lines = QApplication.clipboard().text()
1143
1144 lines = lastLine[:col] + lines + lastLine[col:]
1145 self.executeLines(lines)
1146 line, _ = self.getCursorPosition()
1147 pos = len(self.text(line)) - (len(lastLine) - col)
1148 self.setCursorPosition(line, pos)
1149
1150 def executeLines(self, lines, historyIndex=None):
1151 """
1152 Public method to execute a set of lines as multiple commands.
1153
1154 @param lines multiple lines of text to be executed as
1155 single commands
1156 @type str
1157 @param historyIndex history index to be set
1158 @type int
1159 """
1160 lines = lines.splitlines(True)
1161 if not lines:
1162 return
1163
1164 indentLen = self.__indentLength(lines[0])
1165 for line in lines:
1166 if line.startswith(sys.ps1):
1167 line = line[len(sys.ps1) + indentLen:]
1168 elif line.startswith(sys.ps2):
1169 line = line[len(sys.ps2) + indentLen:]
1170 else:
1171 line = line[indentLen:]
1172
1173 if line.endswith(("\r\n", "\r", "\n")):
1174 fullline = True
1175 cmd = line.rstrip()
1176 else:
1177 fullline = False
1178
1179 self.incrementalSearchActive = True
1180 self.__insertTextAtEnd(line)
1181 if fullline:
1182 self.incrementalSearchActive = False
1183
1184 self.__executeCommand(cmd, historyIndex=historyIndex)
1185 if self.interruptCommandExecution:
1186 self.__executeCommand("")
1187 break
1188
1189 def __indentLength(self, line):
1190 """
1191 Private method to determine the indentation length of the given line.
1192
1193 @param line line to determine the indentation length for
1194 @type str
1195 @return indentation length
1196 @rtype int
1197 """
1198 if line.startswith(sys.ps1):
1199 line = line[len(sys.ps1):]
1200 # If line starts with sys.ps2 or neither don't manipulate the line.
1201 indentLen = len(line) - len(line.lstrip())
1202 return indentLen
1203
1204 def __clearCurrentLine(self):
1205 """
1206 Private method to clear the line containing the cursor.
1207 """
1208 line, col = self.getCursorPosition()
1209 if self.text(line).startswith(sys.ps1):
1210 col = len(sys.ps1)
1211 elif self.text(line).startswith(sys.ps2):
1212 col = len(sys.ps2)
1213 else:
1214 col = 0
1215 self.setCursorPosition(line, col)
1216 self.deleteLineRight()
1217
1218 def __insertText(self, s):
1219 """
1220 Private method to insert some text at the current cursor position.
1221
1222 @param s text to be inserted (string)
1223 """
1224 line, col = self.getCursorPosition()
1225 self.insertAt(Utilities.filterAnsiSequences(s), line, col)
1226 self.setCursorPosition(line, col + len(s))
1227
1228 def __insertTextAtEnd(self, s):
1229 """
1230 Private method to insert some text at the end of the command line.
1231
1232 @param s text to be inserted (string)
1233 """
1234 line, col = self.__getEndPos()
1235 self.setCursorPosition(line, col)
1236 self.insert(Utilities.filterAnsiSequences(s))
1237 self.prline, _ = self.getCursorPosition()
1238
1239 def __insertTextNoEcho(self, s):
1240 """
1241 Private method to insert some text at the end of the buffer without
1242 echoing it.
1243
1244 @param s text to be inserted (string)
1245 """
1246 self.buff += s
1247 self.prline, self.prcol = self.getCursorPosition()
1248
1249 def mousePressEvent(self, event):
1250 """
1251 Protected method to handle the mouse press event.
1252
1253 @param event the mouse press event (QMouseEvent)
1254 """
1255 self.setFocus()
1256 if event.button() == Qt.MouseButton.MiddleButton:
1257 lines = QApplication.clipboard().text(QClipboard.Mode.Selection)
1258 self.paste(lines)
1259 else:
1260 super().mousePressEvent(event)
1261
1262 def wheelEvent(self, evt):
1263 """
1264 Protected method to handle wheel events.
1265
1266 @param evt reference to the wheel event (QWheelEvent)
1267 """
1268 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1269 delta = evt.angleDelta().y()
1270 if delta < 0:
1271 self.zoomOut()
1272 elif delta > 0:
1273 self.zoomIn()
1274 evt.accept()
1275 return
1276
1277 super().wheelEvent(evt)
1278
1279 def event(self, evt):
1280 """
1281 Public method handling events.
1282
1283 @param evt reference to the event (QEvent)
1284 @return flag indicating, if the event was handled (boolean)
1285 """
1286 if evt.type() == QEvent.Type.Gesture:
1287 self.gestureEvent(evt)
1288 return True
1289
1290 return super().event(evt)
1291
1292 def gestureEvent(self, evt):
1293 """
1294 Protected method handling gesture events.
1295
1296 @param evt reference to the gesture event (QGestureEvent
1297 """
1298 pinch = evt.gesture(Qt.GestureType.PinchGesture)
1299 if pinch:
1300 if pinch.state() == Qt.GestureState.GestureStarted:
1301 zoom = (self.getZoom() + 10) / 10.0
1302 pinch.setTotalScaleFactor(zoom)
1303 elif pinch.state() == Qt.GestureState.GestureUpdated:
1304 zoom = int(pinch.totalScaleFactor() * 10) - 10
1305 if zoom <= -9:
1306 zoom = -9
1307 pinch.setTotalScaleFactor(0.1)
1308 elif zoom >= 20:
1309 zoom = 20
1310 pinch.setTotalScaleFactor(3.0)
1311 self.zoomTo(zoom)
1312 evt.accept()
1313
1314 def editorCommand(self, cmd):
1315 """
1316 Public method to perform an editor command.
1317
1318 @param cmd the scintilla command to be performed
1319 """
1320 try:
1321 self.supportedEditorCommands[cmd]()
1322 except TypeError:
1323 self.supportedEditorCommands[cmd](cmd)
1324 except KeyError:
1325 pass
1326
1327 def __isCursorOnLastLine(self):
1328 """
1329 Private method to check, if the cursor is on the last line.
1330
1331 @return flag indicating that the cursor is on the last line (boolean)
1332 """
1333 cline, ccol = self.getCursorPosition()
1334 return cline == self.lines() - 1
1335
1336 def keyPressEvent(self, ev):
1337 """
1338 Protected method to handle the user input a key at a time.
1339
1340 @param ev key event (QKeyEvent)
1341 """
1342 txt = ev.text()
1343
1344 # See it is text to insert.
1345 if len(txt) and txt >= " ":
1346 if not self.__isCursorOnLastLine():
1347 line, col = self.__getEndPos()
1348 self.setCursorPosition(line, col)
1349 self.prline, self.prcol = self.getCursorPosition()
1350 if self.__echoInput:
1351 ac = self.isListActive()
1352 super().keyPressEvent(ev)
1353 self.incrementalSearchActive = True
1354 if ac and self.racEnabled:
1355 self.dbs.remoteCompletion(
1356 self.__getSelectedDebuggerId(),
1357 self.completionText + txt
1358 )
1359 else:
1360 self.__insertTextNoEcho(txt)
1361 else:
1362 ev.ignore()
1363
1364 def __QScintillaCommand(self, cmd):
1365 """
1366 Private method to send the command to QScintilla.
1367
1368 @param cmd QScintilla command
1369 """
1370 self.SendScintilla(cmd)
1371
1372 def __QScintillaTab(self, cmd):
1373 """
1374 Private method to handle the Tab key.
1375
1376 @param cmd QScintilla command
1377 """
1378 if self.isListActive():
1379 self.SendScintilla(cmd)
1380 elif self.__isCursorOnLastLine():
1381 line, index = self.getCursorPosition()
1382 buf = self.text(line)
1383 if buf.startswith(sys.ps1):
1384 buf = buf.replace(sys.ps1, "")
1385 if buf.startswith(sys.ps2):
1386 buf = buf.replace(sys.ps2, "")
1387 if self.inContinue and not buf[:index - len(sys.ps2)].strip():
1388 self.SendScintilla(cmd)
1389 elif self.racEnabled:
1390 self.dbs.remoteCompletion(
1391 self.__getSelectedDebuggerId(),
1392 buf
1393 )
1394
1395 def __QScintillaLeftDeleteCommand(self, method):
1396 """
1397 Private method to handle a QScintilla delete command working to
1398 the left.
1399
1400 @param method shell method to execute
1401 """
1402 if self.__isCursorOnLastLine():
1403 line, col = self.getCursorPosition()
1404 db = 0
1405 ac = self.isListActive()
1406 oldLength = len(self.text(line))
1407
1408 if self.text(line).startswith(sys.ps1):
1409 if col > len(sys.ps1):
1410 method()
1411 db = 1
1412 elif self.text(line).startswith(sys.ps2):
1413 if col > len(sys.ps2):
1414 method()
1415 db = 1
1416 elif col > 0:
1417 method()
1418 db = 1
1419 if db and ac and self.racEnabled and self.completionText:
1420 delta = len(self.text(line)) - oldLength
1421 self.dbs.remoteCompletion(
1422 self.__getSelectedDebuggerId(),
1423 self.completionText[:delta]
1424 )
1425
1426 def __QScintillaDeleteBack(self):
1427 """
1428 Private method to handle the Backspace key.
1429 """
1430 self.__QScintillaLeftDeleteCommand(self.deleteBack)
1431
1432 def __QScintillaDeleteWordLeft(self):
1433 """
1434 Private method to handle the Delete Word Left command.
1435 """
1436 self.__QScintillaLeftDeleteCommand(self.deleteWordLeft)
1437
1438 def __QScintillaDelete(self):
1439 """
1440 Private method to handle the delete command.
1441 """
1442 if self.__isCursorOnLastLine():
1443 if self.hasSelectedText():
1444 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1445 if self.text(lineFrom).startswith(sys.ps1):
1446 if indexFrom >= len(sys.ps1):
1447 self.delete()
1448 elif self.text(lineFrom).startswith(sys.ps2):
1449 if indexFrom >= len(sys.ps2):
1450 self.delete()
1451 elif indexFrom >= 0:
1452 self.delete()
1453 else:
1454 self.delete()
1455
1456 def __QScintillaDeleteLineLeft(self):
1457 """
1458 Private method to handle the Delete Line Left command.
1459 """
1460 if self.__isCursorOnLastLine():
1461 if self.isListActive():
1462 self.cancelList()
1463
1464 line, col = self.getCursorPosition()
1465 if self.text(line).startswith(sys.ps1):
1466 prompt = sys.ps1
1467 elif self.text(line).startswith(sys.ps2):
1468 prompt = sys.ps2
1469 else:
1470 prompt = ""
1471
1472 self.deleteLineLeft()
1473 self.insertAt(prompt, line, 0)
1474 self.setCursorPosition(line, len(prompt))
1475
1476 def __QScintillaNewline(self, cmd):
1477 """
1478 Private method to handle the Return key.
1479
1480 @param cmd QScintilla command
1481 """
1482 if self.__isCursorOnLastLine():
1483 if self.isListActive():
1484 self.SendScintilla(cmd)
1485 else:
1486 self.incrementalSearchString = ""
1487 self.incrementalSearchActive = False
1488 line, col = self.__getEndPos()
1489 self.setCursorPosition(line, col)
1490 buf = self.text(line)
1491 if buf.startswith(sys.ps1):
1492 buf = buf.replace(sys.ps1, "")
1493 if buf.startswith(sys.ps2):
1494 buf = buf.replace(sys.ps2, "")
1495 self.insert('\n')
1496 self.__executeCommand(buf)
1497 else:
1498 txt = ""
1499 line, col = self.getCursorPosition()
1500 if self.hasSelectedText():
1501 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1502 if line == lineFrom:
1503 txt = self.text(line)[indexFrom:].rstrip()
1504 elif line == lineTo:
1505 txt = self.text(line)[:indexTo]
1506 else:
1507 txt = self.text(line)[col:].rstrip()
1508
1509 if txt:
1510 line, col = self.__getEndPos()
1511 self.setCursorPosition(line, col)
1512 self.insert(txt)
1513
1514 def __QScintillaLeftCommand(self, method, allLinesAllowed=False):
1515 """
1516 Private method to handle a QScintilla command working to the left.
1517
1518 @param method shell method to execute
1519 @param allLinesAllowed flag indicating that the command may be executed
1520 on any line (boolean)
1521 """
1522 if self.__isCursorOnLastLine() or allLinesAllowed:
1523 line, col = self.getCursorPosition()
1524 if self.text(line).startswith(sys.ps1):
1525 if col > len(sys.ps1):
1526 method()
1527 elif self.text(line).startswith(sys.ps2):
1528 if col > len(sys.ps2):
1529 method()
1530 elif col > 0:
1531 method()
1532 else:
1533 method()
1534
1535 def __QScintillaCharLeft(self):
1536 """
1537 Private method to handle the Cursor Left command.
1538 """
1539 self.__QScintillaLeftCommand(self.moveCursorLeft)
1540
1541 def __QScintillaWordLeft(self):
1542 """
1543 Private method to handle the Cursor Word Left command.
1544 """
1545 self.__QScintillaLeftCommand(self.moveCursorWordLeft)
1546
1547 def __QScintillaRightCommand(self, method):
1548 """
1549 Private method to handle a QScintilla command working to the right.
1550
1551 @param method shell method to execute
1552 """
1553 if self.__isCursorOnLastLine():
1554 method()
1555 else:
1556 method()
1557
1558 def __QScintillaCharRight(self):
1559 """
1560 Private method to handle the Cursor Right command.
1561 """
1562 self.__QScintillaRightCommand(self.moveCursorRight)
1563
1564 def __QScintillaWordRight(self):
1565 """
1566 Private method to handle the Cursor Word Right command.
1567 """
1568 self.__QScintillaRightCommand(self.moveCursorWordRight)
1569
1570 def __QScintillaDeleteWordRight(self):
1571 """
1572 Private method to handle the Delete Word Right command.
1573 """
1574 self.__QScintillaRightCommand(self.deleteWordRight)
1575
1576 def __QScintillaDeleteLineRight(self):
1577 """
1578 Private method to handle the Delete Line Right command.
1579 """
1580 self.__QScintillaRightCommand(self.deleteLineRight)
1581
1582 def __QScintillaVCHome(self, cmd):
1583 """
1584 Private method to handle the Home key.
1585
1586 @param cmd QScintilla command
1587 """
1588 if self.isListActive():
1589 self.SendScintilla(cmd)
1590 elif self.__isCursorOnLastLine():
1591 line, col = self.getCursorPosition()
1592 if self.text(line).startswith(sys.ps1):
1593 col = len(sys.ps1)
1594 elif self.text(line).startswith(sys.ps2):
1595 col = len(sys.ps2)
1596 else:
1597 col = 0
1598 self.setCursorPosition(line, col)
1599
1600 def __QScintillaLineEnd(self, cmd):
1601 """
1602 Private method to handle the End key.
1603
1604 @param cmd QScintilla command
1605 """
1606 if self.isListActive():
1607 self.SendScintilla(cmd)
1608 elif self.__isCursorOnLastLine():
1609 self.moveCursorToEOL()
1610
1611 def __QScintillaCursorCommand(self, cmd):
1612 """
1613 Private method to handle the cursor commands.
1614
1615 @param cmd QScintilla command
1616 """
1617 if self.isListActive() or self.isCallTipActive():
1618 if cmd in (QsciScintilla.SCI_LINEUP, QsciScintilla.SCI_LINEDOWN):
1619 self.SendScintilla(cmd)
1620 else:
1621 if self.__historyNavigateByCursor:
1622 if cmd == QsciScintilla.SCI_LINEUP:
1623 self.__QScintillaHistoryUp(cmd)
1624 elif cmd == QsciScintilla.SCI_LINEDOWN:
1625 self.__QScintillaHistoryDown(cmd)
1626 elif cmd == QsciScintilla.SCI_LINESCROLLUP:
1627 self.__QScintillaLineUp(cmd)
1628 elif cmd == QsciScintilla.SCI_LINESCROLLDOWN:
1629 self.__QScintillaLineDown(cmd)
1630 else:
1631 if cmd == QsciScintilla.SCI_LINEUP:
1632 self.__QScintillaLineUp(cmd)
1633 elif cmd == QsciScintilla.SCI_LINEDOWN:
1634 self.__QScintillaLineDown(cmd)
1635 elif cmd == QsciScintilla.SCI_LINESCROLLUP:
1636 self.__QScintillaHistoryUp(cmd)
1637 elif cmd == QsciScintilla.SCI_LINESCROLLDOWN:
1638 self.__QScintillaHistoryDown(cmd)
1639
1640 def __QScintillaLineUp(self, cmd):
1641 """
1642 Private method to handle the cursor up command.
1643
1644 @param cmd QScintilla command
1645 """
1646 self.SendScintilla(QsciScintilla.SCI_LINEUP)
1647
1648 def __QScintillaLineDown(self, cmd):
1649 """
1650 Private method to handle the cursor down command.
1651
1652 @param cmd QScintilla command
1653 """
1654 self.SendScintilla(QsciScintilla.SCI_LINEDOWN)
1655
1656 def __QScintillaHistoryUp(self, cmd):
1657 """
1658 Private method to handle the history up command.
1659
1660 @param cmd QScintilla command
1661 """
1662 if self.isHistoryEnabled():
1663 line, col = self.__getEndPos()
1664 buf = self.text(line)
1665 if buf.startswith(sys.ps1):
1666 buf = buf.replace(sys.ps1, "")
1667 if buf.startswith(sys.ps2):
1668 buf = buf.replace(sys.ps2, "")
1669 if buf and self.incrementalSearchActive:
1670 if (
1671 self.incrementalSearchString and
1672 buf.startswith(self.incrementalSearchString)
1673 ):
1674 idx, found = self.__rsearchHistory(
1675 self.incrementalSearchString, self.__histidx)
1676 if found and idx >= 0:
1677 self.__setHistoryIndex(index=idx)
1678 self.__useHistory()
1679 else:
1680 idx, found = self.__rsearchHistory(buf)
1681 if found and idx >= 0:
1682 self.__setHistoryIndex(index=idx)
1683 self.incrementalSearchString = buf
1684 self.__useHistory()
1685 else:
1686 if self.__historyWrap:
1687 if self.__histidx < 0:
1688 # wrap around
1689 self.__setHistoryIndex(index=len(self.__history) - 1)
1690 else:
1691 self.__setHistoryIndex(index=self.__histidx - 1)
1692 self.__useHistory()
1693 else:
1694 if self.__histidx < 0:
1695 self.__setHistoryIndex(index=len(self.__history) - 1)
1696 self.__useHistory()
1697 elif self.__histidx > 0:
1698 self.__setHistoryIndex(index=self.__histidx - 1)
1699 self.__useHistory()
1700
1701 def __QScintillaHistoryDown(self, cmd):
1702 """
1703 Private method to handle the history down command.
1704
1705 @param cmd QScintilla command
1706 """
1707 if self.isHistoryEnabled():
1708 line, col = self.__getEndPos()
1709 buf = self.text(line)
1710 if buf.startswith(sys.ps1):
1711 buf = buf.replace(sys.ps1, "")
1712 if buf.startswith(sys.ps2):
1713 buf = buf.replace(sys.ps2, "")
1714 if buf and self.incrementalSearchActive:
1715 if (
1716 self.incrementalSearchString and
1717 buf.startswith(self.incrementalSearchString)
1718 ):
1719 idx, found = self.__searchHistory(
1720 self.incrementalSearchString, self.__histidx)
1721 if found and idx >= 0:
1722 self.__setHistoryIndex(index=idx)
1723 self.__useHistory()
1724 else:
1725 idx, found = self.__searchHistory(buf)
1726 if found and idx >= 0:
1727 self.__setHistoryIndex(index=idx)
1728 self.incrementalSearchString = buf
1729 self.__useHistory()
1730 else:
1731 if self.__historyWrap:
1732 if self.__histidx >= len(self.__history) - 1:
1733 # wrap around
1734 self.__setHistoryIndex(index=0)
1735 else:
1736 self.__setHistoryIndex(index=self.__histidx + 1)
1737 self.__useHistory()
1738 else:
1739 if self.__isHistoryIndexValid():
1740 self.__setHistoryIndex(index=self.__histidx + 1)
1741 self.__useHistory()
1742
1743 def __QScintillaCancel(self):
1744 """
1745 Private method to handle the ESC command.
1746 """
1747 if self.isListActive() or self.isCallTipActive():
1748 self.SendScintilla(QsciScintilla.SCI_CANCEL)
1749 else:
1750 if self.incrementalSearchActive:
1751 self.__resetIncrementalHistorySearch()
1752 self.__insertHistory("")
1753
1754 def __QScintillaCharLeftExtend(self):
1755 """
1756 Private method to handle the Extend Selection Left command.
1757 """
1758 self.__QScintillaLeftCommand(self.extendSelectionLeft, True)
1759
1760 def __QScintillaWordLeftExtend(self):
1761 """
1762 Private method to handle the Extend Selection Left one word command.
1763 """
1764 self.__QScintillaLeftCommand(self.extendSelectionWordLeft, True)
1765
1766 def __QScintillaVCHomeExtend(self):
1767 """
1768 Private method to handle the Extend Selection to start of line command.
1769 """
1770 line, col = self.getCursorPosition()
1771 if self.text(line).startswith(sys.ps1):
1772 col = len(sys.ps1)
1773 elif self.text(line).startswith(sys.ps2):
1774 col = len(sys.ps2)
1775 else:
1776 col = 0
1777
1778 self.extendSelectionToBOL()
1779 while col > 0:
1780 self.extendSelectionRight()
1781 col -= 1
1782
1783 def __QScintillaAutoCompletionCommand(self, cmd):
1784 """
1785 Private method to handle a command for autocompletion only.
1786
1787 @param cmd QScintilla command
1788 """
1789 if self.isListActive() or self.isCallTipActive():
1790 self.SendScintilla(cmd)
1791
1792 def __executeCommand(self, cmd, historyIndex=None):
1793 """
1794 Private slot to execute a command.
1795
1796 @param cmd command to be executed by debug client
1797 @type str
1798 @param historyIndex history index to be set
1799 @type int
1800 """
1801 if not self.__inRawMode:
1802 self.inCommandExecution = True
1803 self.interruptCommandExecution = False
1804 if not cmd:
1805 # make sure cmd is a string
1806 cmd = ''
1807
1808 # History Handling
1809 if self.isHistoryEnabled():
1810 if cmd != "" and (
1811 len(self.__history) == 0 or self.__history[-1] != cmd):
1812 if len(self.__history) == self.__maxHistoryEntries:
1813 del self.__history[0]
1814 self.__history.append(cmd)
1815 if self.__historyStyle == ShellHistoryStyle.LINUXSTYLE:
1816 self.__setHistoryIndex(index=-1)
1817 elif self.__historyStyle == ShellHistoryStyle.WINDOWSSTYLE:
1818 if historyIndex is None:
1819 if (
1820 self.__histidx - 1 > 0 and
1821 cmd != self.__history[self.__histidx - 1]
1822 ):
1823 self.__setHistoryIndex(index=-1)
1824 else:
1825 self.__setHistoryIndex(historyIndex)
1826
1827 if cmd.startswith("%"):
1828 if cmd == '%start' or cmd.startswith('%start '):
1829 if not self.passive:
1830 cmdList = cmd.split(None, 1)
1831 if len(cmdList) < 2:
1832 self.dbs.startClient(False)
1833 # start default backend
1834 else:
1835 venvName = cmdList[1]
1836 if venvName == self.tr("Project"):
1837 if self.__project.isOpen():
1838 self.dbs.startClient(
1839 False,
1840 forProject=True,
1841 workingDir=self.__project
1842 .getProjectPath()
1843 )
1844 self.__currentWorkingDirectory = (
1845 self.__project.getProjectPath()
1846 )
1847 else:
1848 self.dbs.startClient(
1849 False,
1850 venvName=self.__currentVenv,
1851 workingDir=self
1852 .__currentWorkingDirectory
1853 )
1854 # same as reset
1855 else:
1856 self.dbs.startClient(False, venvName=venvName)
1857 self.__currentWorkingDirectory = ""
1858 self.__getBanner()
1859 return
1860 elif cmd == '%clear':
1861 # Display the banner.
1862 self.__getBanner()
1863 if not self.passive:
1864 return
1865 else:
1866 cmd = ''
1867 elif cmd in ['%reset', '%restart']:
1868 self.dbs.startClient(
1869 False, venvName=self.__currentVenv,
1870 workingDir=self.__currentWorkingDirectory)
1871 if self.passive:
1872 return
1873 else:
1874 cmd = ''
1875 elif cmd in ['%envs', '%environments']:
1876 venvs = (
1877 ericApp().getObject("VirtualEnvManager")
1878 .getVirtualenvNames()
1879 )
1880 s = (
1881 self.tr('Available Virtual Environments:\n{0}\n')
1882 .format('\n'.join(
1883 "- {0}".format(venv)
1884 for venv in sorted(venvs)
1885 ))
1886 )
1887 self.__write(s)
1888 self.__clientStatement(False)
1889 return
1890 elif cmd == '%which':
1891 s = self.tr("Current Virtual Environment: '{0}'\n").format(
1892 self.__currentVenv)
1893 self.__write(s)
1894 self.__clientStatement(False)
1895 return
1896 elif (
1897 cmd in ["%quit", "%quit()", "%exit", "%exit()"] and
1898 self.__windowed
1899 ):
1900 # call main window quit()
1901 self.vm.quit()
1902 return
1903 else:
1904 self.dbs.remoteStatement(
1905 self.__getSelectedDebuggerId(), cmd)
1906 while self.inCommandExecution:
1907 with contextlib.suppress(KeyboardInterrupt):
1908 QApplication.processEvents()
1909 else:
1910 if not self.__echoInput:
1911 cmd = self.buff
1912 self.buff = ""
1913 elif cmd:
1914 cmd = cmd[len(self.prompt):]
1915 self.__inRawMode = False
1916 self.__echoInput = True
1917
1918 self.dbs.remoteRawInput(self.__rawModeDebuggerId, cmd)
1919
1920 if self.__rawModeQueue:
1921 debuggerId, prompt, echo = self.__rawModeQueue.pop(0)
1922 self.__raw_input(prompt, echo, debuggerId)
1923
1924 def __showVenvName(self):
1925 """
1926 Private method to show the name of the active virtual environment.
1927 """
1928 s = "\n" + self.tr("Current Virtual Environment: '{0}'\n").format(
1929 self.__currentVenv)
1930 self.__write(s)
1931 self.__clientStatement(False)
1932
1933 def __useHistory(self):
1934 """
1935 Private method to display a command from the history.
1936 """
1937 if self.__isHistoryIndexValid():
1938 cmd = self.__history[self.__histidx]
1939 else:
1940 cmd = ""
1941 self.__resetIncrementalHistorySearch()
1942
1943 self.__insertHistory(cmd)
1944
1945 def __insertHistory(self, cmd):
1946 """
1947 Private method to insert a command selected from the history.
1948
1949 @param cmd history entry to be inserted (string)
1950 """
1951 self.setCursorPosition(self.prline, self.prcol)
1952 self.setSelection(self.prline, self.prcol,
1953 self.prline, self.lineLength(self.prline))
1954 self.removeSelectedText()
1955 self.__insertText(cmd)
1956
1957 def __resetIncrementalHistorySearch(self):
1958 """
1959 Private method to reset the incremental history search.
1960 """
1961 self.incrementalSearchString = ""
1962 self.incrementalSearchActive = False
1963
1964 def __searchHistory(self, txt, startIdx=-1):
1965 """
1966 Private method used to search the history.
1967
1968 @param txt text to match at the beginning
1969 @type str
1970 @param startIdx index to start search from
1971 @type int
1972 @return tuple containing the index of found entry and a flag indicating
1973 that something was found
1974 @rtype tuple of (int, bool)
1975 """
1976 idx = 0 if startIdx == -1 else startIdx + 1
1977 while (
1978 idx < len(self.__history) and
1979 not self.__history[idx].startswith(txt)
1980 ):
1981 idx += 1
1982 found = (idx < len(self.__history) and
1983 self.__history[idx].startswith(txt))
1984 return idx, found
1985
1986 def __rsearchHistory(self, txt, startIdx=-1):
1987 """
1988 Private method used to reverse search the history.
1989
1990 @param txt text to match at the beginning
1991 @type str
1992 @param startIdx index to start search from
1993 @type int
1994 @return tuple containing the index of found entry and a flag indicating
1995 that something was found
1996 @rtype tuple of (int, bool)
1997 """
1998 idx = len(self.__history) - 1 if startIdx == -1 else startIdx - 1
1999 while (
2000 idx >= 0 and
2001 not self.__history[idx].startswith(txt)
2002 ):
2003 idx -= 1
2004 found = idx >= 0 and self.__history[idx].startswith(txt)
2005 return idx, found
2006
2007 def focusNextPrevChild(self, nextChild):
2008 """
2009 Public method to stop Tab moving to the next window.
2010
2011 While the user is entering a multi-line command, the movement to
2012 the next window by the Tab key being pressed is suppressed.
2013
2014 @param nextChild next window
2015 @return flag indicating the movement
2016 """
2017 if nextChild and self.inContinue:
2018 return False
2019
2020 return QsciScintillaCompat.focusNextPrevChild(self, nextChild)
2021
2022 def contextMenuEvent(self, ev):
2023 """
2024 Protected method to show our own context menu.
2025
2026 @param ev context menu event (QContextMenuEvent)
2027 """
2028 if not self.__windowed:
2029 self.menu.popup(ev.globalPos())
2030 ev.accept()
2031
2032 def clear(self):
2033 """
2034 Public slot to clear the display.
2035 """
2036 # Display the banner.
2037 self.__getBanner()
2038
2039 def doClearRestart(self):
2040 """
2041 Public slot to handle the 'restart and clear' context menu entry.
2042 """
2043 self.doRestart()
2044 self.clear()
2045
2046 def doRestart(self):
2047 """
2048 Public slot to handle the 'restart' context menu entry.
2049 """
2050 self.dbs.startClient(False, venvName=self.__currentVenv,
2051 workingDir=self.__currentWorkingDirectory)
2052
2053 def __startDebugClient(self, action):
2054 """
2055 Private slot to start a debug client according to the action
2056 triggered.
2057
2058 @param action context menu action that was triggered (QAction)
2059 """
2060 venvName = action.text()
2061 if venvName == self.tr("Project"):
2062 if self.__project.isOpen():
2063 self.__currentWorkingDirectory = (
2064 self.__project.getProjectPath()
2065 )
2066 self.dbs.startClient(False, forProject=True,
2067 workingDir=self.__currentWorkingDirectory)
2068 else:
2069 self.dbs.startClient(False, venvName=venvName)
2070 self.__getBanner()
2071
2072 def handlePreferencesChanged(self):
2073 """
2074 Public slot to handle the preferencesChanged signal.
2075 """
2076 # rebind the lexer
2077 self.__bindLexer(self.language)
2078 self.recolor()
2079
2080 # set margin 0 configuration
2081 self.__setTextDisplay()
2082 self.__setMargin0()
2083
2084 # set the autocompletion and calltips function
2085 self.__setAutoCompletion()
2086 self.__setCallTips()
2087
2088 # do the history related stuff
2089 self.__maxHistoryEntries = Preferences.getShell("MaxHistoryEntries")
2090 for key in list(self.__historyLists.keys()):
2091 self.__historyLists[key] = (
2092 self.__historyLists[key][-self.__maxHistoryEntries:]
2093 )
2094 self.__historyStyle = Preferences.getShell("HistoryStyle")
2095 self.__historyWrap = Preferences.getShell("HistoryWrap")
2096 self.__setHistoryIndex()
2097 if not self.__windowed:
2098 self.hmenu.menuAction().setEnabled(self.isHistoryEnabled())
2099 self.__historyNavigateByCursor = Preferences.getShell(
2100 "HistoryNavigateByCursor")
2101 self.historyStyleChanged.emit(self.__historyStyle)
2102
2103 # do stdout /stderr stuff
2104 showStdOutErr = Preferences.getShell("ShowStdOutErr")
2105 if self.__showStdOutErr != showStdOutErr:
2106 if showStdOutErr:
2107 self.dbs.clientProcessStdout.connect(self.__writeStdOut)
2108 self.dbs.clientProcessStderr.connect(self.__writeStdErr)
2109 else:
2110 self.dbs.clientProcessStdout.disconnect(self.__writeStdOut)
2111 self.dbs.clientProcessStderr.disconnect(self.__writeStdErr)
2112 self.__showStdOutErr = showStdOutErr
2113
2114 @pyqtSlot(list, str)
2115 def __showCompletions(self, completions, text):
2116 """
2117 Private method to display the possible completions.
2118
2119 @param completions list of possible completions (list of strings)
2120 @param text text that is about to be completed (string)
2121 """
2122 if len(completions) == 0:
2123 return
2124
2125 if len(completions) > 1:
2126 completions.sort()
2127 self.showUserList(1, completions)
2128 self.completionText = text
2129 else:
2130 txt = completions[0]
2131 if text != "":
2132 txt = txt.replace(text, "")
2133 self.__insertText(txt)
2134 self.completionText = ""
2135
2136 def __completionListSelected(self, listId, txt):
2137 """
2138 Private slot to handle the selection from the completion list.
2139
2140 @param listId the ID of the user list (should be 1) (integer)
2141 @param txt the selected text (string)
2142 """
2143 if listId == 1:
2144 if self.completionText != "":
2145 txt = txt.replace(self.completionText, "")
2146 self.__insertText(txt)
2147 self.completionText = ""
2148
2149 #################################################################
2150 ## Drag and Drop Support
2151 #################################################################
2152
2153 def dragEnterEvent(self, event):
2154 """
2155 Protected method to handle the drag enter event.
2156
2157 @param event the drag enter event (QDragEnterEvent)
2158 """
2159 self.inDragDrop = (
2160 event.mimeData().hasUrls() or
2161 event.mimeData().hasText()
2162 )
2163 if self.inDragDrop:
2164 event.acceptProposedAction()
2165 else:
2166 super().dragEnterEvent(event)
2167
2168 def dragMoveEvent(self, event):
2169 """
2170 Protected method to handle the drag move event.
2171
2172 @param event the drag move event (QDragMoveEvent)
2173 """
2174 if self.inDragDrop:
2175 event.accept()
2176 else:
2177 super().dragMoveEvent(event)
2178
2179 def dragLeaveEvent(self, event):
2180 """
2181 Protected method to handle the drag leave event.
2182
2183 @param event the drag leave event (QDragLeaveEvent)
2184 """
2185 if self.inDragDrop:
2186 self.inDragDrop = False
2187 event.accept()
2188 else:
2189 super().dragLeaveEvent(event)
2190
2191 def dropEvent(self, event):
2192 """
2193 Protected method to handle the drop event.
2194
2195 @param event the drop event (QDropEvent)
2196 """
2197 if event.mimeData().hasUrls() and not self.__windowed:
2198 for url in event.mimeData().urls():
2199 fname = url.toLocalFile()
2200 if fname:
2201 if not pathlib.Path(fname).is_dir():
2202 self.vm.openSourceFile(fname)
2203 else:
2204 EricMessageBox.information(
2205 self,
2206 self.tr("Drop Error"),
2207 self.tr("""<p><b>{0}</b> is not a file.</p>""")
2208 .format(fname))
2209 event.acceptProposedAction()
2210 elif event.mimeData().hasText():
2211 s = event.mimeData().text()
2212 if s:
2213 event.acceptProposedAction()
2214 self.executeLines(s)
2215 del s
2216 else:
2217 super().dropEvent(event)
2218
2219 self.inDragDrop = False
2220
2221 def focusInEvent(self, event):
2222 """
2223 Protected method called when the shell receives focus.
2224
2225 @param event the event object (QFocusEvent)
2226 """
2227 if not self.__actionsAdded:
2228 self.addActions(self.vm.editorActGrp.actions())
2229 self.addActions(self.vm.copyActGrp.actions())
2230 self.addActions(self.vm.viewActGrp.actions())
2231 if not self.__windowed:
2232 self.__searchShortcut = QShortcut(
2233 self.vm.searchAct.shortcut(), self,
2234 self.__find, self.__find)
2235 self.__searchNextShortcut = QShortcut(
2236 self.vm.searchNextAct.shortcut(), self,
2237 self.__searchNext, self.__searchNext)
2238 self.__searchPrevShortcut = QShortcut(
2239 self.vm.searchPrevAct.shortcut(), self,
2240 self.__searchPrev, self.__searchPrev)
2241
2242 with contextlib.suppress(AttributeError):
2243 self.vm.editActGrp.setEnabled(False)
2244 self.vm.editorActGrp.setEnabled(True)
2245 self.vm.copyActGrp.setEnabled(True)
2246 self.vm.viewActGrp.setEnabled(True)
2247 self.vm.searchActGrp.setEnabled(False)
2248 if not self.__windowed:
2249 self.__searchShortcut.setEnabled(True)
2250 self.__searchNextShortcut.setEnabled(True)
2251 self.__searchPrevShortcut.setEnabled(True)
2252 self.setCaretWidth(self.caretWidth)
2253 self.setCursorFlashTime(QApplication.cursorFlashTime())
2254
2255 super().focusInEvent(event)
2256
2257 def focusOutEvent(self, event):
2258 """
2259 Protected method called when the shell loses focus.
2260
2261 @param event the event object (QFocusEvent)
2262 """
2263 with contextlib.suppress(AttributeError):
2264 self.vm.editorActGrp.setEnabled(False)
2265 if not self.__windowed:
2266 self.__searchShortcut.setEnabled(False)
2267 self.__searchNextShortcut.setEnabled(False)
2268 self.__searchPrevShortcut.setEnabled(False)
2269 self.setCaretWidth(0)
2270 super().focusOutEvent(event)
2271
2272 def insert(self, txt):
2273 """
2274 Public slot to insert text at the current cursor position.
2275
2276 The cursor is advanced to the end of the inserted text.
2277
2278 @param txt text to be inserted (string)
2279 """
2280 txt = Utilities.filterAnsiSequences(txt)
2281 length = len(txt)
2282 line, col = self.getCursorPosition()
2283 self.insertAt(txt, line, col)
2284 if re.search(self.linesepRegExp, txt) is not None:
2285 line += 1
2286 self.setCursorPosition(line, col + length)
2287
2288 def __configure(self):
2289 """
2290 Private method to open the configuration dialog.
2291 """
2292 ericApp().getObject("UserInterface").showPreferences("shellPage")
2293
2294 def __find(self):
2295 """
2296 Private slot to show the find widget.
2297 """
2298 txt = self.selectedText()
2299 self.__mainWindow.showFind(txt)
2300
2301 def __searchNext(self):
2302 """
2303 Private method to search for the next occurrence.
2304 """
2305 if self.__lastSearch:
2306 self.searchNext(*self.__lastSearch)
2307
2308 def searchNext(self, txt, caseSensitive, wholeWord, regexp):
2309 """
2310 Public method to search the next occurrence of the given text.
2311
2312 @param txt text to search for
2313 @type str
2314 @param caseSensitive flag indicating to perform a case sensitive
2315 search
2316 @type bool
2317 @param wholeWord flag indicating to search for whole words
2318 only
2319 @type bool
2320 @param regexp flag indicating a regular expression search
2321 @type bool
2322 """
2323 self.__lastSearch = (txt, caseSensitive, wholeWord, regexp)
2324 posixMode = Preferences.getEditor("SearchRegexpMode") == 0 and regexp
2325 cxx11Mode = Preferences.getEditor("SearchRegexpMode") == 1 and regexp
2326 ok = self.findFirst(
2327 txt, regexp, caseSensitive, wholeWord, True, forward=True,
2328 posix=posixMode, cxx11=cxx11Mode)
2329 self.searchStringFound.emit(ok)
2330
2331 def __searchPrev(self):
2332 """
2333 Private method to search for the next occurrence.
2334 """
2335 if self.__lastSearch:
2336 self.searchPrev(*self.__lastSearch)
2337
2338 def searchPrev(self, txt, caseSensitive, wholeWord, regexp):
2339 """
2340 Public method to search the previous occurrence of the given text.
2341
2342 @param txt text to search for
2343 @type str
2344 @param caseSensitive flag indicating to perform a case sensitive
2345 search
2346 @type bool
2347 @param wholeWord flag indicating to search for whole words
2348 only
2349 @type bool
2350 @param regexp flag indicating a regular expression search
2351 @type bool
2352 """
2353 self.__lastSearch = (txt, caseSensitive, wholeWord, regexp)
2354 if self.hasSelectedText():
2355 line, index = self.getSelection()[:2]
2356 else:
2357 line, index = -1, -1
2358 posixMode = Preferences.getEditor("SearchRegexpMode") == 0 and regexp
2359 cxx11Mode = Preferences.getEditor("SearchRegexpMode") == 1 and regexp
2360 ok = self.findFirst(
2361 txt, regexp, caseSensitive, wholeWord, True,
2362 forward=False, line=line, index=index, posix=posixMode,
2363 cxx11=cxx11Mode)
2364 self.searchStringFound.emit(ok)
2365
2366 def historyStyle(self):
2367 """
2368 Public method to get the shell history style.
2369
2370 @return shell history style
2371 @rtype ShellHistoryStyle
2372 """
2373 return self.__historyStyle
2374
2375 def isHistoryEnabled(self):
2376 """
2377 Public method to check, if the history is enabled.
2378
2379 @return flag indicating if history is enabled
2380 @rtype bool
2381 """
2382 return self.__historyStyle != ShellHistoryStyle.DISABLED
2383
2384 def saveContents(self):
2385 """
2386 Public method to save the current contents to a file.
2387 """
2388 txt = self.text()
2389 if txt:
2390 fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
2391 self,
2392 self.tr("Save Shell Contents"),
2393 Preferences.getMultiProject("Workspace"),
2394 self.tr("Text Files (*.txt);;All Files (*)"),
2395 None,
2396 EricFileDialog.DontConfirmOverwrite
2397 )
2398
2399 if fn:
2400 if fn.endswith("."):
2401 fn = fn[:-1]
2402
2403 fpath = pathlib.Path(fn)
2404 if not fpath.suffix:
2405 ex = selectedFilter.split("(*")[1].split(")")[0]
2406 if ex:
2407 fpath = fpath.with_suffix(ex)
2408 if fpath.exists():
2409 res = EricMessageBox.yesNo(
2410 self,
2411 self.tr("Save Shell Contents"),
2412 self.tr("<p>The file <b>{0}</b> already exists."
2413 " Overwrite it?</p>").format(fpath),
2414 icon=EricMessageBox.Warning)
2415 if not res:
2416 return
2417 try:
2418 with fpath.open("w", encoding="utf-8") as f:
2419 f.write(txt)
2420 except (OSError, UnicodeError) as why:
2421 EricMessageBox.critical(
2422 self,
2423 self.tr("Save Shell Contents"),
2424 self.tr('<p>The file <b>{0}</b> could not be saved.<br/>'
2425 'Reason: {1}</p>')
2426 .format(fpath, str(why)))
2427
2428 #################################################################
2429 ## Project Support
2430 #################################################################
2431
2432 def __projectOpened(self):
2433 """
2434 Private slot to start the shell for the opened project.
2435 """
2436 if Preferences.getProject("RestartShellForProject"):
2437 self.dbs.startClient(False, forProject=True,
2438 workingDir=self.__project.getProjectPath())
2439 self.__currentWorkingDirectory = self.__project.getProjectPath()
2440 self.__getBanner()
2441
2442 def __projectClosed(self):
2443 """
2444 Private slot to restart the default shell when the project is closed.
2445 """
2446 if Preferences.getProject("RestartShellForProject"):
2447 self.dbs.startClient(False)
2448 self.__getBanner()
2449
2450 #
2451 # eflag: noqa = M601

eric ide

mercurial