|
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 |