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