QScintilla/Terminal.py

changeset 2474
8727522a69d5
parent 2472
4860fe0ed4a6
child 2476
3137ed00325e
equal deleted inserted replaced
2472:4860fe0ed4a6 2474:8727522a69d5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a simple terminal based on QScintilla.
8 """
9
10 import sys
11 import os
12 import re
13
14 from PyQt4.QtCore import pyqtSignal, QSignalMapper, QTimer, QByteArray, QProcess, Qt, \
15 QEvent
16 from PyQt4.QtGui import QDialog, QInputDialog, QApplication, QMenu, QPalette, QFont, \
17 QWidget, QHBoxLayout, QShortcut
18 from PyQt4.Qsci import QsciScintilla
19
20 from E5Gui.E5Application import e5App
21
22 from .QsciScintillaCompat import QsciScintillaCompat
23
24 import Preferences
25 import Utilities
26
27 import UI.PixmapCache
28
29
30 class TerminalAssembly(QWidget):
31 """
32 Class implementing the containing widget for the terminal.
33 """
34 def __init__(self, vm, parent=None):
35 """
36 Constructor
37
38 @param vm reference to the viewmanager object
39 @param parent reference to the parent widget (QWidget)
40 """
41 super().__init__(parent)
42
43 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))
44
45 self.__terminal = Terminal(vm, self)
46
47 from UI.SearchWidget import SearchWidget
48 self.__searchWidget = SearchWidget(self.__terminal, self)
49 self.__searchWidget.hide()
50
51 self.__layout = QHBoxLayout(self)
52 self.__layout.setContentsMargins(1, 1, 1, 1)
53 self.__layout.addWidget(self.__terminal)
54 self.__layout.addWidget(self.__searchWidget)
55
56 self.__searchWidget.searchNext.connect(self.__terminal.searchNext)
57 self.__searchWidget.searchPrevious.connect(self.__terminal.searchPrev)
58 self.__terminal.searchStringFound.connect(self.__searchWidget.searchStringFound)
59
60 def showFind(self, txt=""):
61 """
62 Public method to display the search widget.
63
64 @param txt text to be shown in the combo (string)
65 """
66 self.__searchWidget.showFind(txt)
67
68 def terminal(self):
69 """
70 Public method to get a reference to the terminal widget.
71
72 @return reference to the terminal widget (Terminal)
73 """
74 return self.__terminal
75
76
77 class Terminal(QsciScintillaCompat):
78 """
79 Class implementing a simple terminal based on QScintilla.
80
81 A user can enter commands that are executed by a shell process.
82
83 @signal searchStringFound(found) emitted to indicate the search result (boolean)
84 """
85 searchStringFound = pyqtSignal(bool)
86
87 def __init__(self, vm, parent=None):
88 """
89 Constructor
90
91 @param vm reference to the viewmanager object
92 @param parent parent widget (QWidget)
93 """
94 super().__init__(parent)
95 self.setUtf8(True)
96
97 self.vm = vm
98 self.__mainWindow = parent
99 self.__lastSearch = ()
100
101 self.linesepRegExp = r"\r\n|\n|\r"
102
103 self.setWindowTitle(self.trUtf8('Terminal'))
104
105 self.setWhatsThis(self.trUtf8(
106 """<b>The Terminal Window</b>"""
107 """<p>This is a very simple terminal like window, that runs a shell"""
108 """ process in the background.</p>"""
109 """<p>The process can be stopped and started via the context menu. Some"""
110 """ Ctrl command may be sent as well. However, the shell may ignore"""
111 """ them.</p>"""
112 """<p>You can use the cursor keys while entering commands. There is also a"""
113 """ history of commands that can be recalled using the up and down cursor"""
114 """ keys. Pressing the up or down key after some text has been entered will"""
115 """ start an incremental search.</p>"""
116 ))
117
118 self.ansi_re = re.compile("\033\[\??[\d;]*\w")
119
120 # Initialise instance variables.
121 self.prline = 0
122 self.prcol = 0
123 self.inDragDrop = False
124 self.lexer_ = None
125
126 # Initialize history
127 self.maxHistoryEntries = Preferences.getTerminal("MaxHistoryEntries")
128 self.history = []
129 self.histidx = -1
130
131 # clear QScintilla defined keyboard commands
132 # we do our own handling through the view manager
133 self.clearAlternateKeys()
134 self.clearKeys()
135 self.__actionsAdded = False
136
137 # Create the history context menu
138 self.hmenu = QMenu(self.trUtf8('History'))
139 self.hmenu.addAction(self.trUtf8('Select entry'), self.__selectHistory)
140 self.hmenu.addAction(self.trUtf8('Show'), self.__showHistory)
141 self.hmenu.addAction(self.trUtf8('Clear'), self.__clearHistory)
142
143 # Create a little context menu to send Ctrl-C, Ctrl-D or Ctrl-Z
144 self.csm = QSignalMapper(self)
145 self.csm.mapped[int].connect(self.__sendCtrl)
146
147 self.cmenu = QMenu(self.trUtf8('Ctrl Commands'))
148 act = self.cmenu.addAction(self.trUtf8('Ctrl-C'))
149 self.csm.setMapping(act, 3)
150 act.triggered[()].connect(self.csm.map)
151 act = self.cmenu.addAction(self.trUtf8('Ctrl-D'))
152 self.csm.setMapping(act, 4)
153 act.triggered[()].connect(self.csm.map)
154 act = self.cmenu.addAction(self.trUtf8('Ctrl-Z'))
155 self.csm.setMapping(act, 26)
156 act.triggered[()].connect(self.csm.map)
157
158 # Create a little context menu
159 self.menu = QMenu(self)
160 self.menu.addAction(self.trUtf8('Cut'), self.cut)
161 self.menu.addAction(self.trUtf8('Copy'), self.copy)
162 self.menu.addAction(self.trUtf8('Paste'), self.paste)
163 self.menu.addSeparator()
164 self.menu.addAction(self.trUtf8('Find'), self.__find)
165 self.menu.addSeparator()
166 self.menu.addMenu(self.hmenu)
167 self.menu.addSeparator()
168 self.menu.addAction(self.trUtf8('Clear'), self.clear)
169 self.__startAct = self.menu.addAction(self.trUtf8("Start"), self.__startShell)
170 self.__stopAct = self.menu.addAction(self.trUtf8("Stop"), self.__stopShell)
171 self.__resetAct = self.menu.addAction(self.trUtf8('Reset'), self.__reset)
172 self.menu.addSeparator()
173 self.__ctrlAct = self.menu.addMenu(self.cmenu)
174 self.menu.addSeparator()
175 self.menu.addAction(self.trUtf8("Configure..."), self.__configure)
176
177 self.__bindLexer()
178 self.__setTextDisplay()
179 self.__setMargin0()
180
181 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))
182
183 self.incrementalSearchString = ""
184 self.incrementalSearchActive = False
185
186 self.supportedEditorCommands = {
187 QsciScintilla.SCI_LINEDELETE: self.__clearCurrentLine,
188 QsciScintilla.SCI_NEWLINE: self.__QScintillaNewline,
189
190 QsciScintilla.SCI_DELETEBACK: self.__QScintillaDeleteBack,
191 QsciScintilla.SCI_CLEAR: self.__QScintillaDelete,
192 QsciScintilla.SCI_DELWORDLEFT: self.__QScintillaDeleteWordLeft,
193 QsciScintilla.SCI_DELWORDRIGHT: self.__QScintillaDeleteWordRight,
194 QsciScintilla.SCI_DELLINELEFT: self.__QScintillaDeleteLineLeft,
195 QsciScintilla.SCI_DELLINERIGHT: self.__QScintillaDeleteLineRight,
196
197 QsciScintilla.SCI_CHARLEFT: self.__QScintillaCharLeft,
198 QsciScintilla.SCI_CHARRIGHT: self.__QScintillaCharRight,
199 QsciScintilla.SCI_WORDLEFT: self.__QScintillaWordLeft,
200 QsciScintilla.SCI_WORDRIGHT: self.__QScintillaWordRight,
201 QsciScintilla.SCI_VCHOME: self.__QScintillaVCHome,
202 QsciScintilla.SCI_LINEEND: self.__QScintillaLineEnd,
203 QsciScintilla.SCI_LINEUP: self.__QScintillaLineUp,
204 QsciScintilla.SCI_LINEDOWN: self.__QScintillaLineDown,
205
206 QsciScintilla.SCI_CHARLEFTEXTEND: self.__QScintillaCharLeftExtend,
207 QsciScintilla.SCI_CHARRIGHTEXTEND: self.extendSelectionRight,
208 QsciScintilla.SCI_WORDLEFTEXTEND: self.__QScintillaWordLeftExtend,
209 QsciScintilla.SCI_WORDRIGHTEXTEND: self.extendSelectionWordRight,
210 QsciScintilla.SCI_VCHOMEEXTEND: self.__QScintillaVCHomeExtend,
211 QsciScintilla.SCI_LINEENDEXTEND: self.extendSelectionToEOL,
212 }
213
214 self.__ioEncoding = Preferences.getSystem("IOEncoding")
215
216 self.__process = QProcess()
217 self.__process.setProcessChannelMode(QProcess.MergedChannels)
218 self.__process.setReadChannel(QProcess.StandardOutput)
219
220 self.__process.readyReadStandardOutput.connect(self.__readOutput)
221 self.__process.started.connect(self.__started)
222 self.__process.finished.connect(self.__finished)
223
224 self.__ctrl = {}
225 for ascii_number, letter in enumerate("abcdefghijklmnopqrstuvwxyz"):
226 self.__ctrl[letter] = chr(ascii_number + 1)
227
228 self.__lastPos = (0, 0)
229
230 self.grabGesture(Qt.PinchGesture)
231
232 self.__startShell()
233
234 def __readOutput(self):
235 """
236 Private method to process the output of the shell.
237 """
238 output = str(self.__process.readAllStandardOutput(),
239 self.__ioEncoding, 'replace')
240 self.__write(self.ansi_re.sub("", output))
241 self.__lastPos = self.__getEndPos()
242
243 def __started(self):
244 """
245 Private method called, when the shell process has started.
246 """
247 if not Utilities.isWindowsPlatform():
248 QTimer.singleShot(250, self.clear)
249
250 self.__startAct.setEnabled(False)
251 self.__stopAct.setEnabled(True)
252 self.__resetAct.setEnabled(True)
253 self.__ctrlAct.setEnabled(True)
254
255 def __finished(self):
256 """
257 Private method called, when the shell process has finished.
258 """
259 super().clear()
260
261 self.__startAct.setEnabled(True)
262 self.__stopAct.setEnabled(False)
263 self.__resetAct.setEnabled(False)
264 self.__ctrlAct.setEnabled(False)
265
266 def __send(self, data):
267 """
268 Private method to send data to the shell process.
269
270 @param data data to be sent to the shell process (string)
271 """
272 pdata = QByteArray()
273 pdata.append(bytes(data, encoding="utf-8"))
274 self.__process.write(pdata)
275
276 def __sendCtrl(self, cmd):
277 """
278 Private slot to send a control command to the shell process.
279
280 @param the control command to be sent (integer)
281 """
282 self.__send(chr(cmd))
283
284 def closeTerminal(self):
285 """
286 Public method to shutdown the terminal.
287 """
288 self.__stopShell()
289 self.saveHistory()
290
291 def __bindLexer(self):
292 """
293 Private slot to set the lexer.
294 """
295 if Utilities.isWindowsPlatform():
296 self.language = "Batch"
297 else:
298 self.language = "Bash"
299 if Preferences.getTerminal("SyntaxHighlightingEnabled"):
300 from . import Lexers
301 self.lexer_ = Lexers.getLexer(self.language, self)
302 else:
303 self.lexer_ = None
304
305 if self.lexer_ is None:
306 self.setLexer(None)
307 font = Preferences.getTerminal("MonospacedFont")
308 self.monospacedStyles(font)
309 return
310
311 # get the font for style 0 and set it as the default font
312 key = 'Scintilla/{0}/style0/font'.format(self.lexer_.language())
313 fdesc = Preferences.Prefs.settings.value(key)
314 if fdesc is not None:
315 font = QFont(fdesc[0], int(fdesc[1]))
316 self.lexer_.setDefaultFont(font)
317 self.setLexer(self.lexer_)
318 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla")
319
320 self.lexer_.setDefaultColor(self.lexer_.color(0))
321 self.lexer_.setDefaultPaper(self.lexer_.paper(0))
322
323 def __setMargin0(self):
324 """
325 Private method to configure margin 0.
326 """
327 # set the settings for all margins
328 self.setMarginsFont(Preferences.getTerminal("MarginsFont"))
329 self.setMarginsForegroundColor(Preferences.getEditorColour("MarginsForeground"))
330 self.setMarginsBackgroundColor(Preferences.getEditorColour("MarginsBackground"))
331
332 # set margin 0 settings
333 linenoMargin = Preferences.getTerminal("LinenoMargin")
334 self.setMarginLineNumbers(0, linenoMargin)
335 if linenoMargin:
336 self.setMarginWidth(0, ' ' + '8' * Preferences.getTerminal("LinenoWidth"))
337 else:
338 self.setMarginWidth(0, 0)
339
340 # disable margins 1 and 2
341 self.setMarginWidth(1, 0)
342 self.setMarginWidth(2, 0)
343
344 def __setTextDisplay(self):
345 """
346 Private method to configure the text display.
347 """
348 self.setTabWidth(Preferences.getEditor("TabWidth"))
349 if Preferences.getEditor("ShowWhitespace"):
350 self.setWhitespaceVisibility(QsciScintilla.WsVisible)
351 try:
352 self.setWhitespaceForegroundColor(
353 Preferences.getEditorColour("WhitespaceForeground"))
354 self.setWhitespaceBackgroundColor(
355 Preferences.getEditorColour("WhitespaceBackground"))
356 self.setWhitespaceSize(
357 Preferences.getEditor("WhitespaceSize"))
358 except AttributeError:
359 # QScintilla before 2.5 doesn't support this
360 pass
361 else:
362 self.setWhitespaceVisibility(QsciScintilla.WsInvisible)
363 self.setEolVisibility(Preferences.getEditor("ShowEOL"))
364 if Preferences.getEditor("BraceHighlighting"):
365 self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
366 else:
367 self.setBraceMatching(QsciScintilla.NoBraceMatch)
368 self.setMatchedBraceForegroundColor(
369 Preferences.getEditorColour("MatchingBrace"))
370 self.setMatchedBraceBackgroundColor(
371 Preferences.getEditorColour("MatchingBraceBack"))
372 self.setUnmatchedBraceForegroundColor(
373 Preferences.getEditorColour("NonmatchingBrace"))
374 self.setUnmatchedBraceBackgroundColor(
375 Preferences.getEditorColour("NonmatchingBraceBack"))
376 if Preferences.getEditor("CustomSelectionColours"):
377 self.setSelectionBackgroundColor(
378 Preferences.getEditorColour("SelectionBackground"))
379 else:
380 self.setSelectionBackgroundColor(
381 QApplication.palette().color(QPalette.Highlight))
382 if Preferences.getEditor("ColourizeSelText"):
383 self.resetSelectionForegroundColor()
384 elif Preferences.getEditor("CustomSelectionColours"):
385 self.setSelectionForegroundColor(
386 Preferences.getEditorColour("SelectionForeground"))
387 else:
388 self.setSelectionForegroundColor(
389 QApplication.palette().color(QPalette.HighlightedText))
390 self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol"))
391 self.setCaretForegroundColor(
392 Preferences.getEditorColour("CaretForeground"))
393 self.setCaretLineBackgroundColor(
394 Preferences.getEditorColour("CaretLineBackground"))
395 self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible"))
396 self.caretWidth = Preferences.getEditor("CaretWidth")
397 self.setCaretWidth(self.caretWidth)
398 self.setWrapMode(QsciScintilla.WrapNone)
399 self.useMonospaced = Preferences.getTerminal("UseMonospacedFont")
400 self.__setMonospaced(self.useMonospaced)
401
402 self.setCursorFlashTime(QApplication.cursorFlashTime())
403
404 if Preferences.getEditor("OverrideEditAreaColours"):
405 self.setColor(Preferences.getEditorColour("EditAreaForeground"))
406 self.setPaper(Preferences.getEditorColour("EditAreaBackground"))
407
408 def __setMonospaced(self, on):
409 """
410 Private method to set/reset a monospaced font.
411
412 @param on flag to indicate usage of a monospace font (boolean)
413 """
414 if on:
415 f = Preferences.getTerminal("MonospacedFont")
416 self.monospacedStyles(f)
417 else:
418 if not self.lexer_:
419 self.clearStyles()
420 self.__setMargin0()
421 self.setFont(Preferences.getTerminal("MonospacedFont"))
422
423 self.useMonospaced = on
424
425 def loadHistory(self):
426 """
427 Public method to load the history.
428 """
429 hl = Preferences.Prefs.settings.value("Terminal/History")
430 if hl is not None:
431 self.history = hl[-self.maxHistoryEntries:]
432 else:
433 self.history = []
434
435 def reloadHistory(self):
436 """
437 Public method to reload the history.
438 """
439 self.loadHistory(self.clientType)
440 self.history = self.historyLists[self.clientType]
441 self.histidx = -1
442
443 def saveHistory(self):
444 """
445 Public method to save the history.
446 """
447 Preferences.Prefs.settings.setValue("Terminal/History", self.history)
448
449 def getHistory(self):
450 """
451 Public method to get the history.
452
453 @return reference to the history list (list of strings)
454 """
455 return self.history
456
457 def __clearHistory(self):
458 """
459 Private slot to clear the current history.
460 """
461 self.history = []
462
463 def __selectHistory(self):
464 """
465 Private slot to select a history entry to execute.
466 """
467 cmd, ok = QInputDialog.getItem(
468 self,
469 self.trUtf8("Select History"),
470 self.trUtf8("Select the history entry to execute (most recent shown last)."),
471 self.history,
472 0, False)
473 if ok:
474 self.__insertHistory(cmd)
475
476 def __showHistory(self):
477 """
478 Private slot to show the shell history dialog.
479 """
480 from .ShellHistoryDialog import ShellHistoryDialog
481 dlg = ShellHistoryDialog(self.history, self.vm, self)
482 if dlg.exec_() == QDialog.Accepted:
483 self.history = dlg.getHistory()
484 self.histidx = -1
485
486 def __getEndPos(self):
487 """
488 Private method to return the line and column of the last character.
489
490 @return tuple of two values (int, int) giving the line and column
491 """
492 line = self.lines() - 1
493 return (line, len(self.text(line)))
494
495 def __write(self, s):
496 """
497 Private method to display some text.
498
499 @param s text to be displayed (string)
500 """
501 line, col = self.__getEndPos()
502 self.setCursorPosition(line, col)
503 self.insert(s)
504 self.prline, self.prcol = self.getCursorPosition()
505 self.ensureCursorVisible()
506 self.ensureLineVisible(self.prline)
507
508 def __clearCurrentLine(self):
509 """
510 Private method to clear the line containing the cursor.
511 """
512 line, col = self.getCursorPosition()
513 if self.text(line).startswith(sys.ps1):
514 col = len(sys.ps1)
515 elif self.text(line).startswith(sys.ps2):
516 col = len(sys.ps2)
517 else:
518 col = 0
519 self.setCursorPosition(line, col)
520 self.deleteLineRight()
521
522 def __insertText(self, s):
523 """
524 Private method to insert some text at the current cursor position.
525
526 @param s text to be inserted (string)
527 """
528 line, col = self.getCursorPosition()
529 self.insertAt(s, line, col)
530 self.setCursorPosition(line, col + len(s))
531
532 def __insertTextAtEnd(self, s):
533 """
534 Private method to insert some text at the end of the command line.
535
536 @param s text to be inserted (string)
537 """
538 line, col = self.__getEndPos()
539 self.setCursorPosition(line, col)
540 self.insert(s)
541 self.prline, self.prcol = self.getCursorPosition()
542
543 def mousePressEvent(self, event):
544 """
545 Protected method to handle the mouse press event.
546
547 @param event the mouse press event (QMouseEvent)
548 """
549 self.setFocus()
550 super().mousePressEvent(event)
551
552 def wheelEvent(self, evt):
553 """
554 Protected method to handle wheel events.
555
556 @param evt reference to the wheel event (QWheelEvent)
557 """
558 if evt.modifiers() & Qt.ControlModifier:
559 if evt.delta() < 0:
560 self.zoomOut()
561 else:
562 self.zoomIn()
563 evt.accept()
564 return
565
566 super().wheelEvent(evt)
567
568
569 def event(self, evt):
570 """
571 Protected method handling events.
572
573 @param evt reference to the event (QEvent)
574 @return flag indicating, if the event was handled (boolean)
575 """
576 if evt.type() == QEvent.Gesture:
577 self.gestureEvent(evt)
578 return True
579
580 return super().event(evt)
581
582 def gestureEvent(self, evt):
583 """
584 Protected method handling gesture events.
585
586 @param evt reference to the gesture event (QGestureEvent
587 """
588 pinch = evt.gesture(Qt.PinchGesture)
589 if pinch:
590 if pinch.state() == Qt.GestureStarted:
591 zoom = (self.getZoom() + 10) / 10.0
592 pinch.setScaleFactor(zoom)
593 else:
594 zoom = int(pinch.scaleFactor() * 10) - 10
595 if zoom <= -9:
596 zoom = -9
597 pinch.setScaleFactor(0.1)
598 elif zoom >= 20:
599 zoom = 20
600 pinch.setScaleFactor(3.0)
601 self.zoomTo(zoom)
602 evt.accept()
603
604 def editorCommand(self, cmd):
605 """
606 Public method to perform an editor command.
607
608 @param cmd the scintilla command to be performed
609 """
610 try:
611 self.supportedEditorCommands[cmd]()
612 except TypeError:
613 self.supportedEditorCommands[cmd](cmd)
614 except KeyError:
615 pass
616
617 def __isCursorOnLastLine(self):
618 """
619 Private method to check, if the cursor is on the last line.
620 """
621 cline, ccol = self.getCursorPosition()
622 return cline == self.lines() - 1
623
624 def keyPressEvent(self, ev):
625 """
626 Re-implemented to handle the user input a key at a time.
627
628 @param ev key event (QKeyEvent)
629 """
630 txt = ev.text()
631
632 # See it is text to insert.
633 if len(txt) and txt >= " ":
634 if not self.__isCursorOnLastLine():
635 line, col = self.__getEndPos()
636 self.setCursorPosition(line, col)
637 self.prline, self.prcol = self.getCursorPosition()
638 super().keyPressEvent(ev)
639 self.incrementalSearchActive = True
640 else:
641 ev.ignore()
642
643 def __QScintillaLeftDeleteCommand(self, method):
644 """
645 Private method to handle a QScintilla delete command working to the left.
646
647 @param method shell method to execute
648 """
649 if self.__isCursorOnLastLine():
650 line, col = self.getCursorPosition()
651 if col > self.__lastPos[1]:
652 method()
653
654 def __QScintillaDeleteBack(self):
655 """
656 Private method to handle the Backspace key.
657 """
658 self.__QScintillaLeftDeleteCommand(self.deleteBack)
659
660 def __QScintillaDeleteWordLeft(self):
661 """
662 Private method to handle the Delete Word Left command.
663 """
664 self.__QScintillaLeftDeleteCommand(self.deleteWordLeft)
665
666 def __QScintillaDelete(self):
667 """
668 Private method to handle the delete command.
669 """
670 if self.__isCursorOnLastLine():
671 if self.hasSelectedText():
672 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
673 if indexFrom >= self.__lastPos[1]:
674 self.delete()
675 self.setSelection(lineTo, indexTo, lineTo, indexTo)
676 else:
677 self.delete()
678
679 def __QScintillaDeleteLineLeft(self):
680 """
681 Private method to handle the Delete Line Left command.
682 """
683 if self.__isCursorOnLastLine():
684 if self.isListActive():
685 self.cancelList()
686
687 line, col = self.getCursorPosition()
688 prompt = self.text(line)[:self.__lastPos[1]]
689 self.deleteLineLeft()
690 self.insertAt(prompt, line, 0)
691 self.setCursorPosition(line, len(prompt))
692
693 def __QScintillaNewline(self, cmd):
694 """
695 Private method to handle the Return key.
696
697 @param cmd QScintilla command
698 """
699 if self.__isCursorOnLastLine():
700 self.incrementalSearchString = ""
701 self.incrementalSearchActive = False
702 line, col = self.__getEndPos()
703 self.setCursorPosition(line, col)
704 self.setSelection(*(self.__lastPos + self.getCursorPosition()))
705 buf = self.selectedText()
706 self.setCursorPosition(line, col) # select nothin
707 self.insert('\n')
708 self.__executeCommand(buf)
709
710 def __QScintillaLeftCommand(self, method, allLinesAllowed=False):
711 """
712 Private method to handle a QScintilla command working to the left.
713
714 @param method shell method to execute
715 """
716 if self.__isCursorOnLastLine() or allLinesAllowed:
717 line, col = self.getCursorPosition()
718 if col > self.__lastPos[1]:
719 method()
720
721 def __QScintillaCharLeft(self):
722 """
723 Private method to handle the Cursor Left command.
724 """
725 self.__QScintillaLeftCommand(self.moveCursorLeft)
726
727 def __QScintillaWordLeft(self):
728 """
729 Private method to handle the Cursor Word Left command.
730 """
731 self.__QScintillaLeftCommand(self.moveCursorWordLeft)
732
733 def __QScintillaRightCommand(self, method):
734 """
735 Private method to handle a QScintilla command working to the right.
736
737 @param method shell method to execute
738 """
739 if self.__isCursorOnLastLine():
740 method()
741
742 def __QScintillaCharRight(self):
743 """
744 Private method to handle the Cursor Right command.
745 """
746 self.__QScintillaRightCommand(self.moveCursorRight)
747
748 def __QScintillaWordRight(self):
749 """
750 Private method to handle the Cursor Word Right command.
751 """
752 self.__QScintillaRightCommand(self.moveCursorWordRight)
753
754 def __QScintillaDeleteWordRight(self):
755 """
756 Private method to handle the Delete Word Right command.
757 """
758 self.__QScintillaRightCommand(self.deleteWordRight)
759
760 def __QScintillaDeleteLineRight(self):
761 """
762 Private method to handle the Delete Line Right command.
763 """
764 self.__QScintillaRightCommand(self.deleteLineRight)
765
766 def __QScintillaVCHome(self, cmd):
767 """
768 Private method to handle the Home key.
769
770 @param cmd QScintilla command
771 """
772 self.setCursorPosition(*self.__lastPos)
773
774 def __QScintillaLineEnd(self, cmd):
775 """
776 Private method to handle the End key.
777
778 @param cmd QScintilla command
779 """
780 self.moveCursorToEOL()
781
782 def __QScintillaLineUp(self, cmd):
783 """
784 Private method to handle the Up key.
785
786 @param cmd QScintilla command
787 """
788 line, col = self.__getEndPos()
789 buf = self.text(line)[self.__lastPos[1]:]
790 if buf and self.incrementalSearchActive:
791 if self.incrementalSearchString:
792 idx = self.__rsearchHistory(self.incrementalSearchString,
793 self.histidx)
794 if idx >= 0:
795 self.histidx = idx
796 self.__useHistory()
797 else:
798 idx = self.__rsearchHistory(buf)
799 if idx >= 0:
800 self.histidx = idx
801 self.incrementalSearchString = buf
802 self.__useHistory()
803 else:
804 if self.histidx < 0:
805 self.histidx = len(self.history)
806 if self.histidx > 0:
807 self.histidx = self.histidx - 1
808 self.__useHistory()
809
810 def __QScintillaLineDown(self, cmd):
811 """
812 Private method to handle the Down key.
813
814 @param cmd QScintilla command
815 """
816 line, col = self.__getEndPos()
817 buf = self.text(line)[self.__lastPos[1]:]
818 if buf and self.incrementalSearchActive:
819 if self.incrementalSearchString:
820 idx = self.__searchHistory(self.incrementalSearchString, self.histidx)
821 if idx >= 0:
822 self.histidx = idx
823 self.__useHistory()
824 else:
825 idx = self.__searchHistory(buf)
826 if idx >= 0:
827 self.histidx = idx
828 self.incrementalSearchString = buf
829 self.__useHistory()
830 else:
831 if self.histidx >= 0 and self.histidx < len(self.history):
832 self.histidx += 1
833 self.__useHistory()
834
835 def __QScintillaCharLeftExtend(self):
836 """
837 Private method to handle the Extend Selection Left command.
838 """
839 self.__QScintillaLeftCommand(self.extendSelectionLeft, True)
840
841 def __QScintillaWordLeftExtend(self):
842 """
843 Private method to handle the Extend Selection Left one word command.
844 """
845 self.__QScintillaLeftCommand(self.extendSelectionWordLeft, True)
846
847 def __QScintillaVCHomeExtend(self):
848 """
849 Private method to handle the Extend Selection to start of line command.
850 """
851 col = self.__lastPos[1]
852 self.extendSelectionToBOL()
853 while col > 0:
854 self.extendSelectionRight()
855 col -= 1
856
857 def __executeCommand(self, cmd):
858 """
859 Private slot to execute a command.
860
861 @param cmd command to be executed by debug client (string)
862 """
863 if not cmd:
864 cmd = ''
865 if len(self.history) == 0 or self.history[-1] != cmd:
866 if len(self.history) == self.maxHistoryEntries:
867 del self.history[0]
868 self.history.append(cmd)
869 self.histidx = -1
870
871 if cmd.lower() in ["clear", "cls"]:
872 self.clear()
873 return
874 else:
875 if not cmd.endswith("\n"):
876 cmd = "{0}\n".format(cmd)
877 self.__send(cmd)
878
879 def __useHistory(self):
880 """
881 Private method to display a command from the history.
882 """
883 if self.histidx < len(self.history):
884 cmd = self.history[self.histidx]
885 else:
886 cmd = ""
887 self.incrementalSearchString = ""
888 self.incrementalSearchActive = False
889
890 self.__insertHistory(cmd)
891
892 def __insertHistory(self, cmd):
893 """
894 Private method to insert a command selected from the history.
895
896 @param cmd history entry to be inserted (string)
897 """
898 self.setCursorPosition(self.prline, self.prcol)
899 self.setSelection(self.prline, self.prcol,\
900 self.prline, self.lineLength(self.prline))
901 self.removeSelectedText()
902 self.__insertText(cmd)
903
904 def __searchHistory(self, txt, startIdx=-1):
905 """
906 Private method used to search the history.
907
908 @param txt text to match at the beginning (string)
909 @param startIdx index to start search from (integer)
910 @return index of found entry (integer)
911 """
912 if startIdx == -1:
913 idx = 0
914 else:
915 idx = startIdx + 1
916 while idx < len(self.history) and \
917 not self.history[idx].startswith(txt):
918 idx += 1
919 return idx
920
921 def __rsearchHistory(self, txt, startIdx=-1):
922 """
923 Private method used to reverse search the history.
924
925 @param txt text to match at the beginning (string)
926 @param startIdx index to start search from (integer)
927 @return index of found entry (integer)
928 """
929 if startIdx == -1:
930 idx = len(self.history) - 1
931 else:
932 idx = startIdx - 1
933 while idx >= 0 and \
934 not self.history[idx].startswith(txt):
935 idx -= 1
936 return idx
937
938 def contextMenuEvent(self, ev):
939 """
940 Reimplemented to show our own context menu.
941
942 @param ev context menu event (QContextMenuEvent)
943 """
944 self.menu.popup(ev.globalPos())
945 ev.accept()
946
947 def clear(self):
948 """
949 Public slot to clear the display.
950 """
951 super().clear()
952 self.__send("\n")
953
954 def __reset(self):
955 """
956 Private slot to handle the 'reset' context menu entry.
957 """
958 self.__stopShell()
959 self.__startShell()
960
961 def __startShell(self):
962 """
963 Private slot to start the shell process.
964 """
965 args = []
966 if Utilities.isWindowsPlatform():
967 args.append("/Q")
968 self.__process.start("cmd.exe", args)
969 else:
970 shell = Preferences.getTerminal("Shell")
971 if not shell:
972 shell = os.environ.get('SHELL')
973 if shell is None:
974 self.__insertText(self.trUtf8("No shell has been configured."))
975 return
976 if Preferences.getTerminal("ShellInteractive"):
977 args.append("-i")
978 self.__process.start(shell, args)
979
980 def __stopShell(self):
981 """
982 Private slot to stop the shell process.
983 """
984 self.__process.kill()
985 self.__process.waitForFinished(3000)
986
987 def handlePreferencesChanged(self):
988 """
989 Public slot to handle the preferencesChanged signal.
990 """
991 # rebind the lexer
992 self.__bindLexer()
993 self.recolor()
994
995 # set margin 0 configuration
996 self.__setTextDisplay()
997 self.__setMargin0()
998
999 # do the history related stuff
1000 self.maxHistoryEntries = Preferences.getTerminal("MaxHistoryEntries")
1001 self.history = self.history[-self.maxHistoryEntries:]
1002
1003 # do the I/O encoding
1004 self.__ioEncoding = Preferences.getSystem("IOEncoding")
1005
1006 def focusInEvent(self, event):
1007 """
1008 Public method called when the shell receives focus.
1009
1010 @param event the event object (QFocusEvent)
1011 """
1012 if not self.__actionsAdded:
1013 self.addActions(self.vm.editorActGrp.actions())
1014 self.addActions(self.vm.copyActGrp.actions())
1015 self.addActions(self.vm.viewActGrp.actions())
1016 self.__searchShortcut = QShortcut(self.vm.searchAct.shortcut(), self,
1017 self.__find, self.__find)
1018 self.__searchNextShortcut = QShortcut(self.vm.searchNextAct.shortcut(), self,
1019 self.__searchNext, self.__searchNext)
1020 self.__searchPrevShortcut = QShortcut(self.vm.searchPrevAct.shortcut(), self,
1021 self.__searchPrev, self.__searchPrev)
1022
1023 try:
1024 self.vm.editActGrp.setEnabled(False)
1025 self.vm.editorActGrp.setEnabled(True)
1026 self.vm.copyActGrp.setEnabled(True)
1027 self.vm.viewActGrp.setEnabled(True)
1028 self.vm.searchActGrp.setEnabled(False)
1029 except AttributeError:
1030 pass
1031 self.__searchShortcut.setEnabled(True)
1032 self.__searchNextShortcut.setEnabled(True)
1033 self.__searchPrevShortcut.setEnabled(True)
1034 self.setCaretWidth(self.caretWidth)
1035 self.setCursorFlashTime(QApplication.cursorFlashTime())
1036
1037 super().focusInEvent(event)
1038
1039 def focusOutEvent(self, event):
1040 """
1041 Public method called when the shell loses focus.
1042
1043 @param event the event object (QFocusEvent)
1044 """
1045 try:
1046 self.vm.editorActGrp.setEnabled(False)
1047 except AttributeError:
1048 pass
1049 self.__searchShortcut.setEnabled(False)
1050 self.__searchNextShortcut.setEnabled(False)
1051 self.__searchPrevShortcut.setEnabled(False)
1052 self.setCaretWidth(0)
1053 super().focusOutEvent(event)
1054
1055 def insert(self, txt):
1056 """
1057 Public slot to insert text at the current cursor position.
1058
1059 The cursor is advanced to the end of the inserted text.
1060
1061 @param txt text to be inserted (string)
1062 """
1063 l = len(txt)
1064 line, col = self.getCursorPosition()
1065 self.insertAt(txt, line, col)
1066 if re.search(self.linesepRegExp, txt) is not None:
1067 line += 1
1068 self.setCursorPosition(line, col + l)
1069
1070 def __configure(self):
1071 """
1072 Private method to open the configuration dialog.
1073 """
1074 e5App().getObject("UserInterface").showPreferences("terminalPage")
1075
1076 def __find(self):
1077 """
1078 Private slot to show the find widget.
1079 """
1080 txt = self.selectedText()
1081 self.__mainWindow.showFind(txt)
1082
1083 def __searchNext(self):
1084 """
1085 Private method to search for the next occurrence.
1086 """
1087 if self.__lastSearch:
1088 self.searchNext(*self.__lastSearch)
1089
1090 def searchNext(self, txt, caseSensitive, wholeWord):
1091 """
1092 Public method to search the next occurrence of the given text.
1093
1094 @param txt text to search for (string)
1095 @param caseSensitive flag indicating to perform a case sensitive
1096 search (boolean)
1097 @param wholeWord flag indicating to search for whole words
1098 only (boolean)
1099 """
1100 self.__lastSearch = (txt, caseSensitive, wholeWord)
1101 ok = self.findFirst(txt, False, caseSensitive, wholeWord, False, forward=True)
1102 self.searchStringFound.emit(ok)
1103
1104 def __searchPrev(self):
1105 """
1106 Private method to search for the next occurrence.
1107 """
1108 if self.__lastSearch:
1109 self.searchPrev(*self.__lastSearch)
1110
1111 def searchPrev(self, txt, caseSensitive, wholeWord):
1112 """
1113 Public method to search the previous occurrence of the given text.
1114
1115 @param txt text to search for (string)
1116 @param caseSensitive flag indicating to perform a case sensitive
1117 search (boolean)
1118 @param wholeWord flag indicating to search for whole words
1119 only (boolean)
1120 """
1121 self.__lastSearch = (txt, caseSensitive, wholeWord)
1122 if self.hasSelectedText():
1123 line, index = self.getSelection()[:2]
1124 else:
1125 line, index = -1, -1
1126 ok = self.findFirst(txt, False, caseSensitive, wholeWord, False, forward=False,
1127 line=line, index=index)
1128 self.searchStringFound.emit(ok)

eric ide

mercurial