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