eric6/QScintilla/Editor.py

branch
maintenance
changeset 6989
8b8cadf8d7e9
parent 6923
d062df8f1d9f
parent 6954
b5a772d457f0
child 7035
d2036d7f5ef6
equal deleted inserted replaced
6938:7926553b7509 6989:8b8cadf8d7e9
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the editor component of the eric6 IDE.
8 """
9 from __future__ import unicode_literals
10 try:
11 str = unicode
12 chr = unichr
13 except NameError:
14 pass
15
16 import os
17 import re
18 import difflib
19
20 from PyQt5.QtCore import QDir, QTimer, QModelIndex, QFileInfo, pyqtSignal, \
21 pyqtSlot, QCryptographicHash, QEvent, QDateTime, QRegExp, Qt, QPoint
22 from PyQt5.QtGui import QCursor, QPalette, QFont, QPixmap, QPainter
23 from PyQt5.QtWidgets import QLineEdit, QActionGroup, QDialog, QInputDialog, \
24 QApplication, QMenu
25 from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog
26 from PyQt5.Qsci import QsciScintilla, QsciMacro, QsciStyledText
27
28 from E5Gui.E5Application import e5App
29 from E5Gui import E5FileDialog, E5MessageBox
30 from E5Utilities.E5Cache import E5Cache
31
32 from .QsciScintillaCompat import QsciScintillaCompat, QSCINTILLA_VERSION
33 from .EditorMarkerMap import EditorMarkerMap
34
35 import Preferences
36 import Utilities
37 from Utilities import MouseUtilities
38 from Globals import qVersionTuple
39
40 import UI.PixmapCache
41
42 from ThirdParty.EditorConfig import editorconfig
43
44 EditorAutoCompletionListID = 1
45 TemplateCompletionListID = 2
46
47
48 class Editor(QsciScintillaCompat):
49 """
50 Class implementing the editor component of the eric6 IDE.
51
52 @signal modificationStatusChanged(bool, QsciScintillaCompat) emitted when
53 the modification status has changed
54 @signal undoAvailable(bool) emitted to signal the undo availability
55 @signal redoAvailable(bool) emitted to signal the redo availability
56 @signal cursorChanged(str, int, int) emitted when the cursor position
57 was changed
58 @signal cursorLineChanged(int) emitted when the cursor line was changed
59 @signal editorAboutToBeSaved(str) emitted before the editor is saved
60 @signal editorSaved(str) emitted after the editor has been saved
61 @signal editorRenamed(str) emitted after the editor got a new name
62 (i.e. after a 'Save As')
63 @signal captionChanged(str, QsciScintillaCompat) emitted when the caption
64 is updated. Typically due to a readOnly attribute change.
65 @signal breakpointToggled(QsciScintillaCompat) emitted when a breakpoint
66 is toggled
67 @signal bookmarkToggled(QsciScintillaCompat) emitted when a bookmark is
68 toggled
69 @signal syntaxerrorToggled(QsciScintillaCompat) emitted when a syntax error
70 was discovered
71 @signal autoCompletionAPIsAvailable(bool) emitted after the autocompletion
72 function has been configured
73 @signal coverageMarkersShown(bool) emitted after the coverage markers have
74 been shown or cleared
75 @signal taskMarkersUpdated(QsciScintillaCompat) emitted when the task
76 markers were updated
77 @signal changeMarkersUpdated(QsciScintillaCompat) emitted when the change
78 markers were updated
79 @signal showMenu(str, QMenu, QsciScintillaCompat) emitted when a menu is
80 about to be shown. The name of the menu, a reference to the menu and
81 a reference to the editor are given.
82 @signal languageChanged(str) emitted when the editors language was set. The
83 language is passed as a parameter.
84 @signal eolChanged(str) emitted when the editors eol type was set. The eol
85 string is passed as a parameter.
86 @signal encodingChanged(str) emitted when the editors encoding was set. The
87 encoding name is passed as a parameter.
88 @signal lastEditPositionAvailable() emitted when a last edit position is
89 available
90 @signal refreshed() emitted to signal a refresh of the editor contents
91 @signal settingsRead() emitted to signal, that the settings have been read
92 and set
93 @signal mouseDoubleClick(position, buttons) emitted to signal a mouse
94 double click somewhere in the editor area
95 """
96 modificationStatusChanged = pyqtSignal(bool, QsciScintillaCompat)
97 undoAvailable = pyqtSignal(bool)
98 redoAvailable = pyqtSignal(bool)
99 cursorChanged = pyqtSignal(str, int, int)
100 cursorLineChanged = pyqtSignal(int)
101 editorAboutToBeSaved = pyqtSignal(str)
102 editorSaved = pyqtSignal(str)
103 editorRenamed = pyqtSignal(str)
104 captionChanged = pyqtSignal(str, QsciScintillaCompat)
105 breakpointToggled = pyqtSignal(QsciScintillaCompat)
106 bookmarkToggled = pyqtSignal(QsciScintillaCompat)
107 syntaxerrorToggled = pyqtSignal(QsciScintillaCompat)
108 autoCompletionAPIsAvailable = pyqtSignal(bool)
109 coverageMarkersShown = pyqtSignal(bool)
110 taskMarkersUpdated = pyqtSignal(QsciScintillaCompat)
111 changeMarkersUpdated = pyqtSignal(QsciScintillaCompat)
112 showMenu = pyqtSignal(str, QMenu, QsciScintillaCompat)
113 languageChanged = pyqtSignal(str)
114 eolChanged = pyqtSignal(str)
115 encodingChanged = pyqtSignal(str)
116 lastEditPositionAvailable = pyqtSignal()
117 refreshed = pyqtSignal()
118 settingsRead = pyqtSignal()
119 mouseDoubleClick = pyqtSignal(QPoint, int)
120
121 WarningCode = 1
122 WarningStyle = 2
123
124 # Autocompletion icon definitions
125 ClassID = 1
126 ClassProtectedID = 2
127 ClassPrivateID = 3
128 MethodID = 4
129 MethodProtectedID = 5
130 MethodPrivateID = 6
131 AttributeID = 7
132 AttributeProtectedID = 8
133 AttributePrivateID = 9
134 EnumID = 10
135 KeywordsID = 11
136 ModuleID = 12
137
138 FromDocumentID = 99
139
140 TemplateImageID = 100
141
142 # Cooperation related definitions
143 Separator = "@@@"
144
145 StartEditToken = "START_EDIT"
146 EndEditToken = "END_EDIT"
147 CancelEditToken = "CANCEL_EDIT"
148 RequestSyncToken = "REQUEST_SYNC"
149 SyncToken = "SYNC"
150
151 VcsConflictMarkerLineRegExpList = (
152 r"""^<<<<<<< .*?$""",
153 r"""^\|\|\|\|\|\|\| .*?$""",
154 r"""^=======.*?$""",
155 r"""^>>>>>>> .*?$""",
156 )
157
158 def __init__(self, dbs, fn="", vm=None,
159 filetype="", editor=None, tv=None):
160 """
161 Constructor
162
163 @param dbs reference to the debug server object
164 @param fn name of the file to be opened (string). If it is None,
165 a new (empty) editor is opened
166 @param vm reference to the view manager object
167 (ViewManager.ViewManager)
168 @param filetype type of the source file (string)
169 @param editor reference to an Editor object, if this is a cloned view
170 @param tv reference to the task viewer object
171 @exception IOError raised to indicate an issue accessing the file
172 """
173 super(Editor, self).__init__()
174 self.setAttribute(Qt.WA_KeyCompression)
175 self.setUtf8(True)
176
177 self.dbs = dbs
178 self.taskViewer = tv
179 self.fileName = fn
180 self.vm = vm
181 self.filetype = filetype
182 self.filetypeByFlag = False
183 self.noName = ""
184 self.project = e5App().getObject("Project")
185
186 # clear some variables
187 self.lastHighlight = None # remember the last highlighted line
188 self.lastErrorMarker = None # remember the last error line
189 self.lastCurrMarker = None # remember the last current line
190
191 self.breaks = {}
192 # key: marker handle,
193 # value: (lineno, condition, temporary,
194 # enabled, ignorecount)
195 self.bookmarks = []
196 # bookmarks are just a list of handles to the
197 # bookmark markers
198 self.syntaxerrors = {}
199 # key: marker handle
200 # value: list of (error message, error index)
201 self.warnings = {}
202 # key: marker handle
203 # value: list of (warning message, warning type)
204 self.notcoveredMarkers = [] # just a list of marker handles
205 self.showingNotcoveredMarkers = False
206
207 self.lexer_ = None
208
209 self.__loadEditorConfig()
210
211 self.condHistory = []
212 self.__lexerReset = False
213 self.completer = None
214 self.encoding = self.__getEditorConfig("DefaultEncoding")
215 self.apiLanguage = ''
216 self.lastModified = 0
217 self.line = -1
218 self.inReopenPrompt = False
219 # true if the prompt to reload a changed source is present
220 self.inFileRenamed = False
221 # true if we are propagating a rename action
222 self.inLanguageChanged = False
223 # true if we are propagating a language change
224 self.inEolChanged = False
225 # true if we are propagating an eol change
226 self.inEncodingChanged = False
227 # true if we are propagating an encoding change
228 self.inDragDrop = False
229 # true if we are in drop mode
230 self.inLinesChanged = False
231 # true if we are propagating a lines changed event
232 self.__hasTaskMarkers = False
233 # no task markers present
234
235 self.macros = {} # list of defined macros
236 self.curMacro = None
237 self.recording = False
238
239 self.acAPI = False
240
241 self.__lastEditPosition = None
242 self.__annotationLines = 0
243
244 # list of clones
245 self.__clones = []
246
247 # clear QScintilla defined keyboard commands
248 # we do our own handling through the view manager
249 self.clearAlternateKeys()
250 self.clearKeys()
251
252 # initialize the mark occurrences timer
253 self.__markOccurrencesTimer = QTimer(self)
254 self.__markOccurrencesTimer.setSingleShot(True)
255 self.__markOccurrencesTimer.setInterval(
256 Preferences.getEditor("MarkOccurrencesTimeout"))
257 self.__markOccurrencesTimer.timeout.connect(self.__markOccurrences)
258 self.__markedText = ""
259 self.__searchIndicatorLines = []
260
261 # initialize some spellchecking stuff
262 self.spell = None
263 self.lastLine = 0
264 self.lastIndex = 0
265
266 # initialize some cooperation stuff
267 self.__isSyncing = False
268 self.__receivedWhileSyncing = []
269 self.__savedText = ""
270 self.__inSharedEdit = False
271 self.__isShared = False
272 self.__inRemoteSharedEdit = False
273
274 # connect signals before loading the text
275 self.modificationChanged.connect(self.__modificationChanged)
276 self.cursorPositionChanged.connect(self.__cursorPositionChanged)
277 self.modificationAttempted.connect(self.__modificationReadOnly)
278
279 # define the margins markers
280 self.__changeMarkerSaved = self.markerDefine(
281 self.__createChangeMarkerPixmap(
282 "OnlineChangeTraceMarkerSaved"))
283 self.__changeMarkerUnsaved = self.markerDefine(
284 self.__createChangeMarkerPixmap(
285 "OnlineChangeTraceMarkerUnsaved"))
286 self.breakpoint = self.markerDefine(
287 UI.PixmapCache.getPixmap("break.png"))
288 self.cbreakpoint = self.markerDefine(
289 UI.PixmapCache.getPixmap("cBreak.png"))
290 self.tbreakpoint = self.markerDefine(
291 UI.PixmapCache.getPixmap("tBreak.png"))
292 self.tcbreakpoint = self.markerDefine(
293 UI.PixmapCache.getPixmap("tCBreak.png"))
294 self.dbreakpoint = self.markerDefine(
295 UI.PixmapCache.getPixmap("breakDisabled.png"))
296 self.bookmark = self.markerDefine(
297 UI.PixmapCache.getPixmap("bookmark16.png"))
298 self.syntaxerror = self.markerDefine(
299 UI.PixmapCache.getPixmap("syntaxError.png"))
300 self.notcovered = self.markerDefine(
301 UI.PixmapCache.getPixmap("notcovered.png"))
302 self.taskmarker = self.markerDefine(
303 UI.PixmapCache.getPixmap("task.png"))
304 self.warning = self.markerDefine(
305 UI.PixmapCache.getPixmap("warning.png"))
306
307 # define the line markers
308 if Preferences.getEditor("LineMarkersBackground"):
309 self.currentline = self.markerDefine(QsciScintilla.Background)
310 self.errorline = self.markerDefine(QsciScintilla.Background)
311 self.__setLineMarkerColours()
312 else:
313 self.currentline = self.markerDefine(
314 UI.PixmapCache.getPixmap("currentLineMarker.png"))
315 self.errorline = self.markerDefine(
316 UI.PixmapCache.getPixmap("errorLineMarker.png"))
317
318 self.breakpointMask = (1 << self.breakpoint) | \
319 (1 << self.cbreakpoint) | \
320 (1 << self.tbreakpoint) | \
321 (1 << self.tcbreakpoint) | \
322 (1 << self.dbreakpoint)
323
324 self.changeMarkersMask = (1 << self.__changeMarkerSaved) | \
325 (1 << self.__changeMarkerUnsaved)
326
327 self.__markerMap = EditorMarkerMap(self)
328
329 # configure the margins
330 self.__setMarginsDisplay()
331 self.linesChanged.connect(self.__resizeLinenoMargin)
332
333 self.marginClicked.connect(self.__marginClicked)
334
335 # set the eol mode
336 self.__setEolMode()
337
338 # set the text display
339 self.__setTextDisplay()
340
341 # initialize the online syntax check timer
342 try:
343 self.syntaxCheckService = e5App().getObject('SyntaxCheckService')
344 self.syntaxCheckService.syntaxChecked.connect(
345 self.__processSyntaxCheckResult)
346 self.syntaxCheckService.error.connect(
347 self.__processSyntaxCheckError)
348 self.__initOnlineSyntaxCheck()
349 except KeyError:
350 self.syntaxCheckService = None
351
352 self.isResourcesFile = False
353 if editor is None:
354 if self.fileName:
355 if (QFileInfo(self.fileName).size() // 1024) > \
356 Preferences.getEditor("WarnFilesize"):
357 res = E5MessageBox.yesNo(
358 self,
359 self.tr("Open File"),
360 self.tr("""<p>The size of the file <b>{0}</b>"""
361 """ is <b>{1} KB</b>."""
362 """ Do you really want to load it?</p>""")
363 .format(self.fileName,
364 QFileInfo(self.fileName).size() // 1024),
365 icon=E5MessageBox.Warning)
366 if not res:
367 raise IOError()
368 self.readFile(self.fileName, True)
369 self.__bindLexer(self.fileName)
370 self.__bindCompleter(self.fileName)
371 self.checkSyntax()
372 self.isResourcesFile = self.fileName.endswith(".qrc")
373
374 self.recolor()
375 else:
376 # clone the given editor
377 self.setDocument(editor.document())
378 self.breaks = editor.breaks
379 self.bookmarks = editor.bookmarks
380 self.syntaxerrors = editor.syntaxerrors
381 self.notcoveredMarkers = editor.notcoveredMarkers
382 self.showingNotcoveredMarkers = editor.showingNotcoveredMarkers
383 self.isResourcesFile = editor.isResourcesFile
384 self.lastModified = editor.lastModified
385
386 self.addClone(editor)
387 editor.addClone(self)
388
389 self.gotoLine(1)
390
391 # set the text display again
392 self.__setTextDisplay()
393
394 # set the auto-completion function
395 self.__acContext = True
396 self.__acText = ""
397 self.__acCompletions = set()
398 self.__acCompletionsFinished = 0
399 self.__acCache = E5Cache(
400 size=Preferences.getEditor("AutoCompletionCacheSize"))
401 self.__acCache.setMaximumCacheTime(
402 Preferences.getEditor("AutoCompletionCacheTime"))
403 self.__acCacheEnabled = Preferences.getEditor(
404 "AutoCompletionCacheEnabled")
405 self.__acTimer = QTimer(self)
406 self.__acTimer.setSingleShot(True)
407 self.__acTimer.setInterval(
408 Preferences.getEditor("AutoCompletionTimeout"))
409 self.__acTimer.timeout.connect(self.__autoComplete)
410
411 self.__acWatchdog = QTimer(self)
412 self.__acWatchdog.setSingleShot(True)
413 self.__acWatchdog.setInterval(
414 Preferences.getEditor("AutoCompletionWatchdogTime"))
415 self.__acWatchdog.timeout.connect(self.autoCompleteQScintilla)
416
417 self.userListActivated.connect(self.__completionListSelected)
418 self.SCN_CHARADDED.connect(self.__charAdded)
419 self.SCN_AUTOCCANCELLED.connect(self.__autocompletionCancelled)
420
421 self.__completionListHookFunctions = {}
422 self.__completionListAsyncHookFunctions = {}
423 self.__setAutoCompletion()
424
425 # set the call-tips function
426 self.__ctHookFunctions = {}
427 self.__setCallTips()
428
429 # set the mouse click handlers (fired on mouse release)
430 self.__mouseClickHandlers = {}
431 # dictionary with tuple of keyboard modifier and mouse button as key
432 # and tuple of plug-in name and function as value
433
434 sh = self.sizeHint()
435 if sh.height() < 300:
436 sh.setHeight(300)
437 self.resize(sh)
438
439 # Make sure tabbing through a QWorkspace works.
440 self.setFocusPolicy(Qt.StrongFocus)
441
442 self.__updateReadOnly(True)
443
444 self.setWhatsThis(self.tr(
445 """<b>A Source Editor Window</b>"""
446 """<p>This window is used to display and edit a source file."""
447 """ You can open as many of these as you like. The name of the"""
448 """ file is displayed in the window's titlebar.</p>"""
449 """<p>In order to set breakpoints just click in the space"""
450 """ between the line numbers and the fold markers. Via the"""
451 """ context menu of the margins they may be edited.</p>"""
452 """<p>In order to set bookmarks just Shift click in the space"""
453 """ between the line numbers and the fold markers.</p>"""
454 """<p>These actions can be reversed via the context menu.</p>"""
455 """<p>Ctrl clicking on a syntax error marker shows some info"""
456 """ about this error.</p>"""
457 ))
458
459 # Set the editors size, if it is too big for the view manager.
460 if self.vm is not None:
461 req = self.size()
462 bnd = req.boundedTo(self.vm.size())
463
464 if bnd.width() < req.width() or bnd.height() < req.height():
465 self.resize(bnd)
466
467 # set the autosave flag
468 self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
469 self.autosaveManuallyDisabled = False
470
471 self.__initContextMenu()
472 self.__initContextMenuMargins()
473
474 self.__checkEol()
475 if editor is None:
476 self.__checkLanguage()
477 self.__checkEncoding()
478 else:
479 # it's a clone
480 self.__languageChanged(editor.apiLanguage, propagate=False)
481 self.__encodingChanged(editor.encoding, propagate=False)
482
483 self.setAcceptDrops(True)
484
485 # breakpoint handling
486 self.breakpointModel = self.dbs.getBreakPointModel()
487 self.__restoreBreakpoints()
488 self.breakpointModel.rowsAboutToBeRemoved.connect(
489 self.__deleteBreakPoints)
490 self.breakpointModel.dataAboutToBeChanged.connect(
491 self.__breakPointDataAboutToBeChanged)
492 self.breakpointModel.dataChanged.connect(
493 self.__changeBreakPoints)
494 self.breakpointModel.rowsInserted.connect(
495 self.__addBreakPoints)
496 self.SCN_MODIFIED.connect(self.__modified)
497
498 # establish connection to some ViewManager action groups
499 self.addActions(self.vm.editorActGrp.actions())
500 self.addActions(self.vm.editActGrp.actions())
501 self.addActions(self.vm.copyActGrp.actions())
502 self.addActions(self.vm.viewActGrp.actions())
503
504 # register images to be shown in autocompletion lists
505 self.__registerImages()
506
507 # connect signals after loading the text
508 self.textChanged.connect(self.__textChanged)
509
510 # initialize the online change trace timer
511 self.__initOnlineChangeTrace()
512
513 if self.fileName and \
514 self.project.isOpen() and \
515 self.project.isProjectSource(self.fileName):
516 self.project.projectPropertiesChanged.connect(
517 self.__projectPropertiesChanged)
518
519 self.grabGesture(Qt.PinchGesture)
520
521 self.SCN_ZOOM.connect(self.__markerMap.update)
522 self.__markerMap.update()
523
524 def __registerImages(self):
525 """
526 Private method to register images for autocompletion lists.
527 """
528 self.registerImage(self.ClassID,
529 UI.PixmapCache.getPixmap("class.png"))
530 self.registerImage(self.ClassProtectedID,
531 UI.PixmapCache.getPixmap("class_protected.png"))
532 self.registerImage(self.ClassPrivateID,
533 UI.PixmapCache.getPixmap("class_private.png"))
534 self.registerImage(self.MethodID,
535 UI.PixmapCache.getPixmap("method.png"))
536 self.registerImage(self.MethodProtectedID,
537 UI.PixmapCache.getPixmap("method_protected.png"))
538 self.registerImage(self.MethodPrivateID,
539 UI.PixmapCache.getPixmap("method_private.png"))
540 self.registerImage(self.AttributeID,
541 UI.PixmapCache.getPixmap("attribute.png"))
542 self.registerImage(self.AttributeProtectedID,
543 UI.PixmapCache.getPixmap("attribute_protected.png"))
544 self.registerImage(self.AttributePrivateID,
545 UI.PixmapCache.getPixmap("attribute_private.png"))
546 self.registerImage(self.EnumID,
547 UI.PixmapCache.getPixmap("enum.png"))
548 self.registerImage(self.KeywordsID,
549 UI.PixmapCache.getPixmap("keywords.png"))
550 self.registerImage(self.ModuleID,
551 UI.PixmapCache.getPixmap("module.png"))
552
553 self.registerImage(self.FromDocumentID,
554 UI.PixmapCache.getPixmap("editor.png"))
555
556 self.registerImage(self.TemplateImageID,
557 UI.PixmapCache.getPixmap("templateViewer.png"))
558
559 def addClone(self, editor):
560 """
561 Public method to add a clone to our list.
562
563 @param editor reference to the cloned editor
564 @type Editor
565 """
566 self.__clones.append(editor)
567
568 editor.editorRenamed.connect(self.fileRenamed)
569 editor.languageChanged.connect(self.languageChanged)
570 editor.eolChanged.connect(self.__eolChanged)
571 editor.encodingChanged.connect(self.__encodingChanged)
572
573 def removeClone(self, editor):
574 """
575 Public method to remove a clone from our list.
576
577 @param editor reference to the cloned editor
578 @type Editor
579 """
580 if editor in self.__clones:
581 editor.editorRenamed.disconnect(self.fileRenamed)
582 editor.languageChanged.disconnect(self.languageChanged)
583 editor.eolChanged.disconnect(self.__eolChanged)
584 editor.encodingChanged.disconnect(self.__encodingChanged)
585 self.__clones.remove(editor)
586
587 def isClone(self, editor):
588 """
589 Public method to test, if the given editor is a clone.
590
591 @param editor reference to the cloned editor
592 @type Editor
593 @return flag indicating a clone
594 @rtype bool
595 """
596 return editor in self.__clones
597
598 def __bindName(self, line0):
599 """
600 Private method to generate a dummy filename for binding a lexer.
601
602 @param line0 first line of text to use in the generation process
603 (string)
604 @return dummy file name to be used for binding a lexer (string)
605 """
606 bindName = ""
607 line0 = line0.lower()
608
609 # check first line if it does not start with #!
610 if line0.startswith(("<html", "<!doctype html", "<?php")):
611 bindName = "dummy.html"
612 elif line0.startswith(("<?xml", "<!doctype")):
613 bindName = "dummy.xml"
614 elif line0.startswith("index: "):
615 bindName = "dummy.diff"
616 elif line0.startswith("\\documentclass"):
617 bindName = "dummy.tex"
618
619 if not bindName and self.filetype:
620 # check filetype
621 from . import Lexers
622 supportedLanguages = Lexers.getSupportedLanguages()
623 if self.filetype in supportedLanguages:
624 bindName = supportedLanguages[self.filetype][1]
625 elif self.filetype in ["Python", "Python2", "Python3"]:
626 bindName = "dummy.py"
627
628 if not bindName and line0.startswith("#!"):
629 # #! marker detection
630 if "python3" in line0:
631 bindName = "dummy.py"
632 self.filetype = "Python3"
633 elif "python2" in line0:
634 bindName = "dummy.py"
635 self.filetype = "Python2"
636 elif "python" in line0:
637 bindName = "dummy.py"
638 self.filetype = "Python2"
639 elif "pypy3" in line0:
640 bindName = "dummy.py"
641 self.filetype = "Python3"
642 elif "pypy2" in line0:
643 bindName = "dummy.py"
644 self.filetype = "Python2"
645 elif "pypy" in line0:
646 bindName = "dummy.py"
647 self.filetype = "Python2"
648 elif ("/bash" in line0 or "/sh" in line0):
649 bindName = "dummy.sh"
650 elif "ruby" in line0:
651 bindName = "dummy.rb"
652 self.filetype = "Ruby"
653 elif "perl" in line0:
654 bindName = "dummy.pl"
655 elif "lua" in line0:
656 bindName = "dummy.lua"
657 elif "dmd" in line0:
658 bindName = "dummy.d"
659 self.filetype = "D"
660
661 if not bindName:
662 # mode line detection: -*- mode: python -*-
663 match = re.search(r"mode[:=]\s*([-\w_.]+)", line0)
664 if match:
665 mode = match.group(1).lower()
666 if mode in ["python3", "pypy3"]:
667 bindName = "dummy.py"
668 self.filetype = "Python3"
669 elif mode in ["python2", "pypy2", "python", "pypy"]:
670 bindName = "dummy.py"
671 self.filetype = "Python2"
672 elif mode == "ruby":
673 bindName = "dummy.rb"
674 self.filetype = "Ruby"
675 elif mode == "perl":
676 bindName = "dummy.pl"
677 elif mode == "lua":
678 bindName = "dummy.lua"
679 elif mode in ["dmd", "d"]:
680 bindName = "dummy.d"
681 self.filetype = "D"
682
683 if not bindName:
684 bindName = self.fileName
685
686 return bindName
687
688 def getMenu(self, menuName):
689 """
690 Public method to get a reference to the main context menu or a submenu.
691
692 @param menuName name of the menu (string)
693 @return reference to the requested menu (QMenu) or None
694 """
695 try:
696 return self.__menus[menuName]
697 except KeyError:
698 return None
699
700 def hasMiniMenu(self):
701 """
702 Public method to check the miniMenu flag.
703
704 @return flag indicating a minimized context menu (boolean)
705 """
706 return self.miniMenu
707
708 def __initContextMenu(self):
709 """
710 Private method used to setup the context menu.
711 """
712 self.miniMenu = Preferences.getEditor("MiniContextMenu")
713
714 self.menuActs = {}
715 self.menu = QMenu()
716 self.__menus = {
717 "Main": self.menu,
718 }
719
720 self.languagesMenu = self.__initContextMenuLanguages()
721 self.__menus["Languages"] = self.languagesMenu
722 if self.isResourcesFile:
723 self.resourcesMenu = self.__initContextMenuResources()
724 self.__menus["Resources"] = self.resourcesMenu
725 else:
726 self.checksMenu = self.__initContextMenuChecks()
727 self.menuShow = self.__initContextMenuShow()
728 self.graphicsMenu = self.__initContextMenuGraphics()
729 self.autocompletionMenu = self.__initContextMenuAutocompletion()
730 self.__menus["Checks"] = self.checksMenu
731 self.__menus["Show"] = self.menuShow
732 self.__menus["Graphics"] = self.graphicsMenu
733 self.__menus["Autocompletion"] = self.autocompletionMenu
734 self.toolsMenu = self.__initContextMenuTools()
735 self.__menus["Tools"] = self.toolsMenu
736 self.exportersMenu = self.__initContextMenuExporters()
737 self.__menus["Exporters"] = self.exportersMenu
738 self.eolMenu = self.__initContextMenuEol()
739 self.__menus["Eol"] = self.eolMenu
740 self.encodingsMenu = self.__initContextMenuEncodings()
741 self.__menus["Encodings"] = self.encodingsMenu
742
743 self.menuActs["Undo"] = self.menu.addAction(
744 UI.PixmapCache.getIcon("editUndo.png"),
745 self.tr('Undo'), self.undo)
746 self.menuActs["Redo"] = self.menu.addAction(
747 UI.PixmapCache.getIcon("editRedo.png"),
748 self.tr('Redo'), self.redo)
749 self.menuActs["Revert"] = self.menu.addAction(
750 self.tr("Revert to last saved state"),
751 self.revertToUnmodified)
752 self.menu.addSeparator()
753 self.menuActs["Cut"] = self.menu.addAction(
754 UI.PixmapCache.getIcon("editCut.png"),
755 self.tr('Cut'), self.cut)
756 self.menuActs["Copy"] = self.menu.addAction(
757 UI.PixmapCache.getIcon("editCopy.png"),
758 self.tr('Copy'), self.copy)
759 self.menuActs["Paste"] = self.menu.addAction(
760 UI.PixmapCache.getIcon("editPaste.png"),
761 self.tr('Paste'), self.paste)
762 if not self.miniMenu:
763 self.menuActs["ExecuteSelection"] = self.menu.addAction(
764 self.tr("Execute Selection In Console"),
765 self.__executeSelection)
766 self.menu.addSeparator()
767 self.menu.addAction(
768 UI.PixmapCache.getIcon("editIndent.png"),
769 self.tr('Indent'), self.indentLineOrSelection)
770 self.menu.addAction(
771 UI.PixmapCache.getIcon("editUnindent.png"),
772 self.tr('Unindent'), self.unindentLineOrSelection)
773 self.menuActs["Comment"] = self.menu.addAction(
774 UI.PixmapCache.getIcon("editComment.png"),
775 self.tr('Comment'), self.commentLineOrSelection)
776 self.menuActs["Uncomment"] = self.menu.addAction(
777 UI.PixmapCache.getIcon("editUncomment.png"),
778 self.tr('Uncomment'), self.uncommentLineOrSelection)
779 self.menuActs["StreamComment"] = self.menu.addAction(
780 self.tr('Stream Comment'),
781 self.streamCommentLineOrSelection)
782 self.menuActs["BoxComment"] = self.menu.addAction(
783 self.tr('Box Comment'),
784 self.boxCommentLineOrSelection)
785 self.menu.addSeparator()
786 self.menu.addAction(
787 self.tr('Select to brace'), self.selectToMatchingBrace)
788 self.menu.addAction(self.tr('Select all'), self.__selectAll)
789 self.menu.addAction(
790 self.tr('Deselect all'), self.__deselectAll)
791 else:
792 self.menuActs["ExecuteSelection"] = None
793 self.menu.addSeparator()
794 self.menuActs["SpellCheck"] = self.menu.addAction(
795 UI.PixmapCache.getIcon("spellchecking.png"),
796 self.tr('Check spelling...'), self.checkSpelling)
797 self.menuActs["SpellCheckSelection"] = self.menu.addAction(
798 UI.PixmapCache.getIcon("spellchecking.png"),
799 self.tr('Check spelling of selection...'),
800 self.__checkSpellingSelection)
801 self.menuActs["SpellCheckRemove"] = self.menu.addAction(
802 self.tr("Remove from dictionary"),
803 self.__removeFromSpellingDictionary)
804 self.menu.addSeparator()
805 self.menu.addAction(
806 self.tr('Shorten empty lines'), self.shortenEmptyLines)
807 self.menu.addSeparator()
808 self.menuActs["Languages"] = self.menu.addMenu(self.languagesMenu)
809 self.menuActs["Encodings"] = self.menu.addMenu(self.encodingsMenu)
810 self.menuActs["Eol"] = self.menu.addMenu(self.eolMenu)
811 self.menu.addSeparator()
812 self.menuActs["MonospacedFont"] = self.menu.addAction(
813 self.tr("Use Monospaced Font"),
814 self.handleMonospacedEnable)
815 self.menuActs["MonospacedFont"].setCheckable(True)
816 self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
817 self.menuActs["AutosaveEnable"] = self.menu.addAction(
818 self.tr("Autosave enabled"), self.__autosaveEnable)
819 self.menuActs["AutosaveEnable"].setCheckable(True)
820 self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled)
821 self.menuActs["TypingAidsEnabled"] = self.menu.addAction(
822 self.tr("Typing aids enabled"), self.__toggleTypingAids)
823 self.menuActs["TypingAidsEnabled"].setCheckable(True)
824 self.menuActs["TypingAidsEnabled"].setEnabled(
825 self.completer is not None)
826 self.menuActs["TypingAidsEnabled"].setChecked(
827 self.completer is not None and self.completer.isEnabled())
828 self.menuActs["AutoCompletionEnable"] = self.menu.addAction(
829 self.tr("Automatic Completion enabled"),
830 self.__toggleAutoCompletionEnable)
831 self.menuActs["AutoCompletionEnable"].setCheckable(True)
832 self.menuActs["AutoCompletionEnable"].setChecked(
833 self.autoCompletionThreshold() != -1)
834 if not self.isResourcesFile:
835 self.menu.addMenu(self.autocompletionMenu)
836 self.menuActs["calltip"] = self.menu.addAction(
837 self.tr('Calltip'), self.callTip)
838 self.menuActs["codeInfo"] = self.menu.addAction(
839 self.tr('Code Info'), self.__showCodeInfo)
840 self.menu.addSeparator()
841 if self.isResourcesFile:
842 self.menu.addMenu(self.resourcesMenu)
843 else:
844 self.menuActs["Check"] = self.menu.addMenu(self.checksMenu)
845 self.menu.addSeparator()
846 self.menuActs["Show"] = self.menu.addMenu(self.menuShow)
847 self.menu.addSeparator()
848 self.menuActs["Diagrams"] = self.menu.addMenu(self.graphicsMenu)
849 self.menu.addSeparator()
850 self.menuActs["Tools"] = self.menu.addMenu(self.toolsMenu)
851 self.menu.addSeparator()
852 self.menu.addAction(
853 UI.PixmapCache.getIcon("documentNewView.png"),
854 self.tr('New Document View'), self.__newView)
855 self.menuActs["NewSplit"] = self.menu.addAction(
856 UI.PixmapCache.getIcon("splitVertical.png"),
857 self.tr('New Document View (with new split)'),
858 self.__newViewNewSplit)
859 self.menuActs["NewSplit"].setEnabled(self.vm.canSplit())
860 self.menu.addAction(
861 UI.PixmapCache.getIcon("close.png"),
862 self.tr('Close'), self.__contextClose)
863 self.menu.addSeparator()
864 self.reopenEncodingMenu = self.__initContextMenuReopenWithEncoding()
865 self.menuActs["Reopen"] = self.menu.addMenu(self.reopenEncodingMenu)
866 self.menuActs["Save"] = self.menu.addAction(
867 UI.PixmapCache.getIcon("fileSave.png"),
868 self.tr('Save'), self.__contextSave)
869 self.menu.addAction(
870 UI.PixmapCache.getIcon("fileSaveAs.png"),
871 self.tr('Save As...'), self.__contextSaveAs)
872 self.menu.addAction(
873 UI.PixmapCache.getIcon("fileSaveCopy.png"),
874 self.tr('Save Copy...'), self.__contextSaveCopy)
875 if not self.miniMenu:
876 self.menu.addMenu(self.exportersMenu)
877 self.menu.addSeparator()
878 self.menuActs["OpenRejections"] = self.menu.addAction(
879 self.tr("Open 'rejection' file"),
880 self.__contextOpenRejections)
881 self.menu.addSeparator()
882 self.menu.addAction(
883 UI.PixmapCache.getIcon("printPreview.png"),
884 self.tr("Print Preview"), self.printPreviewFile)
885 self.menu.addAction(
886 UI.PixmapCache.getIcon("print.png"),
887 self.tr('Print'), self.printFile)
888 else:
889 self.menuActs["OpenRejections"] = None
890
891 self.menu.aboutToShow.connect(self.__showContextMenu)
892
893 self.spellingMenu = QMenu()
894 self.__menus["Spelling"] = self.spellingMenu
895
896 self.spellingMenu.aboutToShow.connect(self.__showContextMenuSpelling)
897 self.spellingMenu.triggered.connect(
898 self.__contextMenuSpellingTriggered)
899
900 def __initContextMenuAutocompletion(self):
901 """
902 Private method used to setup the Checks context sub menu.
903
904 @return reference to the generated menu (QMenu)
905 """
906 menu = QMenu(self.tr('Complete'))
907
908 self.menuActs["acDynamic"] = menu.addAction(
909 self.tr('Complete'), self.autoComplete)
910 menu.addSeparator()
911 self.menuActs["acClearCache"] = menu.addAction(
912 self.tr("Clear Completions Cache"), self.__clearCompletionsCache)
913 menu.addSeparator()
914 menu.addAction(
915 self.tr('Complete from Document'), self.autoCompleteFromDocument)
916 self.menuActs["acAPI"] = menu.addAction(
917 self.tr('Complete from APIs'), self.autoCompleteFromAPIs)
918 self.menuActs["acAPIDocument"] = menu.addAction(
919 self.tr('Complete from Document and APIs'),
920 self.autoCompleteFromAll)
921
922 menu.aboutToShow.connect(self.__showContextMenuAutocompletion)
923
924 return menu
925
926 def __initContextMenuChecks(self):
927 """
928 Private method used to setup the Checks context sub menu.
929
930 @return reference to the generated menu (QMenu)
931 """
932 menu = QMenu(self.tr('Check'))
933 menu.aboutToShow.connect(self.__showContextMenuChecks)
934 return menu
935
936 def __initContextMenuTools(self):
937 """
938 Private method used to setup the Tools context sub menu.
939
940 @return reference to the generated menu (QMenu)
941 """
942 menu = QMenu(self.tr('Tools'))
943 menu.aboutToShow.connect(self.__showContextMenuTools)
944 return menu
945
946 def __initContextMenuShow(self):
947 """
948 Private method used to setup the Show context sub menu.
949
950 @return reference to the generated menu (QMenu)
951 """
952 menu = QMenu(self.tr('Show'))
953
954 menu.addAction(self.tr('Code metrics...'), self.__showCodeMetrics)
955 self.coverageMenuAct = menu.addAction(
956 self.tr('Code coverage...'), self.__showCodeCoverage)
957 self.coverageShowAnnotationMenuAct = menu.addAction(
958 self.tr('Show code coverage annotations'),
959 self.codeCoverageShowAnnotations)
960 self.coverageHideAnnotationMenuAct = menu.addAction(
961 self.tr('Hide code coverage annotations'),
962 self.__codeCoverageHideAnnotations)
963 self.profileMenuAct = menu.addAction(
964 self.tr('Profile data...'), self.__showProfileData)
965
966 menu.aboutToShow.connect(self.__showContextMenuShow)
967
968 return menu
969
970 def __initContextMenuGraphics(self):
971 """
972 Private method used to setup the diagrams context sub menu.
973
974 @return reference to the generated menu (QMenu)
975 """
976 menu = QMenu(self.tr('Diagrams'))
977
978 menu.addAction(
979 self.tr('Class Diagram...'), self.__showClassDiagram)
980 menu.addAction(
981 self.tr('Package Diagram...'), self.__showPackageDiagram)
982 menu.addAction(
983 self.tr('Imports Diagram...'), self.__showImportsDiagram)
984 self.applicationDiagramMenuAct = menu.addAction(
985 self.tr('Application Diagram...'),
986 self.__showApplicationDiagram)
987 menu.addSeparator()
988 menu.addAction(
989 UI.PixmapCache.getIcon("open.png"),
990 self.tr("Load Diagram..."), self.__loadDiagram)
991
992 menu.aboutToShow.connect(self.__showContextMenuGraphics)
993
994 return menu
995
996 def __initContextMenuLanguages(self):
997 """
998 Private method used to setup the Languages context sub menu.
999
1000 @return reference to the generated menu (QMenu)
1001 """
1002 menu = QMenu(self.tr("Languages"))
1003
1004 self.languagesActGrp = QActionGroup(self)
1005 self.noLanguageAct = menu.addAction(
1006 UI.PixmapCache.getIcon("fileText.png"),
1007 self.tr("No Language"))
1008 self.noLanguageAct.setCheckable(True)
1009 self.noLanguageAct.setData("None")
1010 self.languagesActGrp.addAction(self.noLanguageAct)
1011 menu.addSeparator()
1012
1013 from . import Lexers
1014 self.supportedLanguages = {}
1015 supportedLanguages = Lexers.getSupportedLanguages()
1016 languages = sorted(list(supportedLanguages.keys()))
1017 for language in languages:
1018 if language != "Guessed":
1019 self.supportedLanguages[language] = \
1020 supportedLanguages[language][:2]
1021 act = menu.addAction(
1022 UI.PixmapCache.getIcon(supportedLanguages[language][2]),
1023 self.supportedLanguages[language][0])
1024 act.setCheckable(True)
1025 act.setData(language)
1026 self.supportedLanguages[language].append(act)
1027 self.languagesActGrp.addAction(act)
1028
1029 menu.addSeparator()
1030 self.pygmentsAct = menu.addAction(self.tr("Guessed"))
1031 self.pygmentsAct.setCheckable(True)
1032 self.pygmentsAct.setData("Guessed")
1033 self.languagesActGrp.addAction(self.pygmentsAct)
1034 self.pygmentsSelAct = menu.addAction(self.tr("Alternatives"))
1035 self.pygmentsSelAct.setData("Alternatives")
1036
1037 menu.triggered.connect(self.__languageMenuTriggered)
1038 menu.aboutToShow.connect(self.__showContextMenuLanguages)
1039
1040 return menu
1041
1042 def __initContextMenuEncodings(self):
1043 """
1044 Private method used to setup the Encodings context sub menu.
1045
1046 @return reference to the generated menu (QMenu)
1047 """
1048 self.supportedEncodings = {}
1049
1050 menu = QMenu(self.tr("Encodings"))
1051
1052 self.encodingsActGrp = QActionGroup(self)
1053
1054 for encoding in sorted(Utilities.supportedCodecs):
1055 act = menu.addAction(encoding)
1056 act.setCheckable(True)
1057 act.setData(encoding)
1058 self.supportedEncodings[encoding] = act
1059 self.encodingsActGrp.addAction(act)
1060
1061 menu.triggered.connect(self.__encodingsMenuTriggered)
1062 menu.aboutToShow.connect(self.__showContextMenuEncodings)
1063
1064 return menu
1065
1066 def __initContextMenuReopenWithEncoding(self):
1067 """
1068 Private method used to setup the Reopen With Encoding context sub menu.
1069
1070 @return reference to the generated menu (QMenu)
1071 """
1072 menu = QMenu(self.tr("Re-Open With Encoding"))
1073 menu.setIcon(UI.PixmapCache.getIcon("open.png"))
1074
1075 for encoding in sorted(Utilities.supportedCodecs):
1076 act = menu.addAction(encoding)
1077 act.setData(encoding)
1078
1079 menu.triggered.connect(self.__reopenWithEncodingMenuTriggered)
1080
1081 return menu
1082
1083 def __initContextMenuEol(self):
1084 """
1085 Private method to setup the eol context sub menu.
1086
1087 @return reference to the generated menu (QMenu)
1088 """
1089 self.supportedEols = {}
1090
1091 menu = QMenu(self.tr("End-of-Line Type"))
1092
1093 self.eolActGrp = QActionGroup(self)
1094
1095 act = menu.addAction(UI.PixmapCache.getIcon("eolLinux.png"),
1096 self.tr("Unix"))
1097 act.setCheckable(True)
1098 act.setData('\n')
1099 self.supportedEols['\n'] = act
1100 self.eolActGrp.addAction(act)
1101
1102 act = menu.addAction(UI.PixmapCache.getIcon("eolWindows.png"),
1103 self.tr("Windows"))
1104 act.setCheckable(True)
1105 act.setData('\r\n')
1106 self.supportedEols['\r\n'] = act
1107 self.eolActGrp.addAction(act)
1108
1109 act = menu.addAction(UI.PixmapCache.getIcon("eolMac.png"),
1110 self.tr("Macintosh"))
1111 act.setCheckable(True)
1112 act.setData('\r')
1113 self.supportedEols['\r'] = act
1114 self.eolActGrp.addAction(act)
1115
1116 menu.triggered.connect(self.__eolMenuTriggered)
1117 menu.aboutToShow.connect(self.__showContextMenuEol)
1118
1119 return menu
1120
1121 def __initContextMenuExporters(self):
1122 """
1123 Private method used to setup the Exporters context sub menu.
1124
1125 @return reference to the generated menu (QMenu)
1126 """
1127 menu = QMenu(self.tr("Export as"))
1128
1129 from . import Exporters
1130 supportedExporters = Exporters.getSupportedFormats()
1131 exporters = sorted(list(supportedExporters.keys()))
1132 for exporter in exporters:
1133 act = menu.addAction(supportedExporters[exporter])
1134 act.setData(exporter)
1135
1136 menu.triggered.connect(self.__exportMenuTriggered)
1137
1138 return menu
1139
1140 def __initContextMenuMargins(self):
1141 """
1142 Private method used to setup the context menu for the margins.
1143 """
1144 self.marginMenuActs = {}
1145
1146 # bookmark margin
1147 self.bmMarginMenu = QMenu()
1148
1149 self.bmMarginMenu.addAction(
1150 self.tr('Toggle bookmark'), self.menuToggleBookmark)
1151 self.marginMenuActs["NextBookmark"] = self.bmMarginMenu.addAction(
1152 self.tr('Next bookmark'), self.nextBookmark)
1153 self.marginMenuActs["PreviousBookmark"] = self.bmMarginMenu.addAction(
1154 self.tr('Previous bookmark'), self.previousBookmark)
1155 self.marginMenuActs["ClearBookmark"] = self.bmMarginMenu.addAction(
1156 self.tr('Clear all bookmarks'), self.clearBookmarks)
1157
1158 self.bmMarginMenu.aboutToShow.connect(
1159 lambda: self.__showContextMenuMargin(self.bmMarginMenu))
1160
1161 # breakpoint margin
1162 self.bpMarginMenu = QMenu()
1163
1164 self.marginMenuActs["Breakpoint"] = self.bpMarginMenu.addAction(
1165 self.tr('Toggle breakpoint'), self.menuToggleBreakpoint)
1166 self.marginMenuActs["TempBreakpoint"] = self.bpMarginMenu.addAction(
1167 self.tr('Toggle temporary breakpoint'),
1168 self.__menuToggleTemporaryBreakpoint)
1169 self.marginMenuActs["EditBreakpoint"] = self.bpMarginMenu.addAction(
1170 self.tr('Edit breakpoint...'), self.menuEditBreakpoint)
1171 self.marginMenuActs["EnableBreakpoint"] = self.bpMarginMenu.addAction(
1172 self.tr('Enable breakpoint'),
1173 self.__menuToggleBreakpointEnabled)
1174 self.marginMenuActs["NextBreakpoint"] = self.bpMarginMenu.addAction(
1175 self.tr('Next breakpoint'), self.menuNextBreakpoint)
1176 self.marginMenuActs["PreviousBreakpoint"] = \
1177 self.bpMarginMenu.addAction(
1178 self.tr('Previous breakpoint'),
1179 self.menuPreviousBreakpoint)
1180 self.marginMenuActs["ClearBreakpoint"] = self.bpMarginMenu.addAction(
1181 self.tr('Clear all breakpoints'), self.__menuClearBreakpoints)
1182
1183 self.bpMarginMenu.aboutToShow.connect(
1184 lambda: self.__showContextMenuMargin(self.bpMarginMenu))
1185
1186 # fold margin
1187 self.foldMarginMenu = QMenu()
1188
1189 self.marginMenuActs["ToggleAllFolds"] = \
1190 self.foldMarginMenu.addAction(
1191 self.tr("Toggle all folds"),
1192 self.foldAll)
1193 self.marginMenuActs["ToggleAllFoldsAndChildren"] = \
1194 self.foldMarginMenu.addAction(
1195 self.tr("Toggle all folds (including children)"),
1196 lambda: self.foldAll(True))
1197 self.marginMenuActs["ToggleCurrentFold"] = \
1198 self.foldMarginMenu.addAction(
1199 self.tr("Toggle current fold"),
1200 self.toggleCurrentFold)
1201 self.foldMarginMenu.addSeparator()
1202 self.marginMenuActs["ExpandChildren"] = \
1203 self.foldMarginMenu.addAction(
1204 self.tr("Expand (including children)"),
1205 self.__contextMenuExpandFoldWithChildren)
1206 self.marginMenuActs["CollapseChildren"] = \
1207 self.foldMarginMenu.addAction(
1208 self.tr("Collapse (including children)"),
1209 self.__contextMenuCollapseFoldWithChildren)
1210 self.foldMarginMenu.addSeparator()
1211 self.marginMenuActs["ClearAllFolds"] = \
1212 self.foldMarginMenu.addAction(
1213 self.tr("Clear all folds"),
1214 self.clearFolds)
1215
1216 self.foldMarginMenu.aboutToShow.connect(
1217 lambda: self.__showContextMenuMargin(self.foldMarginMenu))
1218
1219 # indicator margin
1220 self.indicMarginMenu = QMenu()
1221
1222 self.marginMenuActs["GotoSyntaxError"] = \
1223 self.indicMarginMenu.addAction(
1224 self.tr('Goto syntax error'), self.gotoSyntaxError)
1225 self.marginMenuActs["ShowSyntaxError"] = \
1226 self.indicMarginMenu.addAction(
1227 self.tr('Show syntax error message'),
1228 self.__showSyntaxError)
1229 self.marginMenuActs["ClearSyntaxError"] = \
1230 self.indicMarginMenu.addAction(
1231 self.tr('Clear syntax error'), self.clearSyntaxError)
1232 self.indicMarginMenu.addSeparator()
1233 self.marginMenuActs["NextWarningMarker"] = \
1234 self.indicMarginMenu.addAction(
1235 self.tr("Next warning"), self.nextWarning)
1236 self.marginMenuActs["PreviousWarningMarker"] = \
1237 self.indicMarginMenu.addAction(
1238 self.tr("Previous warning"), self.previousWarning)
1239 self.marginMenuActs["ShowWarning"] = \
1240 self.indicMarginMenu.addAction(
1241 self.tr('Show warning message'), self.__showWarning)
1242 self.marginMenuActs["ClearWarnings"] = \
1243 self.indicMarginMenu.addAction(
1244 self.tr('Clear warnings'), self.clearWarnings)
1245 self.indicMarginMenu.addSeparator()
1246 self.marginMenuActs["NextCoverageMarker"] = \
1247 self.indicMarginMenu.addAction(
1248 self.tr('Next uncovered line'), self.nextUncovered)
1249 self.marginMenuActs["PreviousCoverageMarker"] = \
1250 self.indicMarginMenu.addAction(
1251 self.tr('Previous uncovered line'), self.previousUncovered)
1252 self.indicMarginMenu.addSeparator()
1253 self.marginMenuActs["NextTaskMarker"] = \
1254 self.indicMarginMenu.addAction(
1255 self.tr('Next task'), self.nextTask)
1256 self.marginMenuActs["PreviousTaskMarker"] = \
1257 self.indicMarginMenu.addAction(
1258 self.tr('Previous task'), self.previousTask)
1259 self.indicMarginMenu.addSeparator()
1260 self.marginMenuActs["NextChangeMarker"] = \
1261 self.indicMarginMenu.addAction(
1262 self.tr('Next change'), self.nextChange)
1263 self.marginMenuActs["PreviousChangeMarker"] = \
1264 self.indicMarginMenu.addAction(
1265 self.tr('Previous change'), self.previousChange)
1266 self.marginMenuActs["ClearChangeMarkers"] = \
1267 self.indicMarginMenu.addAction(
1268 self.tr('Clear changes'), self.__reinitOnlineChangeTrace)
1269
1270 self.indicMarginMenu.aboutToShow.connect(
1271 lambda: self.__showContextMenuMargin(self.indicMarginMenu))
1272
1273 def __exportMenuTriggered(self, act):
1274 """
1275 Private method to handle the selection of an export format.
1276
1277 @param act reference to the action that was triggered (QAction)
1278 """
1279 exporterFormat = act.data()
1280 self.exportFile(exporterFormat)
1281
1282 def exportFile(self, exporterFormat):
1283 """
1284 Public method to export the file.
1285
1286 @param exporterFormat format the file should be exported into (string)
1287 """
1288 if exporterFormat:
1289 from . import Exporters
1290 exporter = Exporters.getExporter(exporterFormat, self)
1291 if exporter:
1292 exporter.exportSource()
1293 else:
1294 E5MessageBox.critical(
1295 self,
1296 self.tr("Export source"),
1297 self.tr(
1298 """<p>No exporter available for the """
1299 """export format <b>{0}</b>. Aborting...</p>""")
1300 .format(exporterFormat))
1301 else:
1302 E5MessageBox.critical(
1303 self,
1304 self.tr("Export source"),
1305 self.tr("""No export format given. Aborting..."""))
1306
1307 def __showContextMenuLanguages(self):
1308 """
1309 Private slot handling the aboutToShow signal of the languages context
1310 menu.
1311 """
1312 if self.apiLanguage.startswith("Pygments|"):
1313 self.pygmentsSelAct.setText(
1314 self.tr("Alternatives ({0})").format(
1315 self.getLanguage(normalized=False)))
1316 else:
1317 self.pygmentsSelAct.setText(self.tr("Alternatives"))
1318 self.showMenu.emit("Languages", self.languagesMenu, self)
1319
1320 def __selectPygmentsLexer(self):
1321 """
1322 Private method to select a specific pygments lexer.
1323
1324 @return name of the selected pygments lexer (string)
1325 """
1326 from pygments.lexers import get_all_lexers
1327 lexerList = sorted(l[0] for l in get_all_lexers())
1328 try:
1329 lexerSel = lexerList.index(
1330 self.getLanguage(normalized=False, forPygments=True))
1331 except ValueError:
1332 lexerSel = 0
1333 lexerName, ok = QInputDialog.getItem(
1334 self,
1335 self.tr("Pygments Lexer"),
1336 self.tr("Select the Pygments lexer to apply."),
1337 lexerList,
1338 lexerSel,
1339 False)
1340 if ok and lexerName:
1341 return lexerName
1342 else:
1343 return ""
1344
1345 def __languageMenuTriggered(self, act):
1346 """
1347 Private method to handle the selection of a lexer language.
1348
1349 @param act reference to the action that was triggered (QAction)
1350 """
1351 if act == self.noLanguageAct:
1352 self.__resetLanguage()
1353 elif act == self.pygmentsAct:
1354 self.setLanguage("dummy.pygments")
1355 elif act == self.pygmentsSelAct:
1356 language = self.__selectPygmentsLexer()
1357 if language:
1358 self.setLanguage("dummy.pygments", pyname=language)
1359 else:
1360 language = act.data()
1361 if language:
1362 self.filetype = language
1363 self.setLanguage(self.supportedLanguages[language][1])
1364 self.checkSyntax()
1365
1366 def __languageChanged(self, language, propagate=True):
1367 """
1368 Private slot handling a change of a connected editor's language.
1369
1370 @param language language to be set (string)
1371 @keyparam propagate flag indicating to propagate the change (boolean)
1372 """
1373 if language == '':
1374 self.__resetLanguage(propagate=propagate)
1375 elif language == "Guessed":
1376 self.setLanguage("dummy.pygments",
1377 propagate=propagate)
1378 elif language.startswith("Pygments|"):
1379 pyname = language.split("|", 1)[1]
1380 self.setLanguage("dummy.pygments", pyname=pyname,
1381 propagate=propagate)
1382 else:
1383 self.filetype = language
1384 self.setLanguage(self.supportedLanguages[language][1],
1385 propagate=propagate)
1386 self.checkSyntax()
1387
1388 def __resetLanguage(self, propagate=True):
1389 """
1390 Private method used to reset the language selection.
1391
1392 @keyparam propagate flag indicating to propagate the change (boolean)
1393 """
1394 if self.lexer_ is not None and \
1395 (self.lexer_.lexer() == "container" or self.lexer_.lexer() is None):
1396 self.SCN_STYLENEEDED.disconnect(self.__styleNeeded)
1397
1398 self.apiLanguage = ""
1399 self.lexer_ = None
1400 self.__lexerReset = True
1401 self.setLexer()
1402 if self.completer is not None:
1403 self.completer.setEnabled(False)
1404 self.completer = None
1405 useMonospaced = self.useMonospaced
1406 self.__setTextDisplay()
1407 self.setMonospaced(useMonospaced)
1408 self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
1409
1410 if not self.inLanguageChanged and propagate:
1411 self.inLanguageChanged = True
1412 self.languageChanged.emit(self.apiLanguage)
1413 self.inLanguageChanged = False
1414
1415 def setLanguage(self, filename, initTextDisplay=True, propagate=True,
1416 pyname=""):
1417 """
1418 Public method to set a lexer language.
1419
1420 @param filename filename used to determine the associated lexer
1421 language (string)
1422 @param initTextDisplay flag indicating an initialization of the text
1423 display is required as well (boolean)
1424 @keyparam propagate flag indicating to propagate the change (boolean)
1425 @keyparam pyname name of the pygments lexer to use (string)
1426 """
1427 # clear all warning and syntax error markers
1428 self.clearSyntaxError()
1429 self.clearWarnings()
1430
1431 self.menuActs["MonospacedFont"].setChecked(False)
1432
1433 self.__lexerReset = False
1434 self.__bindLexer(filename, pyname=pyname)
1435 self.__bindCompleter(filename)
1436 self.recolor()
1437 self.__checkLanguage()
1438
1439 # set the text display
1440 if initTextDisplay:
1441 self.__setTextDisplay()
1442
1443 # set the auto-completion and call-tips function
1444 self.__setAutoCompletion()
1445 self.__setCallTips()
1446
1447 if not self.inLanguageChanged and propagate:
1448 self.inLanguageChanged = True
1449 self.languageChanged.emit(self.apiLanguage)
1450 self.inLanguageChanged = False
1451
1452 def __checkLanguage(self):
1453 """
1454 Private method to check the selected language of the language submenu.
1455 """
1456 if self.apiLanguage == "":
1457 self.noLanguageAct.setChecked(True)
1458 elif self.apiLanguage == "Guessed":
1459 self.pygmentsAct.setChecked(True)
1460 elif self.apiLanguage.startswith("Pygments|"):
1461 act = self.languagesActGrp.checkedAction()
1462 if act:
1463 act.setChecked(False)
1464 else:
1465 self.supportedLanguages[self.apiLanguage][2].setChecked(True)
1466
1467 def projectLexerAssociationsChanged(self):
1468 """
1469 Public slot to handle changes of the project lexer associations.
1470 """
1471 self.setLanguage(self.fileName)
1472
1473 def __showContextMenuEncodings(self):
1474 """
1475 Private slot handling the aboutToShow signal of the encodings context
1476 menu.
1477 """
1478 self.showMenu.emit("Encodings", self.encodingsMenu, self)
1479
1480 def __encodingsMenuTriggered(self, act):
1481 """
1482 Private method to handle the selection of an encoding.
1483
1484 @param act reference to the action that was triggered (QAction)
1485 """
1486 encoding = act.data()
1487 self.setModified(True)
1488 self.__encodingChanged("{0}-selected".format(encoding))
1489
1490 def __checkEncoding(self):
1491 """
1492 Private method to check the selected encoding of the encodings submenu.
1493 """
1494 try:
1495 self.supportedEncodings[self.__normalizedEncoding()]\
1496 .setChecked(True)
1497 except (AttributeError, KeyError):
1498 pass
1499
1500 def __encodingChanged(self, encoding, propagate=True):
1501 """
1502 Private slot to handle a change of the encoding.
1503
1504 @param encoding changed encoding (string)
1505 @keyparam propagate flag indicating to propagate the change (boolean)
1506 """
1507 self.encoding = encoding
1508 self.__checkEncoding()
1509
1510 if not self.inEncodingChanged and propagate:
1511 self.inEncodingChanged = True
1512 self.encodingChanged.emit(self.encoding)
1513 self.inEncodingChanged = False
1514
1515 def __normalizedEncoding(self, encoding=""):
1516 """
1517 Private method to calculate the normalized encoding string.
1518
1519 @param encoding encoding to be normalized (string)
1520 @return normalized encoding (string)
1521 """
1522 if not encoding:
1523 encoding = self.encoding
1524 return encoding.replace("-default", "")\
1525 .replace("-guessed", "")\
1526 .replace("-selected", "")
1527
1528 def __showContextMenuEol(self):
1529 """
1530 Private slot handling the aboutToShow signal of the eol context menu.
1531 """
1532 self.showMenu.emit("Eol", self.eolMenu, self)
1533
1534 def __eolMenuTriggered(self, act):
1535 """
1536 Private method to handle the selection of an eol type.
1537
1538 @param act reference to the action that was triggered (QAction)
1539 """
1540 eol = act.data()
1541 self.setEolModeByEolString(eol)
1542 self.convertEols(self.eolMode())
1543
1544 def __checkEol(self):
1545 """
1546 Private method to check the selected eol type of the eol submenu.
1547 """
1548 try:
1549 self.supportedEols[self.getLineSeparator()].setChecked(True)
1550 except (AttributeError, TypeError):
1551 pass
1552
1553 def __eolChanged(self):
1554 """
1555 Private slot to handle a change of the eol mode.
1556 """
1557 self.__checkEol()
1558
1559 if not self.inEolChanged:
1560 self.inEolChanged = True
1561 eol = self.getLineSeparator()
1562 self.eolChanged.emit(eol)
1563 self.inEolChanged = False
1564
1565 def __bindLexer(self, filename, pyname=""):
1566 """
1567 Private slot to set the correct lexer depending on language.
1568
1569 @param filename filename used to determine the associated lexer
1570 language (string)
1571 @keyparam pyname name of the pygments lexer to use (string)
1572 """
1573 if self.lexer_ is not None and \
1574 (self.lexer_.lexer() == "container" or self.lexer_.lexer() is None):
1575 self.SCN_STYLENEEDED.disconnect(self.__styleNeeded)
1576
1577 language = ""
1578 if not self.filetype:
1579 if filename:
1580 basename = os.path.basename(filename)
1581 if self.project.isOpen() and \
1582 self.project.isProjectFile(filename):
1583 language = self.project.getEditorLexerAssoc(basename)
1584 if not language:
1585 language = Preferences.getEditorLexerAssoc(basename)
1586 if not language:
1587 bindName = self.__bindName(self.text(0))
1588 if bindName:
1589 language = Preferences.getEditorLexerAssoc(bindName)
1590 if language == "Python":
1591 # correction for Python
1592 pyVer = Utilities.determinePythonVersion(
1593 filename, self.text(0), self)
1594 language = "Python{0}".format(pyVer)
1595 if language in ['Python2', 'Python3', 'Ruby', 'JavaScript']:
1596 self.filetype = language
1597 else:
1598 self.filetype = ""
1599 else:
1600 language = self.filetype
1601
1602 if language.startswith("Pygments|"):
1603 pyname = language.split("|", 1)[1]
1604 language = ""
1605
1606 from . import Lexers
1607 self.lexer_ = Lexers.getLexer(language, self, pyname=pyname)
1608 if self.lexer_ is None:
1609 self.setLexer()
1610 self.apiLanguage = ""
1611 return
1612
1613 if pyname:
1614 self.apiLanguage = "Pygments|{0}".format(pyname)
1615 else:
1616 if language == "Protocol":
1617 self.apiLanguage = language
1618 else:
1619 # Change API language for lexer where QScintilla reports
1620 # an abbreviated name.
1621 self.apiLanguage = self.lexer_.language()
1622 if self.apiLanguage == "POV":
1623 self.apiLanguage = "Povray"
1624 elif self.apiLanguage == "PO":
1625 self.apiLanguage = "Gettext"
1626 self.setLexer(self.lexer_)
1627 self.__setMarginsDisplay()
1628 if self.lexer_.lexer() == "container" or self.lexer_.lexer() is None:
1629 self.SCN_STYLENEEDED.connect(self.__styleNeeded)
1630
1631 # get the font for style 0 and set it as the default font
1632 key = 'Scintilla/{0}/style0/font'.format(self.lexer_.language())
1633 fdesc = Preferences.Prefs.settings.value(key)
1634 if fdesc is not None:
1635 font = QFont(fdesc[0], int(fdesc[1]))
1636 self.lexer_.setDefaultFont(font)
1637 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla")
1638 if self.lexer_.hasSubstyles():
1639 self.lexer_.readSubstyles(self)
1640
1641 # now set the lexer properties
1642 self.lexer_.initProperties()
1643
1644 # initialize the lexer APIs settings
1645 if self.project.isOpen() and self.project.isProjectFile(filename):
1646 projectType = self.project.getProjectType()
1647 else:
1648 projectType = ""
1649 api = self.vm.getAPIsManager().getAPIs(self.apiLanguage,
1650 projectType=projectType)
1651 if api is not None and not api.isEmpty():
1652 self.lexer_.setAPIs(api.getQsciAPIs())
1653 self.acAPI = True
1654 else:
1655 self.acAPI = False
1656 self.autoCompletionAPIsAvailable.emit(self.acAPI)
1657
1658 self.__setAnnotationStyles()
1659
1660 self.lexer_.setDefaultColor(self.lexer_.color(0))
1661 self.lexer_.setDefaultPaper(self.lexer_.paper(0))
1662
1663 def __styleNeeded(self, position):
1664 """
1665 Private slot to handle the need for more styling.
1666
1667 @param position end position, that needs styling (integer)
1668 """
1669 self.lexer_.styleText(self.getEndStyled(), position)
1670
1671 def getLexer(self):
1672 """
1673 Public method to retrieve a reference to the lexer object.
1674
1675 @return the lexer object (Lexer)
1676 """
1677 return self.lexer_
1678
1679 def getLanguage(self, normalized=True, forPygments=False):
1680 """
1681 Public method to retrieve the language of the editor.
1682
1683 @keyparam normalized flag indicating to normalize some Pygments
1684 lexer names (boolean)
1685 @keyparam forPygments flag indicating to normalize some lexer
1686 names for Pygments (boolean)
1687 @return language of the editor (string)
1688 """
1689 if self.apiLanguage == "Guessed" or \
1690 self.apiLanguage.startswith("Pygments|"):
1691 lang = self.lexer_.name()
1692 if normalized:
1693 # adjust some Pygments lexer names
1694 if lang == "Python":
1695 lang = "Python2"
1696 elif lang == "Python 3":
1697 lang = "Python3"
1698 else:
1699 lang = self.apiLanguage
1700 if forPygments:
1701 # adjust some names to Pygments lexer names
1702 if lang == "Python2":
1703 lang = "Python"
1704 elif lang == "Python3":
1705 lang = "Python 3"
1706 return lang
1707
1708 def getApiLanguage(self):
1709 """
1710 Public method to get the API language of the editor.
1711
1712 @return API language
1713 @rtype str
1714 """
1715 return self.apiLanguage
1716
1717 def __bindCompleter(self, filename):
1718 """
1719 Private slot to set the correct typing completer depending on language.
1720
1721 @param filename filename used to determine the associated typing
1722 completer language (string)
1723 """
1724 if self.completer is not None:
1725 self.completer.setEnabled(False)
1726 self.completer = None
1727
1728 filename = os.path.basename(filename)
1729 apiLanguage = Preferences.getEditorLexerAssoc(filename)
1730 if apiLanguage == "":
1731 pyVer = self.__getPyVersion()
1732 if pyVer:
1733 apiLanguage = "Python{0}".format(pyVer)
1734 elif self.isRubyFile():
1735 apiLanguage = "Ruby"
1736
1737 from . import TypingCompleters
1738 self.completer = TypingCompleters.getCompleter(apiLanguage, self)
1739
1740 def getCompleter(self):
1741 """
1742 Public method to retrieve a reference to the completer object.
1743
1744 @return the completer object (CompleterBase)
1745 """
1746 return self.completer
1747
1748 def __modificationChanged(self, m):
1749 """
1750 Private slot to handle the modificationChanged signal.
1751
1752 It emits the signal modificationStatusChanged with parameters
1753 m and self.
1754
1755 @param m modification status
1756 """
1757 if not m and bool(self.fileName):
1758 self.lastModified = QFileInfo(self.fileName).lastModified()
1759 self.modificationStatusChanged.emit(m, self)
1760 self.undoAvailable.emit(self.isUndoAvailable())
1761 self.redoAvailable.emit(self.isRedoAvailable())
1762
1763 def __cursorPositionChanged(self, line, index):
1764 """
1765 Private slot to handle the cursorPositionChanged signal.
1766
1767 It emits the signal cursorChanged with parameters fileName,
1768 line and pos.
1769
1770 @param line line number of the cursor
1771 @param index position in line of the cursor
1772 """
1773 self.cursorChanged.emit(self.fileName, line + 1, index)
1774
1775 if Preferences.getEditor("MarkOccurrencesEnabled"):
1776 self.__markOccurrencesTimer.stop()
1777 self.__markOccurrencesTimer.start()
1778
1779 if self.lastLine != line:
1780 self.cursorLineChanged.emit(line)
1781
1782 if self.spell is not None:
1783 # do spell checking
1784 doSpelling = True
1785 if self.lastLine == line:
1786 start, end = self.getWordBoundaries(
1787 line, index, useWordChars=False)
1788 if start <= self.lastIndex and self.lastIndex <= end:
1789 doSpelling = False
1790 if doSpelling:
1791 pos = self.positionFromLineIndex(self.lastLine, self.lastIndex)
1792 self.spell.checkWord(pos)
1793
1794 if self.lastLine != line:
1795 self.__markerMap.update()
1796
1797 self.lastLine = line
1798 self.lastIndex = index
1799
1800 def __modificationReadOnly(self):
1801 """
1802 Private slot to handle the modificationAttempted signal.
1803 """
1804 E5MessageBox.warning(
1805 self,
1806 self.tr("Modification of Read Only file"),
1807 self.tr("""You are attempting to change a read only file. """
1808 """Please save to a different file first."""))
1809
1810 def setNoName(self, noName):
1811 """
1812 Public method to set the display string for an unnamed editor.
1813
1814 @param noName display string for this unnamed editor (string)
1815 """
1816 self.noName = noName
1817
1818 def getNoName(self):
1819 """
1820 Public method to get the display string for an unnamed editor.
1821
1822 @return display string for this unnamed editor (string)
1823 """
1824 return self.noName
1825
1826 def getFileName(self):
1827 """
1828 Public method to return the name of the file being displayed.
1829
1830 @return filename of the displayed file (string)
1831 """
1832 return self.fileName
1833
1834 def getFileType(self):
1835 """
1836 Public method to return the type of the file being displayed.
1837
1838 @return type of the displayed file (string)
1839 """
1840 return self.filetype
1841
1842 def getFileTypeByFlag(self):
1843 """
1844 Public method to return the type of the file, if it was set by an
1845 eflag: marker.
1846
1847 @return type of the displayed file, if set by an eflag: marker or an
1848 empty string (string)
1849 """
1850 if self.filetypeByFlag:
1851 return self.filetype
1852 else:
1853 return ""
1854
1855 def determineFileType(self):
1856 """
1857 Public method to determine the file type using various tests.
1858
1859 @return type of the displayed file or an empty string (string)
1860 """
1861 ftype = self.filetype
1862 if not ftype:
1863 pyVer = self.__getPyVersion()
1864 if pyVer:
1865 ftype = "Python{0}".format(pyVer)
1866 elif self.isRubyFile():
1867 ftype = "Ruby"
1868 else:
1869 ftype = ""
1870
1871 return ftype
1872
1873 def getEncoding(self):
1874 """
1875 Public method to return the current encoding.
1876
1877 @return current encoding (string)
1878 """
1879 return self.encoding
1880
1881 def __getPyVersion(self):
1882 """
1883 Private method to return the Python main version (2 or 3) or 0 if it's
1884 not a Python file at all.
1885
1886 @return Python version (2 or 3) or 0 if it's not a Python file (int)
1887 """
1888 return Utilities.determinePythonVersion(
1889 self.fileName, self.text(0), self)
1890
1891 def isPyFile(self):
1892 """
1893 Public method to return a flag indicating a Python (2 or 3) file.
1894
1895 @return flag indicating a Python (2 or 3) file (boolean)
1896 """
1897 return self.__getPyVersion() in [2, 3]
1898
1899 def isPy2File(self):
1900 """
1901 Public method to return a flag indicating a Python2 file.
1902
1903 @return flag indicating a Python2 file (boolean)
1904 """
1905 return self.__getPyVersion() == 2
1906
1907 def isPy3File(self):
1908 """
1909 Public method to return a flag indicating a Python3 file.
1910
1911 @return flag indicating a Python3 file (boolean)
1912 """
1913 return self.__getPyVersion() == 3
1914
1915 def isRubyFile(self):
1916 """
1917 Public method to return a flag indicating a Ruby file.
1918
1919 @return flag indicating a Ruby file (boolean)
1920 """
1921 if self.filetype == "Ruby":
1922 return True
1923
1924 if self.filetype == "":
1925 line0 = self.text(0)
1926 if line0.startswith("#!") and \
1927 "ruby" in line0:
1928 self.filetype = "Ruby"
1929 return True
1930
1931 if bool(self.fileName) and \
1932 os.path.splitext(self.fileName)[1] in \
1933 self.dbs.getExtensions('Ruby'):
1934 self.filetype = "Ruby"
1935 return True
1936
1937 return False
1938
1939 def isJavascriptFile(self):
1940 """
1941 Public method to return a flag indicating a Javascript file.
1942
1943 @return flag indicating a Javascript file (boolean)
1944 """
1945 if self.filetype == "JavaScript":
1946 return True
1947
1948 if self.filetype == "":
1949 if self.fileName and \
1950 os.path.splitext(self.fileName)[1] == ".js":
1951 self.filetype = "JavaScript"
1952 return True
1953
1954 return False
1955
1956 def highlightVisible(self):
1957 """
1958 Public method to make sure that the highlight is visible.
1959 """
1960 if self.lastHighlight is not None:
1961 lineno = self.markerLine(self.lastHighlight)
1962 self.ensureVisible(lineno + 1)
1963
1964 def highlight(self, line=None, error=False, syntaxError=False):
1965 """
1966 Public method to highlight [or de-highlight] a particular line.
1967
1968 @param line line number to highlight (integer)
1969 @param error flag indicating whether the error highlight should be
1970 used (boolean)
1971 @param syntaxError flag indicating a syntax error (boolean)
1972 """
1973 if line is None:
1974 self.lastHighlight = None
1975 if self.lastErrorMarker is not None:
1976 self.markerDeleteHandle(self.lastErrorMarker)
1977 self.lastErrorMarker = None
1978 if self.lastCurrMarker is not None:
1979 self.markerDeleteHandle(self.lastCurrMarker)
1980 self.lastCurrMarker = None
1981 else:
1982 if error:
1983 if self.lastErrorMarker is not None:
1984 self.markerDeleteHandle(self.lastErrorMarker)
1985 self.lastErrorMarker = self.markerAdd(line - 1, self.errorline)
1986 self.lastHighlight = self.lastErrorMarker
1987 else:
1988 if self.lastCurrMarker is not None:
1989 self.markerDeleteHandle(self.lastCurrMarker)
1990 self.lastCurrMarker = self.markerAdd(line - 1,
1991 self.currentline)
1992 self.lastHighlight = self.lastCurrMarker
1993 self.setCursorPosition(line - 1, 0)
1994
1995 def getHighlightPosition(self):
1996 """
1997 Public method to return the position of the highlight bar.
1998
1999 @return line number of the highlight bar (integer)
2000 """
2001 if self.lastHighlight is not None:
2002 return self.markerLine(self.lastHighlight)
2003 else:
2004 return 1
2005
2006 ###########################################################################
2007 ## Breakpoint handling methods below
2008 ###########################################################################
2009
2010 def __modified(self, pos, mtype, text, length, linesAdded, line, foldNow,
2011 foldPrev, token, annotationLinesAdded):
2012 """
2013 Private method to handle changes of the number of lines.
2014
2015 @param pos start position of change (integer)
2016 @param mtype flags identifying the change (integer)
2017 @param text text that is given to the Undo system (string)
2018 @param length length of the change (integer)
2019 @param linesAdded number of added/deleted lines (integer)
2020 @param line line number of a fold level or marker change (integer)
2021 @param foldNow new fold level (integer)
2022 @param foldPrev previous fold level (integer)
2023 @param token ???
2024 @param annotationLinesAdded number of added/deleted annotation lines
2025 (integer)
2026 """
2027 if mtype & (self.SC_MOD_INSERTTEXT | self.SC_MOD_DELETETEXT) and \
2028 linesAdded != 0:
2029 if self.breaks:
2030 bps = [] # list of breakpoints
2031 for handle, (ln, cond, temp, enabled, ignorecount) in \
2032 self.breaks.items():
2033 line = self.markerLine(handle) + 1
2034 if ln != line:
2035 bps.append((ln, line))
2036 self.breaks[handle] = (line, cond, temp, enabled,
2037 ignorecount)
2038 self.inLinesChanged = True
2039 for ln, line in sorted(bps, reverse=linesAdded > 0):
2040 index1 = self.breakpointModel.getBreakPointIndex(
2041 self.fileName, ln)
2042 index2 = self.breakpointModel.index(index1.row(), 1)
2043 self.breakpointModel.setData(index2, line)
2044 self.inLinesChanged = False
2045
2046 def __restoreBreakpoints(self):
2047 """
2048 Private method to restore the breakpoints.
2049 """
2050 for handle in list(self.breaks.keys()):
2051 self.markerDeleteHandle(handle)
2052 self.__addBreakPoints(
2053 QModelIndex(), 0, self.breakpointModel.rowCount() - 1)
2054 self.__markerMap.update()
2055
2056 def __deleteBreakPoints(self, parentIndex, start, end):
2057 """
2058 Private slot to delete breakpoints.
2059
2060 @param parentIndex index of parent item (QModelIndex)
2061 @param start start row (integer)
2062 @param end end row (integer)
2063 """
2064 for row in range(start, end + 1):
2065 index = self.breakpointModel.index(row, 0, parentIndex)
2066 fn, lineno = self.breakpointModel.getBreakPointByIndex(index)[0:2]
2067 if fn == self.fileName:
2068 self.clearBreakpoint(lineno)
2069
2070 def __changeBreakPoints(self, startIndex, endIndex):
2071 """
2072 Private slot to set changed breakpoints.
2073
2074 @param startIndex start index of the breakpoints being changed
2075 (QModelIndex)
2076 @param endIndex end index of the breakpoints being changed
2077 (QModelIndex)
2078 """
2079 if not self.inLinesChanged:
2080 self.__addBreakPoints(QModelIndex(), startIndex.row(),
2081 endIndex.row())
2082
2083 def __breakPointDataAboutToBeChanged(self, startIndex, endIndex):
2084 """
2085 Private slot to handle the dataAboutToBeChanged signal of the
2086 breakpoint model.
2087
2088 @param startIndex start index of the rows to be changed (QModelIndex)
2089 @param endIndex end index of the rows to be changed (QModelIndex)
2090 """
2091 self.__deleteBreakPoints(QModelIndex(), startIndex.row(),
2092 endIndex.row())
2093
2094 def __addBreakPoints(self, parentIndex, start, end):
2095 """
2096 Private slot to add breakpoints.
2097
2098 @param parentIndex index of parent item (QModelIndex)
2099 @param start start row (integer)
2100 @param end end row (integer)
2101 """
2102 for row in range(start, end + 1):
2103 index = self.breakpointModel.index(row, 0, parentIndex)
2104 fn, line, cond, temp, enabled, ignorecount = \
2105 self.breakpointModel.getBreakPointByIndex(index)[:6]
2106 if fn == self.fileName:
2107 self.newBreakpointWithProperties(
2108 line, (cond, temp, enabled, ignorecount))
2109
2110 def clearBreakpoint(self, line):
2111 """
2112 Public method to clear a breakpoint.
2113
2114 Note: This doesn't clear the breakpoint in the debugger,
2115 it just deletes it from the editor internal list of breakpoints.
2116
2117 @param line line number of the breakpoint (integer)
2118 """
2119 if self.inLinesChanged:
2120 return
2121
2122 for handle in self.breaks:
2123 if self.markerLine(handle) == line - 1:
2124 break
2125 else:
2126 # not found, simply ignore it
2127 return
2128
2129 del self.breaks[handle]
2130 self.markerDeleteHandle(handle)
2131 self.__markerMap.update()
2132
2133 def newBreakpointWithProperties(self, line, properties):
2134 """
2135 Public method to set a new breakpoint and its properties.
2136
2137 @param line line number of the breakpoint (integer)
2138 @param properties properties for the breakpoint (tuple)
2139 (condition, temporary flag, enabled flag, ignore count)
2140 """
2141 if not properties[2]:
2142 marker = self.dbreakpoint
2143 elif properties[0]:
2144 marker = properties[1] and self.tcbreakpoint or self.cbreakpoint
2145 else:
2146 marker = properties[1] and self.tbreakpoint or self.breakpoint
2147
2148 if self.markersAtLine(line - 1) & self.breakpointMask == 0:
2149 handle = self.markerAdd(line - 1, marker)
2150 self.breaks[handle] = (line,) + properties
2151 self.breakpointToggled.emit(self)
2152 self.__markerMap.update()
2153
2154 def __toggleBreakpoint(self, line, temporary=False):
2155 """
2156 Private method to toggle a breakpoint.
2157
2158 @param line line number of the breakpoint (integer)
2159 @param temporary flag indicating a temporary breakpoint (boolean)
2160 """
2161 for handle in self.breaks:
2162 if self.markerLine(handle) == line - 1:
2163 # delete breakpoint or toggle it to the next state
2164 index = self.breakpointModel.getBreakPointIndex(
2165 self.fileName, line)
2166 if Preferences.getDebugger("ThreeStateBreakPoints") and \
2167 not self.breakpointModel.isBreakPointTemporaryByIndex(
2168 index):
2169 self.breakpointModel.deleteBreakPointByIndex(index)
2170 self.__addBreakPoint(line, True)
2171 else:
2172 self.breakpointModel.deleteBreakPointByIndex(index)
2173 self.breakpointToggled.emit(self)
2174 break
2175 else:
2176 self.__addBreakPoint(line, temporary)
2177
2178 def __addBreakPoint(self, line, temporary):
2179 """
2180 Private method to add a new breakpoint.
2181
2182 @param line line number of the breakpoint (integer)
2183 @param temporary flag indicating a temporary breakpoint (boolean)
2184 """
2185 if self.fileName and self.isPyFile():
2186 self.breakpointModel.addBreakPoint(
2187 self.fileName, line, ('', temporary, True, 0))
2188 self.breakpointToggled.emit(self)
2189
2190 def __toggleBreakpointEnabled(self, line):
2191 """
2192 Private method to toggle a breakpoints enabled status.
2193
2194 @param line line number of the breakpoint (integer)
2195 """
2196 for handle in self.breaks:
2197 if self.markerLine(handle) == line - 1:
2198 index = self.breakpointModel.getBreakPointIndex(
2199 self.fileName, line)
2200 self.breakpointModel.setBreakPointEnabledByIndex(
2201 index, not self.breaks[handle][3])
2202 break
2203
2204 def curLineHasBreakpoint(self):
2205 """
2206 Public method to check for the presence of a breakpoint at the current
2207 line.
2208
2209 @return flag indicating the presence of a breakpoint (boolean)
2210 """
2211 line, _ = self.getCursorPosition()
2212 return self.markersAtLine(line) & self.breakpointMask != 0
2213
2214 def getBreakpointLines(self):
2215 """
2216 Public method to get the lines containing a breakpoint.
2217
2218 @return list of lines containing a breakpoint (list of integer)
2219 """
2220 lines = []
2221 line = -1
2222 while True:
2223 line = self.markerFindNext(line + 1, self.breakpointMask)
2224 if line < 0:
2225 break
2226 else:
2227 lines.append(line)
2228 return lines
2229
2230 def hasBreakpoints(self):
2231 """
2232 Public method to check for the presence of breakpoints.
2233
2234 @return flag indicating the presence of breakpoints (boolean)
2235 """
2236 return len(self.breaks) > 0
2237
2238 def __menuToggleTemporaryBreakpoint(self):
2239 """
2240 Private slot to handle the 'Toggle temporary breakpoint' context menu
2241 action.
2242 """
2243 if self.line < 0:
2244 self.line, index = self.getCursorPosition()
2245 self.line += 1
2246 self.__toggleBreakpoint(self.line, 1)
2247 self.line = -1
2248
2249 def menuToggleBreakpoint(self):
2250 """
2251 Public slot to handle the 'Toggle breakpoint' context menu action.
2252 """
2253 if self.line < 0:
2254 self.line, index = self.getCursorPosition()
2255 self.line += 1
2256 self.__toggleBreakpoint(self.line)
2257 self.line = -1
2258
2259 def __menuToggleBreakpointEnabled(self):
2260 """
2261 Private slot to handle the 'Enable/Disable breakpoint' context menu
2262 action.
2263 """
2264 if self.line < 0:
2265 self.line, index = self.getCursorPosition()
2266 self.line += 1
2267 self.__toggleBreakpointEnabled(self.line)
2268 self.line = -1
2269
2270 def menuEditBreakpoint(self, line=None):
2271 """
2272 Public slot to handle the 'Edit breakpoint' context menu action.
2273
2274 @param line linenumber of the breakpoint to edit
2275 """
2276 if line is not None:
2277 self.line = line - 1
2278 if self.line < 0:
2279 self.line, index = self.getCursorPosition()
2280
2281 for handle in self.breaks:
2282 if self.markerLine(handle) == self.line:
2283 ln, cond, temp, enabled, ignorecount = self.breaks[handle]
2284 index = self.breakpointModel.getBreakPointIndex(self.fileName,
2285 ln)
2286 if not index.isValid():
2287 return
2288
2289 from Debugger.EditBreakpointDialog import EditBreakpointDialog
2290 dlg = EditBreakpointDialog(
2291 (self.fileName, ln),
2292 (cond, temp, enabled, ignorecount),
2293 self.condHistory, self, modal=True)
2294 if dlg.exec_() == QDialog.Accepted:
2295 cond, temp, enabled, ignorecount = dlg.getData()
2296 self.breakpointModel.setBreakPointByIndex(
2297 index, self.fileName, ln,
2298 (cond, temp, enabled, ignorecount))
2299 break
2300
2301 self.line = -1
2302
2303 def menuNextBreakpoint(self):
2304 """
2305 Public slot to handle the 'Next breakpoint' context menu action.
2306 """
2307 line, index = self.getCursorPosition()
2308 if line == self.lines() - 1:
2309 line = 0
2310 else:
2311 line += 1
2312 bpline = self.markerFindNext(line, self.breakpointMask)
2313 if bpline < 0:
2314 # wrap around
2315 bpline = self.markerFindNext(0, self.breakpointMask)
2316 if bpline >= 0:
2317 self.setCursorPosition(bpline, 0)
2318 self.ensureLineVisible(bpline)
2319
2320 def menuPreviousBreakpoint(self):
2321 """
2322 Public slot to handle the 'Previous breakpoint' context menu action.
2323 """
2324 line, index = self.getCursorPosition()
2325 if line == 0:
2326 line = self.lines() - 1
2327 else:
2328 line -= 1
2329 bpline = self.markerFindPrevious(line, self.breakpointMask)
2330 if bpline < 0:
2331 # wrap around
2332 bpline = self.markerFindPrevious(
2333 self.lines() - 1, self.breakpointMask)
2334 if bpline >= 0:
2335 self.setCursorPosition(bpline, 0)
2336 self.ensureLineVisible(bpline)
2337
2338 def __menuClearBreakpoints(self):
2339 """
2340 Private slot to handle the 'Clear all breakpoints' context menu action.
2341 """
2342 self.__clearBreakpoints(self.fileName)
2343
2344 def __clearBreakpoints(self, fileName):
2345 """
2346 Private slot to clear all breakpoints.
2347
2348 @param fileName name of the file (string)
2349 """
2350 idxList = []
2351 for (ln, _, _, _, _) in self.breaks.values():
2352 index = self.breakpointModel.getBreakPointIndex(fileName, ln)
2353 if index.isValid():
2354 idxList.append(index)
2355 if idxList:
2356 self.breakpointModel.deleteBreakPoints(idxList)
2357
2358 ###########################################################################
2359 ## Bookmark handling methods below
2360 ###########################################################################
2361
2362 def toggleBookmark(self, line):
2363 """
2364 Public method to toggle a bookmark.
2365
2366 @param line line number of the bookmark (integer)
2367 """
2368 for handle in self.bookmarks:
2369 if self.markerLine(handle) == line - 1:
2370 self.bookmarks.remove(handle)
2371 self.markerDeleteHandle(handle)
2372 break
2373 else:
2374 # set a new bookmark
2375 handle = self.markerAdd(line - 1, self.bookmark)
2376 self.bookmarks.append(handle)
2377 self.bookmarkToggled.emit(self)
2378 self.__markerMap.update()
2379
2380 def getBookmarks(self):
2381 """
2382 Public method to retrieve the bookmarks.
2383
2384 @return sorted list of all lines containing a bookmark
2385 (list of integer)
2386 """
2387 bmlist = []
2388 for handle in self.bookmarks:
2389 bmlist.append(self.markerLine(handle) + 1)
2390
2391 bmlist.sort()
2392 return bmlist
2393
2394 def getBookmarkLines(self):
2395 """
2396 Public method to get the lines containing a bookmark.
2397
2398 @return list of lines containing a bookmark (list of integer)
2399 """
2400 lines = []
2401 line = -1
2402 while True:
2403 line = self.markerFindNext(line + 1, 1 << self.bookmark)
2404 if line < 0:
2405 break
2406 else:
2407 lines.append(line)
2408 return lines
2409
2410 def hasBookmarks(self):
2411 """
2412 Public method to check for the presence of bookmarks.
2413
2414 @return flag indicating the presence of bookmarks (boolean)
2415 """
2416 return len(self.bookmarks) > 0
2417
2418 def menuToggleBookmark(self):
2419 """
2420 Public slot to handle the 'Toggle bookmark' context menu action.
2421 """
2422 if self.line < 0:
2423 self.line, index = self.getCursorPosition()
2424 self.line += 1
2425 self.toggleBookmark(self.line)
2426 self.line = -1
2427
2428 def nextBookmark(self):
2429 """
2430 Public slot to handle the 'Next bookmark' context menu action.
2431 """
2432 line, index = self.getCursorPosition()
2433 if line == self.lines() - 1:
2434 line = 0
2435 else:
2436 line += 1
2437 bmline = self.markerFindNext(line, 1 << self.bookmark)
2438 if bmline < 0:
2439 # wrap around
2440 bmline = self.markerFindNext(0, 1 << self.bookmark)
2441 if bmline >= 0:
2442 self.setCursorPosition(bmline, 0)
2443 self.ensureLineVisible(bmline)
2444
2445 def previousBookmark(self):
2446 """
2447 Public slot to handle the 'Previous bookmark' context menu action.
2448 """
2449 line, index = self.getCursorPosition()
2450 if line == 0:
2451 line = self.lines() - 1
2452 else:
2453 line -= 1
2454 bmline = self.markerFindPrevious(line, 1 << self.bookmark)
2455 if bmline < 0:
2456 # wrap around
2457 bmline = self.markerFindPrevious(
2458 self.lines() - 1, 1 << self.bookmark)
2459 if bmline >= 0:
2460 self.setCursorPosition(bmline, 0)
2461 self.ensureLineVisible(bmline)
2462
2463 def clearBookmarks(self):
2464 """
2465 Public slot to handle the 'Clear all bookmarks' context menu action.
2466 """
2467 for handle in self.bookmarks:
2468 self.markerDeleteHandle(handle)
2469 self.bookmarks = []
2470 self.bookmarkToggled.emit(self)
2471 self.__markerMap.update()
2472
2473 ###########################################################################
2474 ## Printing methods below
2475 ###########################################################################
2476
2477 def printFile(self):
2478 """
2479 Public slot to print the text.
2480 """
2481 from .Printer import Printer
2482 printer = Printer(mode=QPrinter.HighResolution)
2483 sb = e5App().getObject("UserInterface").statusBar()
2484 printDialog = QPrintDialog(printer, self)
2485 if self.hasSelectedText():
2486 printDialog.setOption(QAbstractPrintDialog.PrintSelection, True)
2487 if printDialog.exec_() == QDialog.Accepted:
2488 sb.showMessage(self.tr('Printing...'))
2489 QApplication.processEvents()
2490 fn = self.getFileName()
2491 if fn is not None:
2492 printer.setDocName(os.path.basename(fn))
2493 else:
2494 printer.setDocName(self.noName)
2495 if printDialog.printRange() == QAbstractPrintDialog.Selection:
2496 # get the selection
2497 fromLine, fromIndex, toLine, toIndex = self.getSelection()
2498 if toIndex == 0:
2499 toLine -= 1
2500 # Qscintilla seems to print one line more than told
2501 res = printer.printRange(self, fromLine, toLine - 1)
2502 else:
2503 res = printer.printRange(self)
2504 if res:
2505 sb.showMessage(self.tr('Printing completed'), 2000)
2506 else:
2507 sb.showMessage(self.tr('Error while printing'), 2000)
2508 QApplication.processEvents()
2509 else:
2510 sb.showMessage(self.tr('Printing aborted'), 2000)
2511 QApplication.processEvents()
2512
2513 def printPreviewFile(self):
2514 """
2515 Public slot to show a print preview of the text.
2516 """
2517 from PyQt5.QtPrintSupport import QPrintPreviewDialog
2518 from .Printer import Printer
2519
2520 printer = Printer(mode=QPrinter.HighResolution)
2521 fn = self.getFileName()
2522 if fn is not None:
2523 printer.setDocName(os.path.basename(fn))
2524 else:
2525 printer.setDocName(self.noName)
2526 preview = QPrintPreviewDialog(printer, self)
2527 preview.paintRequested.connect(self.__printPreview)
2528 preview.exec_()
2529
2530 def __printPreview(self, printer):
2531 """
2532 Private slot to generate a print preview.
2533
2534 @param printer reference to the printer object
2535 (QScintilla.Printer.Printer)
2536 """
2537 printer.printRange(self)
2538
2539 ###########################################################################
2540 ## Task handling methods below
2541 ###########################################################################
2542
2543 def getTaskLines(self):
2544 """
2545 Public method to get the lines containing a task.
2546
2547 @return list of lines containing a task (list of integer)
2548 """
2549 lines = []
2550 line = -1
2551 while True:
2552 line = self.markerFindNext(line + 1, 1 << self.taskmarker)
2553 if line < 0:
2554 break
2555 else:
2556 lines.append(line)
2557 return lines
2558
2559 def hasTaskMarkers(self):
2560 """
2561 Public method to determine, if this editor contains any task markers.
2562
2563 @return flag indicating the presence of task markers (boolean)
2564 """
2565 return self.__hasTaskMarkers
2566
2567 def nextTask(self):
2568 """
2569 Public slot to handle the 'Next task' context menu action.
2570 """
2571 line, index = self.getCursorPosition()
2572 if line == self.lines() - 1:
2573 line = 0
2574 else:
2575 line += 1
2576 taskline = self.markerFindNext(line, 1 << self.taskmarker)
2577 if taskline < 0:
2578 # wrap around
2579 taskline = self.markerFindNext(0, 1 << self.taskmarker)
2580 if taskline >= 0:
2581 self.setCursorPosition(taskline, 0)
2582 self.ensureLineVisible(taskline)
2583
2584 def previousTask(self):
2585 """
2586 Public slot to handle the 'Previous task' context menu action.
2587 """
2588 line, index = self.getCursorPosition()
2589 if line == 0:
2590 line = self.lines() - 1
2591 else:
2592 line -= 1
2593 taskline = self.markerFindPrevious(line, 1 << self.taskmarker)
2594 if taskline < 0:
2595 # wrap around
2596 taskline = self.markerFindPrevious(
2597 self.lines() - 1, 1 << self.taskmarker)
2598 if taskline >= 0:
2599 self.setCursorPosition(taskline, 0)
2600 self.ensureLineVisible(taskline)
2601
2602 def extractTasks(self):
2603 """
2604 Public slot to extract all tasks.
2605 """
2606 from Tasks.Task import Task
2607 markers = {
2608 Task.TypeWarning:
2609 Preferences.getTasks("TasksWarningMarkers").split(),
2610 Task.TypeNote:
2611 Preferences.getTasks("TasksNoteMarkers").split(),
2612 Task.TypeTodo:
2613 Preferences.getTasks("TasksTodoMarkers").split(),
2614 Task.TypeFixme:
2615 Preferences.getTasks("TasksFixmeMarkers").split(),
2616 }
2617 txtList = self.text().split(self.getLineSeparator())
2618
2619 # clear all task markers and tasks
2620 self.markerDeleteAll(self.taskmarker)
2621 self.taskViewer.clearFileTasks(self.fileName)
2622 self.__hasTaskMarkers = False
2623
2624 # now search tasks and record them
2625 lineIndex = -1
2626 for line in txtList:
2627 lineIndex += 1
2628 shouldBreak = False
2629
2630 if line.endswith("__NO-TASK__"):
2631 # ignore potential task marker
2632 continue
2633
2634 for taskType, taskMarkers in markers.items():
2635 for taskMarker in taskMarkers:
2636 index = line.find(taskMarker)
2637 if index > -1:
2638 task = line[index:]
2639 self.markerAdd(lineIndex, self.taskmarker)
2640 self.taskViewer.addFileTask(
2641 task, self.fileName, lineIndex + 1, taskType)
2642 self.__hasTaskMarkers = True
2643 shouldBreak = True
2644 break
2645 if shouldBreak:
2646 break
2647 self.taskMarkersUpdated.emit(self)
2648 self.__markerMap.update()
2649
2650 ###########################################################################
2651 ## Change tracing methods below
2652 ###########################################################################
2653
2654 def __createChangeMarkerPixmap(self, key, size=16, width=4):
2655 """
2656 Private method to create a pixmap for the change markers.
2657
2658 @param key key of the color to use (string)
2659 @param size size of the pixmap (integer)
2660 @param width width of the marker line (integer)
2661 @return create pixmap (QPixmap)
2662 """
2663 pixmap = QPixmap(size, size)
2664 pixmap.fill(Qt.transparent)
2665 painter = QPainter(pixmap)
2666 painter.fillRect(size - 4, 0, 4, size,
2667 Preferences.getEditorColour(key))
2668 painter.end()
2669 return pixmap
2670
2671 def __initOnlineChangeTrace(self):
2672 """
2673 Private slot to initialize the online change trace.
2674 """
2675 self.__hasChangeMarkers = False
2676 self.__oldText = self.text()
2677 self.__lastSavedText = self.text()
2678 self.__onlineChangeTraceTimer = QTimer(self)
2679 self.__onlineChangeTraceTimer.setSingleShot(True)
2680 self.__onlineChangeTraceTimer.setInterval(
2681 Preferences.getEditor("OnlineChangeTraceInterval"))
2682 self.__onlineChangeTraceTimer.timeout.connect(
2683 self.__onlineChangeTraceTimerTimeout)
2684 self.textChanged.connect(self.__resetOnlineChangeTraceTimer)
2685
2686 def __reinitOnlineChangeTrace(self):
2687 """
2688 Private slot to re-initialize the online change trace.
2689 """
2690 self.__oldText = self.text()
2691 self.__lastSavedText = self.text()
2692 self.__deleteAllChangeMarkers()
2693
2694 def __resetOnlineChangeTraceTimer(self):
2695 """
2696 Private method to reset the online syntax check timer.
2697 """
2698 if Preferences.getEditor("OnlineChangeTrace"):
2699 self.__onlineChangeTraceTimer.stop()
2700 self.__onlineChangeTraceTimer.start()
2701
2702 def __onlineChangeTraceTimerTimeout(self):
2703 """
2704 Private slot to mark added and changed lines.
2705 """
2706 self.__deleteAllChangeMarkers()
2707
2708 # step 1: mark saved changes
2709 oldL = self.__oldText.splitlines()
2710 newL = self.__lastSavedText.splitlines()
2711 matcher = difflib.SequenceMatcher(None, oldL, newL)
2712
2713 for token, _, _, j1, j2 in matcher.get_opcodes():
2714 if token in ["insert", "replace"]:
2715 for lineNo in range(j1, j2):
2716 self.markerAdd(lineNo, self.__changeMarkerSaved)
2717 self.__hasChangeMarkers = True
2718
2719 # step 2: mark unsaved changes
2720 oldL = self.__lastSavedText.splitlines()
2721 newL = self.text().splitlines()
2722 matcher = difflib.SequenceMatcher(None, oldL, newL)
2723
2724 for token, _, _, j1, j2 in matcher.get_opcodes():
2725 if token in ["insert", "replace"]:
2726 for lineNo in range(j1, j2):
2727 self.markerAdd(lineNo, self.__changeMarkerUnsaved)
2728 self.__hasChangeMarkers = True
2729
2730 if self.__hasChangeMarkers:
2731 self.changeMarkersUpdated.emit(self)
2732 self.__markerMap.update()
2733
2734 def __resetOnlineChangeTraceInfo(self):
2735 """
2736 Private slot to reset the online change trace info.
2737 """
2738 self.__lastSavedText = self.text()
2739 self.__deleteAllChangeMarkers()
2740
2741 # mark saved changes
2742 oldL = self.__oldText.splitlines()
2743 newL = self.__lastSavedText.splitlines()
2744 matcher = difflib.SequenceMatcher(None, oldL, newL)
2745
2746 for token, _, _, j1, j2 in matcher.get_opcodes():
2747 if token in ["insert", "replace"]:
2748 for lineNo in range(j1, j2):
2749 self.markerAdd(lineNo, self.__changeMarkerSaved)
2750 self.__hasChangeMarkers = True
2751
2752 if self.__hasChangeMarkers:
2753 self.changeMarkersUpdated.emit(self)
2754 self.__markerMap.update()
2755
2756 def __deleteAllChangeMarkers(self):
2757 """
2758 Private slot to delete all change markers.
2759 """
2760 self.markerDeleteAll(self.__changeMarkerUnsaved)
2761 self.markerDeleteAll(self.__changeMarkerSaved)
2762 self.__hasChangeMarkers = False
2763 self.changeMarkersUpdated.emit(self)
2764 self.__markerMap.update()
2765
2766 def getChangeLines(self):
2767 """
2768 Public method to get the lines containing a change.
2769
2770 @return list of lines containing a change (list of integer)
2771 """
2772 lines = []
2773 line = -1
2774 while True:
2775 line = self.markerFindNext(line + 1, self.changeMarkersMask)
2776 if line < 0:
2777 break
2778 else:
2779 lines.append(line)
2780 return lines
2781
2782 def hasChangeMarkers(self):
2783 """
2784 Public method to determine, if this editor contains any change markers.
2785
2786 @return flag indicating the presence of change markers (boolean)
2787 """
2788 return self.__hasChangeMarkers
2789
2790 def nextChange(self):
2791 """
2792 Public slot to handle the 'Next change' context menu action.
2793 """
2794 line, index = self.getCursorPosition()
2795 if line == self.lines() - 1:
2796 line = 0
2797 else:
2798 line += 1
2799 changeline = self.markerFindNext(line, self.changeMarkersMask)
2800 if changeline < 0:
2801 # wrap around
2802 changeline = self.markerFindNext(0, self.changeMarkersMask)
2803 if changeline >= 0:
2804 self.setCursorPosition(changeline, 0)
2805 self.ensureLineVisible(changeline)
2806
2807 def previousChange(self):
2808 """
2809 Public slot to handle the 'Previous change' context menu action.
2810 """
2811 line, index = self.getCursorPosition()
2812 if line == 0:
2813 line = self.lines() - 1
2814 else:
2815 line -= 1
2816 changeline = self.markerFindPrevious(line, self.changeMarkersMask)
2817 if changeline < 0:
2818 # wrap around
2819 changeline = self.markerFindPrevious(
2820 self.lines() - 1, self.changeMarkersMask)
2821 if changeline >= 0:
2822 self.setCursorPosition(changeline, 0)
2823 self.ensureLineVisible(changeline)
2824
2825 ###########################################################################
2826 ## Flags handling methods below
2827 ###########################################################################
2828
2829 def __processFlags(self):
2830 """
2831 Private method to extract flags and process them.
2832
2833 @return list of change flags (list of string)
2834 """
2835 txt = self.text()
2836 flags = Utilities.extractFlags(txt)
2837
2838 changedFlags = []
2839
2840 # Flag 1: FileType
2841 if "FileType" in flags:
2842 oldFiletype = self.filetype
2843 if isinstance(flags["FileType"], str):
2844 self.filetype = flags["FileType"]
2845 self.filetypeByFlag = True
2846 if oldFiletype != self.filetype:
2847 changedFlags.append("FileType")
2848 else:
2849 if self.filetype != "" and self.filetypeByFlag:
2850 self.filetype = ""
2851 self.filetypeByFlag = False
2852 self.__bindName(txt.splitlines()[0])
2853 changedFlags.append("FileType")
2854
2855 return changedFlags
2856
2857 ###########################################################################
2858 ## File handling methods below
2859 ###########################################################################
2860
2861 def checkDirty(self):
2862 """
2863 Public method to check dirty status and open a message window.
2864
2865 @return flag indicating successful reset of the dirty flag (boolean)
2866 """
2867 if self.isModified():
2868 fn = self.fileName
2869 if fn is None:
2870 fn = self.noName
2871 res = E5MessageBox.okToClearData(
2872 self,
2873 self.tr("File Modified"),
2874 self.tr("<p>The file <b>{0}</b> has unsaved changes.</p>")
2875 .format(fn),
2876 self.saveFile)
2877 if res:
2878 self.vm.setEditorName(self, self.fileName)
2879 return res
2880
2881 return True
2882
2883 def revertToUnmodified(self):
2884 """
2885 Public method to revert back to the last saved state.
2886 """
2887 undo_ = True
2888 while self.isModified():
2889 if undo_:
2890 # try undo first
2891 if self.isUndoAvailable():
2892 self.undo()
2893 else:
2894 undo_ = False
2895 else:
2896 # try redo next
2897 if self.isRedoAvailable():
2898 self.redo()
2899 else:
2900 break
2901 # Couldn't find the unmodified state
2902
2903 def readFile(self, fn, createIt=False, encoding=""):
2904 """
2905 Public slot to read the text from a file.
2906
2907 @param fn filename to read from (string)
2908 @keyparam createIt flag indicating the creation of a new file, if the
2909 given one doesn't exist (boolean)
2910 @keyparam encoding encoding to be used to read the file (string)
2911 (Note: this parameter overrides encoding detection)
2912 """
2913 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
2914
2915 self.__loadEditorConfig(fileName=fn)
2916
2917 try:
2918 if createIt and not os.path.exists(fn):
2919 f = open(fn, "w")
2920 f.close()
2921 if encoding == "":
2922 encoding = self.__getEditorConfig("DefaultEncoding",
2923 nodefault=True)
2924 if encoding:
2925 txt, self.encoding = Utilities.readEncodedFileWithEncoding(
2926 fn, encoding)
2927 else:
2928 txt, self.encoding = Utilities.readEncodedFile(fn)
2929 except (UnicodeDecodeError, IOError) as why:
2930 QApplication.restoreOverrideCursor()
2931 E5MessageBox.critical(
2932 self.vm,
2933 self.tr('Open File'),
2934 self.tr('<p>The file <b>{0}</b> could not be opened.</p>'
2935 '<p>Reason: {1}</p>')
2936 .format(fn, str(why)))
2937 QApplication.restoreOverrideCursor()
2938 raise
2939
2940 modified = False
2941
2942 if (not self.__getEditorConfig("TabForIndentation")) and \
2943 Preferences.getEditor("ConvertTabsOnLoad") and \
2944 not (self.lexer_ and
2945 self.lexer_.alwaysKeepTabs()):
2946 txtExpanded = txt.expandtabs(self.__getEditorConfig("TabWidth"))
2947 if txtExpanded != txt:
2948 modified = True
2949 txt = txtExpanded
2950
2951 self.setText(txt)
2952
2953 # get eric specific flags
2954 self.__processFlags()
2955
2956 # perform automatic EOL conversion
2957 if self.__getEditorConfig("EOLMode", nodefault=True) or \
2958 Preferences.getEditor("AutomaticEOLConversion"):
2959 self.convertEols(self.eolMode())
2960 else:
2961 fileEol = self.detectEolString(txt)
2962 self.setEolModeByEolString(fileEol)
2963
2964 self.extractTasks()
2965
2966 QApplication.restoreOverrideCursor()
2967
2968 self.setModified(modified)
2969 self.lastModified = QFileInfo(self.fileName).lastModified()
2970
2971 def __removeTrailingWhitespace(self):
2972 """
2973 Private method to remove trailing whitespace.
2974 """
2975 searchRE = r"[ \t]+$" # whitespace at the end of a line
2976
2977 ok = self.findFirstTarget(searchRE, True, False, False, 0, 0)
2978 self.beginUndoAction()
2979 while ok:
2980 self.replaceTarget("")
2981 ok = self.findNextTarget()
2982 self.endUndoAction()
2983
2984 def writeFile(self, fn, backup=True):
2985 """
2986 Public slot to write the text to a file.
2987
2988 @param fn filename to write to (string)
2989 @param backup flag indicating to save a backup (boolean)
2990 @return flag indicating success (boolean)
2991 """
2992 config = self.__loadEditorConfigObject(fn)
2993
2994 eol = self.__getEditorConfig("EOLMode", nodefault=True, config=config)
2995 if eol is not None:
2996 self.convertEols(eol)
2997
2998 if self.__getEditorConfig("StripTrailingWhitespace", config=config):
2999 self.__removeTrailingWhitespace()
3000
3001 txt = self.text()
3002
3003 if self.__getEditorConfig("InsertFinalNewline", config=config):
3004 eol = self.getLineSeparator()
3005 if eol:
3006 if len(txt) >= len(eol):
3007 if txt[-len(eol):] != eol:
3008 txt += eol
3009 else:
3010 txt += eol
3011
3012 # create a backup file, if the option is set
3013 createBackup = backup and Preferences.getEditor("CreateBackupFile")
3014 if createBackup:
3015 if os.path.islink(fn):
3016 fn = os.path.realpath(fn)
3017 bfn = '{0}~'.format(fn)
3018 try:
3019 permissions = os.stat(fn).st_mode
3020 perms_valid = True
3021 except EnvironmentError:
3022 # if there was an error, ignore it
3023 perms_valid = False
3024 try:
3025 os.remove(bfn)
3026 except EnvironmentError:
3027 # if there was an error, ignore it
3028 pass
3029 try:
3030 os.rename(fn, bfn)
3031 except EnvironmentError:
3032 # if there was an error, ignore it
3033 pass
3034
3035 # now write text to the file fn
3036 try:
3037 editorConfigEncoding = self.__getEditorConfig(
3038 "DefaultEncoding", nodefault=True, config=config)
3039 self.encoding = Utilities.writeEncodedFile(
3040 fn, txt, self.encoding, forcedEncoding=editorConfigEncoding)
3041 if createBackup and perms_valid:
3042 os.chmod(fn, permissions)
3043 return True
3044 except (IOError, Utilities.CodingError, UnicodeError) as why:
3045 E5MessageBox.critical(
3046 self,
3047 self.tr('Save File'),
3048 self.tr('<p>The file <b>{0}</b> could not be saved.<br/>'
3049 'Reason: {1}</p>')
3050 .format(fn, str(why)))
3051 return False
3052
3053 def __getSaveFileName(self, path=None):
3054 """
3055 Private method to get the name of the file to be saved.
3056
3057 @param path directory to save the file in (string)
3058 @return file name (string)
3059 """
3060 # save to project, if a project is loaded
3061 if self.project.isOpen():
3062 if self.fileName and \
3063 self.project.startswithProjectPath(self.fileName):
3064 path = os.path.dirname(self.fileName)
3065 else:
3066 path = self.project.getProjectPath()
3067
3068 if not path and self.fileName:
3069 path = os.path.dirname(self.fileName)
3070 if not path:
3071 path = Preferences.getMultiProject("Workspace") or \
3072 Utilities.getHomeDir()
3073
3074 from . import Lexers
3075 if self.fileName:
3076 filterPattern = "(*{0})".format(
3077 os.path.splitext(self.fileName)[1])
3078 for fileFilter in Lexers.getSaveFileFiltersList(True):
3079 if filterPattern in fileFilter:
3080 defaultFilter = fileFilter
3081 break
3082 else:
3083 defaultFilter = Preferences.getEditor("DefaultSaveFilter")
3084 else:
3085 defaultFilter = Preferences.getEditor("DefaultSaveFilter")
3086 fn, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
3087 self,
3088 self.tr("Save File"),
3089 path,
3090 Lexers.getSaveFileFiltersList(True, True),
3091 defaultFilter,
3092 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
3093
3094 if fn:
3095 if fn.endswith("."):
3096 fn = fn[:-1]
3097
3098 ext = QFileInfo(fn).suffix()
3099 if not ext:
3100 ex = selectedFilter.split("(*")[1].split(")")[0]
3101 if ex:
3102 fn += ex
3103 if QFileInfo(fn).exists():
3104 res = E5MessageBox.yesNo(
3105 self,
3106 self.tr("Save File"),
3107 self.tr("<p>The file <b>{0}</b> already exists."
3108 " Overwrite it?</p>").format(fn),
3109 icon=E5MessageBox.Warning)
3110 if not res:
3111 return ""
3112 fn = Utilities.toNativeSeparators(fn)
3113
3114 return fn
3115
3116 def saveFileCopy(self, path=None):
3117 """
3118 Public method to save a copy of the file.
3119
3120 @param path directory to save the file in (string)
3121 @return flag indicating success (boolean)
3122 """
3123 fn = self.__getSaveFileName(path)
3124 if not fn:
3125 return False
3126
3127 res = self.writeFile(fn)
3128 if res:
3129 # save to project, if a project is loaded
3130 if self.project.isOpen() and \
3131 self.project.startswithProjectPath(fn):
3132 self.project.appendFile(fn)
3133
3134 return res
3135
3136 def saveFile(self, saveas=False, path=None):
3137 """
3138 Public method to save the text to a file.
3139
3140 @param saveas flag indicating a 'save as' action (boolean)
3141 @param path directory to save the file in (string)
3142 @return flag indicating success (boolean)
3143 """
3144 if not saveas and not self.isModified():
3145 return False # do nothing if text wasn't changed
3146
3147 newName = None
3148 if saveas or self.fileName == "":
3149 saveas = True
3150
3151 fn = self.__getSaveFileName(path)
3152 if not fn:
3153 return False
3154
3155 newName = fn
3156
3157 # save to project, if a project is loaded
3158 if self.project.isOpen() and \
3159 self.project.startswithProjectPath(fn):
3160 editorConfigEol = self.__getEditorConfig(
3161 "EOLMode", nodefault=True,
3162 config=self.__loadEditorConfigObject(fn))
3163 if editorConfigEol is not None:
3164 self.setEolMode(editorConfigEol)
3165 else:
3166 self.setEolModeByEolString(self.project.getEolString())
3167 self.convertEols(self.eolMode())
3168 else:
3169 fn = self.fileName
3170
3171 self.__loadEditorConfig(fn)
3172 self.editorAboutToBeSaved.emit(self.fileName)
3173 if self.writeFile(fn):
3174 if saveas:
3175 self.__clearBreakpoints(self.fileName)
3176 self.fileName = fn
3177 self.setModified(False)
3178 self.setReadOnly(False)
3179 self.setWindowTitle(self.fileName)
3180 # get eric specific flags
3181 changedFlags = self.__processFlags()
3182 if not self.__lexerReset and "FileType" in changedFlags:
3183 self.setLanguage(self.fileName)
3184
3185 if saveas:
3186 self.isResourcesFile = self.fileName.endswith(".qrc")
3187 self.__initContextMenu()
3188 self.editorRenamed.emit(self.fileName)
3189
3190 # save to project, if a project is loaded
3191 if self.project.isOpen() and \
3192 self.project.startswithProjectPath(fn):
3193 self.project.appendFile(fn)
3194 self.addedToProject()
3195
3196 self.setLanguage(self.fileName)
3197
3198 self.lastModified = QFileInfo(self.fileName).lastModified()
3199 if newName is not None:
3200 self.vm.addToRecentList(newName)
3201 self.editorSaved.emit(self.fileName)
3202 self.checkSyntax()
3203 self.extractTasks()
3204 self.__resetOnlineChangeTraceInfo()
3205 self.__checkEncoding()
3206 return True
3207 else:
3208 self.lastModified = QFileInfo(fn).lastModified()
3209 return False
3210
3211 def saveFileAs(self, path=None, toProject=False):
3212 """
3213 Public slot to save a file with a new name.
3214
3215 @param path directory to save the file in (string)
3216 @keyparam toProject flag indicating a save to project operation
3217 (boolean)
3218 @return tuple of two values (boolean, string) giving a success
3219 indicator and the name of the saved file
3220 """
3221 return self.saveFile(True, path)
3222
3223 def handleRenamed(self, fn):
3224 """
3225 Public slot to handle the editorRenamed signal.
3226
3227 @param fn filename to be set for the editor (string).
3228 """
3229 self.__clearBreakpoints(fn)
3230
3231 self.fileName = fn
3232 self.setWindowTitle(self.fileName)
3233
3234 self.__loadEditorConfig()
3235
3236 if self.lexer_ is None:
3237 self.setLanguage(self.fileName)
3238
3239 self.lastModified = QFileInfo(self.fileName).lastModified()
3240 self.vm.setEditorName(self, self.fileName)
3241 self.__updateReadOnly(True)
3242
3243 def fileRenamed(self, fn):
3244 """
3245 Public slot to handle the editorRenamed signal.
3246
3247 @param fn filename to be set for the editor (string).
3248 """
3249 self.handleRenamed(fn)
3250 if not self.inFileRenamed:
3251 self.inFileRenamed = True
3252 self.editorRenamed.emit(self.fileName)
3253 self.inFileRenamed = False
3254
3255 ###########################################################################
3256 ## Utility methods below
3257 ###########################################################################
3258
3259 def ensureVisible(self, line, expand=False):
3260 """
3261 Public slot to ensure, that the specified line is visible.
3262
3263 @param line line number to make visible
3264 @type int
3265 @keyparam expand flag indicating to expand all folds
3266 @type bool
3267 """
3268 self.ensureLineVisible(line - 1)
3269 if expand:
3270 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line - 1,
3271 QsciScintilla.SC_FOLDACTION_EXPAND)
3272
3273 def ensureVisibleTop(self, line, expand=False):
3274 """
3275 Public slot to ensure, that the specified line is visible at the top
3276 of the editor.
3277
3278 @param line line number to make visible
3279 @type int
3280 @keyparam expand flag indicating to expand all folds
3281 @type bool
3282 """
3283 self.ensureVisible(line)
3284 self.setFirstVisibleLine(line - 1)
3285 self.ensureCursorVisible()
3286 if expand:
3287 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line - 1,
3288 QsciScintilla.SC_FOLDACTION_EXPAND)
3289
3290 def __marginClicked(self, margin, line, modifiers):
3291 """
3292 Private slot to handle the marginClicked signal.
3293
3294 @param margin id of the clicked margin (integer)
3295 @param line line number of the click (integer)
3296 @param modifiers keyboard modifiers (Qt.KeyboardModifiers)
3297 """
3298 if margin == self.__bmMargin:
3299 self.toggleBookmark(line + 1)
3300 elif margin == self.__bpMargin:
3301 self.__toggleBreakpoint(line + 1)
3302 elif margin == self.__indicMargin:
3303 if self.markersAtLine(line) & (1 << self.syntaxerror):
3304 self.__showSyntaxError(line)
3305 elif self.markersAtLine(line) & (1 << self.warning):
3306 self.__showWarning(line)
3307
3308 def handleMonospacedEnable(self):
3309 """
3310 Public slot to handle the Use Monospaced Font context menu entry.
3311 """
3312 if self.menuActs["MonospacedFont"].isChecked():
3313 if not self.lexer_:
3314 self.setMonospaced(True)
3315 else:
3316 if self.lexer_:
3317 self.lexer_.readSettings(
3318 Preferences.Prefs.settings, "Scintilla")
3319 if self.lexer_.hasSubstyles():
3320 self.lexer_.readSubstyles(self)
3321 self.lexer_.initProperties()
3322 self.setMonospaced(False)
3323 self.__setMarginsDisplay()
3324
3325 def getWordBoundaries(self, line, index, useWordChars=True):
3326 """
3327 Public method to get the word boundaries at a position.
3328
3329 @param line number of line to look at (int)
3330 @param index position to look at (int)
3331 @keyparam useWordChars flag indicating to use the wordCharacters
3332 method (boolean)
3333 @return tuple with start and end indexes of the word at the position
3334 (integer, integer)
3335 """
3336 text = self.text(line)
3337 if self.caseSensitive():
3338 cs = Qt.CaseSensitive
3339 else:
3340 cs = Qt.CaseInsensitive
3341 wc = self.wordCharacters()
3342 if wc is None or not useWordChars:
3343 regExp = QRegExp(r'[^\w_]', cs)
3344 else:
3345 wc = re.sub(r'\w', "", wc)
3346 regExp = QRegExp(r'[^\w{0}]'.format(re.escape(wc)), cs)
3347 start = regExp.lastIndexIn(text, index) + 1
3348 end = regExp.indexIn(text, index)
3349 if start == end + 1 and index > 0:
3350 # we are on a word boundary, try again
3351 start = regExp.lastIndexIn(text, index - 1) + 1
3352 if start == -1:
3353 start = 0
3354 if end == -1:
3355 end = len(text)
3356
3357 return (start, end)
3358
3359 def getWord(self, line, index, direction=0, useWordChars=True):
3360 """
3361 Public method to get the word at a position.
3362
3363 @param line number of line to look at (int)
3364 @param index position to look at (int)
3365 @param direction direction to look in (0 = whole word, 1 = left,
3366 2 = right)
3367 @keyparam useWordChars flag indicating to use the wordCharacters
3368 method (boolean)
3369 @return the word at that position (string)
3370 """
3371 start, end = self.getWordBoundaries(line, index, useWordChars)
3372 if direction == 1:
3373 end = index
3374 elif direction == 2:
3375 start = index
3376 if end > start:
3377 text = self.text(line)
3378 word = text[start:end]
3379 else:
3380 word = ''
3381 return word
3382
3383 def getWordLeft(self, line, index):
3384 """
3385 Public method to get the word to the left of a position.
3386
3387 @param line number of line to look at (int)
3388 @param index position to look at (int)
3389 @return the word to the left of that position (string)
3390 """
3391 return self.getWord(line, index, 1)
3392
3393 def getWordRight(self, line, index):
3394 """
3395 Public method to get the word to the right of a position.
3396
3397 @param line number of line to look at (int)
3398 @param index position to look at (int)
3399 @return the word to the right of that position (string)
3400 """
3401 return self.getWord(line, index, 2)
3402
3403 def getCurrentWord(self):
3404 """
3405 Public method to get the word at the current position.
3406
3407 @return the word at that current position (string)
3408 """
3409 line, index = self.getCursorPosition()
3410 return self.getWord(line, index)
3411
3412 def getCurrentWordBoundaries(self):
3413 """
3414 Public method to get the word boundaries at the current position.
3415
3416 @return tuple with start and end indexes of the current word
3417 (integer, integer)
3418 """
3419 line, index = self.getCursorPosition()
3420 return self.getWordBoundaries(line, index)
3421
3422 def selectWord(self, line, index):
3423 """
3424 Public method to select the word at a position.
3425
3426 @param line number of line to look at (int)
3427 @param index position to look at (int)
3428 """
3429 start, end = self.getWordBoundaries(line, index)
3430 self.setSelection(line, start, line, end)
3431
3432 def selectCurrentWord(self):
3433 """
3434 Public method to select the current word.
3435 """
3436 line, index = self.getCursorPosition()
3437 self.selectWord(line, index)
3438
3439 def __getCharacter(self, pos):
3440 """
3441 Private method to get the character to the left of the current position
3442 in the current line.
3443
3444 @param pos position to get character at (integer)
3445 @return requested character or "", if there are no more (string) and
3446 the next position (i.e. pos - 1)
3447 """
3448 if pos <= 0:
3449 return "", pos
3450
3451 pos = self.positionBefore(pos)
3452 ch = self.charAt(pos)
3453
3454 # Don't go past the end of the previous line
3455 if ch == '\n' or ch == '\r':
3456 return "", pos
3457
3458 return ch, pos
3459
3460 def getSearchText(self, selectionOnly=False):
3461 """
3462 Public method to determine the selection or the current word for the
3463 next search operation.
3464
3465 @param selectionOnly flag indicating that only selected text should be
3466 returned (boolean)
3467 @return selection or current word (string)
3468 """
3469 if self.hasSelectedText():
3470 text = self.selectedText()
3471 if '\r' in text or '\n' in text:
3472 # the selection contains at least a newline, it is
3473 # unlikely to be the expression to search for
3474 return ''
3475
3476 return text
3477
3478 if not selectionOnly:
3479 # no selected text, determine the word at the current position
3480 return self.getCurrentWord()
3481
3482 return ''
3483
3484 def setSearchIndicator(self, startPos, indicLength):
3485 """
3486 Public method to set a search indicator for the given range.
3487
3488 @param startPos start position of the indicator (integer)
3489 @param indicLength length of the indicator (integer)
3490 """
3491 self.setIndicatorRange(self.searchIndicator, startPos, indicLength)
3492 line = self.lineIndexFromPosition(startPos)[0]
3493 if line not in self.__searchIndicatorLines:
3494 self.__searchIndicatorLines.append(line)
3495
3496 def clearSearchIndicators(self):
3497 """
3498 Public method to clear all search indicators.
3499 """
3500 self.clearAllIndicators(self.searchIndicator)
3501 self.__markedText = ""
3502 self.__searchIndicatorLines = []
3503 self.__markerMap.update()
3504
3505 def __markOccurrences(self):
3506 """
3507 Private method to mark all occurrences of the current word.
3508 """
3509 word = self.getCurrentWord()
3510 if not word:
3511 self.clearSearchIndicators()
3512 return
3513
3514 if self.__markedText == word:
3515 return
3516
3517 self.clearSearchIndicators()
3518 ok = self.findFirstTarget(word, False, self.caseSensitive(), True,
3519 0, 0)
3520 while ok:
3521 tgtPos, tgtLen = self.getFoundTarget()
3522 self.setSearchIndicator(tgtPos, tgtLen)
3523 ok = self.findNextTarget()
3524 self.__markedText = word
3525 self.__markerMap.update()
3526
3527 def getSearchIndicatorLines(self):
3528 """
3529 Public method to get the lines containing a search indicator.
3530
3531 @return list of lines containing a search indicator (list of integer)
3532 """
3533 return self.__searchIndicatorLines[:]
3534
3535 def updateMarkerMap(self):
3536 """
3537 Public method to initiate an update of the marker map.
3538 """
3539 self.__markerMap.update()
3540
3541 ###########################################################################
3542 ## Highlightinh marker handling methods below
3543 ###########################################################################
3544
3545 def setHighlight(self, startLine, startIndex, endLine, endIndex):
3546 """
3547 Public method to set a text highlight.
3548
3549 @param startLine line of the highlight start
3550 @type int
3551 @param startIndex index of the highlight start
3552 @type int
3553 @param endLine line of the highlight end
3554 @type int
3555 @param endIndex index of the highlight end
3556 @type int
3557 """
3558 self.setIndicator(self.highlightIndicator, startLine, startIndex,
3559 endLine, endIndex)
3560
3561 def clearAllHighlights(self):
3562 """
3563 Public method to clear all highlights.
3564 """
3565 self.clearAllIndicators(self.highlightIndicator)
3566
3567 def clearHighlight(self, startLine, startIndex, endLine, endIndex):
3568 """
3569 Public method to clear a text highlight.
3570
3571 @param startLine line of the highlight start
3572 @type int
3573 @param startIndex index of the highlight start
3574 @type int
3575 @param endLine line of the highlight end
3576 @type int
3577 @param endIndex index of the highlight end
3578 @type int
3579 """
3580 self.clearIndicator(self.highlightIndicator, startLine, startIndex,
3581 endLine, endIndex)
3582
3583 ###########################################################################
3584 ## Comment handling methods below
3585 ###########################################################################
3586
3587 def __isCommentedLine(self, line, commentStr):
3588 """
3589 Private method to check, if the given line is a comment line as
3590 produced by the configured comment rules.
3591
3592 @param line text of the line to check (string)
3593 @param commentStr comment string to check against (string)
3594 @return flag indicating a commented line (boolean)
3595 """
3596 if Preferences.getEditor("CommentColumn0"):
3597 return line.startswith(commentStr)
3598 else:
3599 return line.strip().startswith(commentStr)
3600
3601 def toggleCommentBlock(self):
3602 """
3603 Public slot to toggle the comment of a block.
3604
3605 If the line of the cursor or the selection is not commented, it will
3606 be commented. If it is commented, the comment block will be removed.
3607 The later works independent of the current selection.
3608 """
3609 if self.lexer_ is None or not self.lexer_.canBlockComment():
3610 return
3611
3612 commentStr = self.lexer_.commentStr()
3613 line, index = self.getCursorPosition()
3614
3615 # check if line starts with our comment string (i.e. was commented
3616 # by our comment...() slots
3617 if self.hasSelectedText() and \
3618 self.__isCommentedLine(self.text(self.getSelection()[0]),
3619 commentStr):
3620 self.uncommentLineOrSelection()
3621 elif not self.__isCommentedLine(self.text(line), commentStr):
3622 # it doesn't, so comment the line or selection
3623 self.commentLineOrSelection()
3624 else:
3625 # determine the start of the comment block
3626 begline = line
3627 while begline > 0 and \
3628 self.__isCommentedLine(self.text(begline - 1), commentStr):
3629 begline -= 1
3630 # determine the end of the comment block
3631 endline = line
3632 lines = self.lines()
3633 while endline < lines and \
3634 self.__isCommentedLine(self.text(endline + 1), commentStr):
3635 endline += 1
3636
3637 self.setSelection(begline, 0, endline, self.lineLength(endline))
3638 self.uncommentLineOrSelection()
3639
3640 # reset the cursor
3641 self.setCursorPosition(line, index - len(commentStr))
3642
3643 def commentLine(self):
3644 """
3645 Public slot to comment the current line.
3646 """
3647 if self.lexer_ is None or not self.lexer_.canBlockComment():
3648 return
3649
3650 line, index = self.getCursorPosition()
3651 self.beginUndoAction()
3652 if Preferences.getEditor("CommentColumn0"):
3653 self.insertAt(self.lexer_.commentStr(), line, 0)
3654 else:
3655 self.insertAt(self.lexer_.commentStr(), line,
3656 self.indentation(line))
3657 self.endUndoAction()
3658
3659 def uncommentLine(self):
3660 """
3661 Public slot to uncomment the current line.
3662 """
3663 if self.lexer_ is None or not self.lexer_.canBlockComment():
3664 return
3665
3666 commentStr = self.lexer_.commentStr()
3667 line, index = self.getCursorPosition()
3668
3669 # check if line starts with our comment string (i.e. was commented
3670 # by our comment...() slots
3671 if not self.__isCommentedLine(self.text(line), commentStr):
3672 return
3673
3674 # now remove the comment string
3675 self.beginUndoAction()
3676 if Preferences.getEditor("CommentColumn0"):
3677 self.setSelection(line, 0, line, len(commentStr))
3678 else:
3679 self.setSelection(line, self.indentation(line),
3680 line, self.indentation(line) + len(commentStr))
3681 self.removeSelectedText()
3682 self.endUndoAction()
3683
3684 def commentSelection(self):
3685 """
3686 Public slot to comment the current selection.
3687 """
3688 if self.lexer_ is None or not self.lexer_.canBlockComment():
3689 return
3690
3691 if not self.hasSelectedText():
3692 return
3693
3694 commentStr = self.lexer_.commentStr()
3695
3696 # get the selection boundaries
3697 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
3698 if indexTo == 0:
3699 endLine = lineTo - 1
3700 else:
3701 endLine = lineTo
3702
3703 self.beginUndoAction()
3704 # iterate over the lines
3705 for line in range(lineFrom, endLine + 1):
3706 if Preferences.getEditor("CommentColumn0"):
3707 self.insertAt(commentStr, line, 0)
3708 else:
3709 self.insertAt(commentStr, line, self.indentation(line))
3710
3711 # change the selection accordingly
3712 self.setSelection(lineFrom, 0, endLine + 1, 0)
3713 self.endUndoAction()
3714
3715 def uncommentSelection(self):
3716 """
3717 Public slot to uncomment the current selection.
3718 """
3719 if self.lexer_ is None or not self.lexer_.canBlockComment():
3720 return
3721
3722 if not self.hasSelectedText():
3723 return
3724
3725 commentStr = self.lexer_.commentStr()
3726
3727 # get the selection boundaries
3728 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
3729 if indexTo == 0:
3730 endLine = lineTo - 1
3731 else:
3732 endLine = lineTo
3733
3734 self.beginUndoAction()
3735 # iterate over the lines
3736 for line in range(lineFrom, endLine + 1):
3737 # check if line starts with our comment string (i.e. was commented
3738 # by our comment...() slots
3739 if not self.__isCommentedLine(self.text(line), commentStr):
3740 continue
3741
3742 if Preferences.getEditor("CommentColumn0"):
3743 self.setSelection(line, 0, line, len(commentStr))
3744 else:
3745 self.setSelection(line,
3746 self.indentation(line),
3747 line,
3748 self.indentation(line) + len(commentStr))
3749 self.removeSelectedText()
3750
3751 # adjust selection start
3752 if line == lineFrom:
3753 indexFrom -= len(commentStr)
3754 if indexFrom < 0:
3755 indexFrom = 0
3756
3757 # adjust selection end
3758 if line == lineTo:
3759 indexTo -= len(commentStr)
3760 if indexTo < 0:
3761 indexTo = 0
3762
3763 # change the selection accordingly
3764 self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
3765 self.endUndoAction()
3766
3767 def commentLineOrSelection(self):
3768 """
3769 Public slot to comment the current line or current selection.
3770 """
3771 if self.hasSelectedText():
3772 self.commentSelection()
3773 else:
3774 self.commentLine()
3775
3776 def uncommentLineOrSelection(self):
3777 """
3778 Public slot to uncomment the current line or current selection.
3779 """
3780 if self.hasSelectedText():
3781 self.uncommentSelection()
3782 else:
3783 self.uncommentLine()
3784
3785 def streamCommentLine(self):
3786 """
3787 Public slot to stream comment the current line.
3788 """
3789 if self.lexer_ is None or not self.lexer_.canStreamComment():
3790 return
3791
3792 commentStr = self.lexer_.streamCommentStr()
3793 line, index = self.getCursorPosition()
3794
3795 self.beginUndoAction()
3796 self.insertAt(commentStr['end'], line, self.lineLength(line))
3797 self.insertAt(commentStr['start'], line, 0)
3798 self.endUndoAction()
3799
3800 def streamCommentSelection(self):
3801 """
3802 Public slot to comment the current selection.
3803 """
3804 if self.lexer_ is None or not self.lexer_.canStreamComment():
3805 return
3806
3807 if not self.hasSelectedText():
3808 return
3809
3810 commentStr = self.lexer_.streamCommentStr()
3811
3812 # get the selection boundaries
3813 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
3814 if indexTo == 0:
3815 endLine = lineTo - 1
3816 endIndex = self.lineLength(endLine)
3817 else:
3818 endLine = lineTo
3819 endIndex = indexTo
3820
3821 self.beginUndoAction()
3822 self.insertAt(commentStr['end'], endLine, endIndex)
3823 self.insertAt(commentStr['start'], lineFrom, indexFrom)
3824
3825 # change the selection accordingly
3826 if indexTo > 0:
3827 indexTo += len(commentStr['end'])
3828 if lineFrom == endLine:
3829 indexTo += len(commentStr['start'])
3830 self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
3831 self.endUndoAction()
3832
3833 def streamCommentLineOrSelection(self):
3834 """
3835 Public slot to stream comment the current line or current selection.
3836 """
3837 if self.hasSelectedText():
3838 self.streamCommentSelection()
3839 else:
3840 self.streamCommentLine()
3841
3842 def boxCommentLine(self):
3843 """
3844 Public slot to box comment the current line.
3845 """
3846 if self.lexer_ is None or not self.lexer_.canBoxComment():
3847 return
3848
3849 commentStr = self.lexer_.boxCommentStr()
3850 line, index = self.getCursorPosition()
3851
3852 eol = self.getLineSeparator()
3853 self.beginUndoAction()
3854 self.insertAt(eol, line, self.lineLength(line))
3855 self.insertAt(commentStr['end'], line + 1, 0)
3856 self.insertAt(commentStr['middle'], line, 0)
3857 self.insertAt(eol, line, 0)
3858 self.insertAt(commentStr['start'], line, 0)
3859 self.endUndoAction()
3860
3861 def boxCommentSelection(self):
3862 """
3863 Public slot to box comment the current selection.
3864 """
3865 if self.lexer_ is None or not self.lexer_.canBoxComment():
3866 return
3867
3868 if not self.hasSelectedText():
3869 return
3870
3871 commentStr = self.lexer_.boxCommentStr()
3872
3873 # get the selection boundaries
3874 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
3875 if indexTo == 0:
3876 endLine = lineTo - 1
3877 else:
3878 endLine = lineTo
3879
3880 self.beginUndoAction()
3881 # iterate over the lines
3882 for line in range(lineFrom, endLine + 1):
3883 self.insertAt(commentStr['middle'], line, 0)
3884
3885 # now do the comments before and after the selection
3886 eol = self.getLineSeparator()
3887 self.insertAt(eol, endLine, self.lineLength(endLine))
3888 self.insertAt(commentStr['end'], endLine + 1, 0)
3889 self.insertAt(eol, lineFrom, 0)
3890 self.insertAt(commentStr['start'], lineFrom, 0)
3891
3892 # change the selection accordingly
3893 self.setSelection(lineFrom, 0, endLine + 3, 0)
3894 self.endUndoAction()
3895
3896 def boxCommentLineOrSelection(self):
3897 """
3898 Public slot to box comment the current line or current selection.
3899 """
3900 if self.hasSelectedText():
3901 self.boxCommentSelection()
3902 else:
3903 self.boxCommentLine()
3904
3905 ###########################################################################
3906 ## Indentation handling methods below
3907 ###########################################################################
3908
3909 def __indentLine(self, indent=True):
3910 """
3911 Private method to indent or unindent the current line.
3912
3913 @param indent flag indicating an indent operation (boolean)
3914 <br />If the flag is true, an indent operation is performed.
3915 Otherwise the current line is unindented.
3916 """
3917 line, index = self.getCursorPosition()
3918 self.beginUndoAction()
3919 if indent:
3920 self.indent(line)
3921 else:
3922 self.unindent(line)
3923 self.endUndoAction()
3924 if indent:
3925 self.setCursorPosition(line, index + self.indentationWidth())
3926 else:
3927 self.setCursorPosition(line, index - self.indentationWidth())
3928
3929 def __indentSelection(self, indent=True):
3930 """
3931 Private method to indent or unindent the current selection.
3932
3933 @param indent flag indicating an indent operation (boolean)
3934 <br />If the flag is true, an indent operation is performed.
3935 Otherwise the current line is unindented.
3936 """
3937 if not self.hasSelectedText():
3938 return
3939
3940 # get the selection
3941 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
3942
3943 if indexTo == 0:
3944 endLine = lineTo - 1
3945 else:
3946 endLine = lineTo
3947
3948 self.beginUndoAction()
3949 # iterate over the lines
3950 for line in range(lineFrom, endLine + 1):
3951 if indent:
3952 self.indent(line)
3953 else:
3954 self.unindent(line)
3955 self.endUndoAction()
3956 if indent:
3957 if indexTo == 0:
3958 self.setSelection(
3959 lineFrom, indexFrom + self.indentationWidth(),
3960 lineTo, 0)
3961 else:
3962 self.setSelection(
3963 lineFrom, indexFrom + self.indentationWidth(),
3964 lineTo, indexTo + self.indentationWidth())
3965 else:
3966 indexStart = indexFrom - self.indentationWidth()
3967 if indexStart < 0:
3968 indexStart = 0
3969 indexEnd = indexTo - self.indentationWidth()
3970 if indexEnd < 0:
3971 indexEnd = 0
3972 self.setSelection(lineFrom, indexStart, lineTo, indexEnd)
3973
3974 def indentLineOrSelection(self):
3975 """
3976 Public slot to indent the current line or current selection.
3977 """
3978 if self.hasSelectedText():
3979 self.__indentSelection(True)
3980 else:
3981 self.__indentLine(True)
3982
3983 def unindentLineOrSelection(self):
3984 """
3985 Public slot to unindent the current line or current selection.
3986 """
3987 if self.hasSelectedText():
3988 self.__indentSelection(False)
3989 else:
3990 self.__indentLine(False)
3991
3992 def smartIndentLineOrSelection(self):
3993 """
3994 Public slot to indent current line smartly.
3995 """
3996 if self.hasSelectedText():
3997 if self.lexer_ and self.lexer_.hasSmartIndent():
3998 self.lexer_.smartIndentSelection(self)
3999 else:
4000 self.__indentSelection(True)
4001 else:
4002 if self.lexer_ and self.lexer_.hasSmartIndent():
4003 self.lexer_.smartIndentLine(self)
4004 else:
4005 self.__indentLine(True)
4006
4007 def gotoLine(self, line, pos=1, firstVisible=False, expand=False):
4008 """
4009 Public slot to jump to the beginning of a line.
4010
4011 @param line line number to go to
4012 @type int
4013 @keyparam pos position in line to go to
4014 @type int
4015 @keyparam firstVisible flag indicating to make the line the first
4016 visible line
4017 @type bool
4018 @keyparam expand flag indicating to expand all folds
4019 @type bool
4020 """
4021 self.setCursorPosition(line - 1, pos - 1)
4022 if firstVisible:
4023 self.ensureVisibleTop(line, expand)
4024 else:
4025 self.ensureVisible(line, expand)
4026
4027 def __textChanged(self):
4028 """
4029 Private slot to handle a change of the editor text.
4030
4031 This slot defers the handling to the next time the event loop
4032 is run in order to ensure, that cursor position has been updated
4033 by the underlying Scintilla editor.
4034 """
4035 QTimer.singleShot(0, self.__saveLastEditPosition)
4036
4037 def __saveLastEditPosition(self):
4038 """
4039 Private slot to record the last edit position.
4040 """
4041 self.__lastEditPosition = self.getCursorPosition()
4042 self.lastEditPositionAvailable.emit()
4043
4044 def isLastEditPositionAvailable(self):
4045 """
4046 Public method to check, if a last edit position is available.
4047
4048 @return flag indicating availability (boolean)
4049 """
4050 return self.__lastEditPosition is not None
4051
4052 def gotoLastEditPosition(self):
4053 """
4054 Public method to move the cursor to the last edit position.
4055 """
4056 self.setCursorPosition(*self.__lastEditPosition)
4057 self.ensureVisible(self.__lastEditPosition[0])
4058
4059 def gotoMethodClass(self, goUp=False):
4060 """
4061 Public method to go to the next Python method or class definition.
4062
4063 @param goUp flag indicating the move direction (boolean)
4064 """
4065 if self.isPyFile() or self.isRubyFile():
4066 lineNo = self.getCursorPosition()[0]
4067 line = self.text(lineNo)
4068 if line.strip().startswith(("class ", "def ", "module ")):
4069 if goUp:
4070 lineNo -= 1
4071 else:
4072 lineNo += 1
4073 while True:
4074 if goUp and lineNo < 0:
4075 self.setCursorPosition(0, 0)
4076 self.ensureVisible(0)
4077 return
4078 elif not goUp and lineNo == self.lines():
4079 lineNo = self.lines() - 1
4080 self.setCursorPosition(lineNo, self.lineLength(lineNo))
4081 self.ensureVisible(lineNo)
4082 return
4083
4084 line = self.text(lineNo)
4085 if line.strip().startswith(("class ", "def ", "module ")):
4086 # try 'def ' first because it occurs more often
4087 first = line.find("def ")
4088 if first > -1:
4089 first += 4
4090 else:
4091 first = line.find("class ")
4092 if first > -1:
4093 first += 6
4094 else:
4095 first = line.find("module ") + 7
4096 match = re.search("[:(]", line)
4097 if match:
4098 end = match.start()
4099 else:
4100 end = self.lineLength(lineNo) - 1
4101 self.setSelection(lineNo, first, lineNo, end)
4102 self.ensureVisible(lineNo)
4103 return
4104
4105 if goUp:
4106 lineNo -= 1
4107 else:
4108 lineNo += 1
4109
4110 ###########################################################################
4111 ## Setup methods below
4112 ###########################################################################
4113
4114 def readSettings(self):
4115 """
4116 Public slot to read the settings into our lexer.
4117 """
4118 # read the lexer settings and reinit the properties
4119 if self.lexer_ is not None:
4120 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla")
4121 if self.lexer_.hasSubstyles():
4122 self.lexer_.readSubstyles(self)
4123 self.lexer_.initProperties()
4124
4125 self.lexer_.setDefaultColor(self.lexer_.color(0))
4126 self.lexer_.setDefaultPaper(self.lexer_.paper(0))
4127
4128 self.__bindLexer(self.fileName)
4129 self.recolor()
4130
4131 # read the typing completer settings
4132 if self.completer is not None:
4133 self.completer.readSettings()
4134
4135 # set the line marker colours or pixmap
4136 if Preferences.getEditor("LineMarkersBackground"):
4137 self.markerDefine(QsciScintilla.Background, self.currentline)
4138 self.markerDefine(QsciScintilla.Background, self.errorline)
4139 self.__setLineMarkerColours()
4140 else:
4141 self.markerDefine(
4142 UI.PixmapCache.getPixmap("currentLineMarker.png"),
4143 self.currentline)
4144 self.markerDefine(
4145 UI.PixmapCache.getPixmap("errorLineMarker.png"),
4146 self.errorline)
4147
4148 # set the text display
4149 self.__setTextDisplay()
4150
4151 # set margin 0 and 2 configuration
4152 self.__setMarginsDisplay()
4153
4154 # set the auto-completion function
4155 self.__acCache.setSize(
4156 Preferences.getEditor("AutoCompletionCacheSize"))
4157 self.__acCache.setMaximumCacheTime(
4158 Preferences.getEditor("AutoCompletionCacheTime"))
4159 self.__acCacheEnabled = Preferences.getEditor(
4160 "AutoCompletionCacheEnabled")
4161 acTimeout = Preferences.getEditor("AutoCompletionTimeout")
4162 if acTimeout != self.__acTimer.interval:
4163 self.__acTimer.setInterval(acTimeout)
4164 self.__setAutoCompletion()
4165
4166 # set the calltips function
4167 self.__setCallTips()
4168
4169 # set the autosave flags
4170 self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
4171
4172 if Preferences.getEditor("MiniContextMenu") != self.miniMenu:
4173 # regenerate context menu
4174 self.__initContextMenu()
4175 else:
4176 # set checked context menu items
4177 self.menuActs["AutoCompletionEnable"].setChecked(
4178 self.autoCompletionThreshold() != -1)
4179 self.menuActs["MonospacedFont"].setChecked(
4180 self.useMonospaced)
4181 self.menuActs["AutosaveEnable"].setChecked(
4182 self.autosaveEnabled and not self.autosaveManuallyDisabled)
4183
4184 # regenerate the margins context menu(s)
4185 self.__initContextMenuMargins()
4186
4187 if Preferences.getEditor("MarkOccurrencesEnabled"):
4188 self.__markOccurrencesTimer.setInterval(
4189 Preferences.getEditor("MarkOccurrencesTimeout"))
4190 else:
4191 self.__markOccurrencesTimer.stop()
4192 self.clearSearchIndicators()
4193
4194 if Preferences.getEditor("OnlineSyntaxCheck"):
4195 self.__onlineSyntaxCheckTimer.setInterval(
4196 Preferences.getEditor("OnlineSyntaxCheckInterval") * 1000)
4197 else:
4198 self.__onlineSyntaxCheckTimer.stop()
4199
4200 if Preferences.getEditor("OnlineChangeTrace"):
4201 self.__onlineChangeTraceTimer.setInterval(
4202 Preferences.getEditor("OnlineChangeTraceInterval"))
4203 else:
4204 self.__onlineChangeTraceTimer.stop()
4205 self.__deleteAllChangeMarkers()
4206 self.markerDefine(self.__createChangeMarkerPixmap(
4207 "OnlineChangeTraceMarkerUnsaved"), self.__changeMarkerUnsaved)
4208 self.markerDefine(self.__createChangeMarkerPixmap(
4209 "OnlineChangeTraceMarkerSaved"), self.__changeMarkerSaved)
4210
4211 # refresh the annotations display
4212 self.__refreshAnnotations()
4213
4214 self.__markerMap.setMapPosition(
4215 Preferences.getEditor("ShowMarkerMapOnRight"))
4216 self.__markerMap.initColors()
4217
4218 self.settingsRead.emit()
4219
4220 def __setLineMarkerColours(self):
4221 """
4222 Private method to set the line marker colours.
4223 """
4224 self.setMarkerForegroundColor(
4225 Preferences.getEditorColour("CurrentMarker"), self.currentline)
4226 self.setMarkerBackgroundColor(
4227 Preferences.getEditorColour("CurrentMarker"), self.currentline)
4228 self.setMarkerForegroundColor(
4229 Preferences.getEditorColour("ErrorMarker"), self.errorline)
4230 self.setMarkerBackgroundColor(
4231 Preferences.getEditorColour("ErrorMarker"), self.errorline)
4232
4233 def __setMarginsDisplay(self):
4234 """
4235 Private method to configure margins 0 and 2.
4236 """
4237 # set the settings for all margins
4238 self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont"))
4239 self.setMarginsForegroundColor(
4240 Preferences.getEditorColour("MarginsForeground"))
4241 self.setMarginsBackgroundColor(
4242 Preferences.getEditorColour("MarginsBackground"))
4243
4244 # reset standard margins settings
4245 for margin in range(5):
4246 self.setMarginLineNumbers(margin, False)
4247 self.setMarginMarkerMask(margin, 0)
4248 self.setMarginWidth(margin, 0)
4249 self.setMarginSensitivity(margin, False)
4250
4251 # set marker margin(s) settings
4252 self.__bmMargin = 0
4253 self.__linenoMargin = 1
4254 self.__bpMargin = 2
4255 self.__foldMargin = 3
4256 self.__indicMargin = 4
4257
4258 marginBmMask = (1 << self.bookmark)
4259 self.setMarginWidth(self.__bmMargin, 16)
4260 self.setMarginSensitivity(self.__bmMargin, True)
4261 self.setMarginMarkerMask(self.__bmMargin, marginBmMask)
4262
4263 marginBpMask = (1 << self.breakpoint) | \
4264 (1 << self.cbreakpoint) | \
4265 (1 << self.tbreakpoint) | \
4266 (1 << self.tcbreakpoint) | \
4267 (1 << self.dbreakpoint)
4268 self.setMarginWidth(self.__bpMargin, 16)
4269 self.setMarginSensitivity(self.__bpMargin, True)
4270 self.setMarginMarkerMask(self.__bpMargin, marginBpMask)
4271
4272 marginIndicMask = (1 << self.syntaxerror) | \
4273 (1 << self.notcovered) | \
4274 (1 << self.taskmarker) | \
4275 (1 << self.warning) | \
4276 (1 << self.__changeMarkerUnsaved) | \
4277 (1 << self.__changeMarkerSaved) | \
4278 (1 << self.currentline) | \
4279 (1 << self.errorline)
4280 self.setMarginWidth(self.__indicMargin, 16)
4281 self.setMarginSensitivity(self.__indicMargin, True)
4282 self.setMarginMarkerMask(self.__indicMargin, marginIndicMask)
4283
4284 # set linenumber margin settings
4285 linenoMargin = Preferences.getEditor("LinenoMargin")
4286 self.setMarginLineNumbers(self.__linenoMargin, linenoMargin)
4287 if linenoMargin:
4288 self.__resizeLinenoMargin()
4289 else:
4290 self.setMarginWidth(self.__linenoMargin, 0)
4291
4292 # set folding margin settings
4293 if Preferences.getEditor("FoldingMargin"):
4294 self.setMarginWidth(self.__foldMargin, 16)
4295 folding = Preferences.getEditor("FoldingStyle")
4296 try:
4297 folding = QsciScintilla.FoldStyle(folding)
4298 except AttributeError:
4299 pass
4300 self.setFolding(folding, self.__foldMargin)
4301 self.setFoldMarginColors(
4302 Preferences.getEditorColour("FoldmarginBackground"),
4303 Preferences.getEditorColour("FoldmarginBackground"))
4304 self.setFoldMarkersColors(
4305 Preferences.getEditorColour("FoldMarkersForeground"),
4306 Preferences.getEditorColour("FoldMarkersBackground"))
4307 else:
4308 self.setMarginWidth(self.__foldMargin, 0)
4309 self.setFolding(QsciScintilla.NoFoldStyle, self.__foldMargin)
4310
4311 def __resizeLinenoMargin(self):
4312 """
4313 Private slot to resize the line numbers margin.
4314 """
4315 linenoMargin = Preferences.getEditor("LinenoMargin")
4316 if linenoMargin:
4317 self.setMarginWidth(
4318 self.__linenoMargin, '8' * (len(str(self.lines())) + 1))
4319
4320 def __setTabAndIndent(self):
4321 """
4322 Private method to set indentation size and style and tab width.
4323 """
4324 self.setTabWidth(self.__getEditorConfig("TabWidth"))
4325 self.setIndentationWidth(self.__getEditorConfig("IndentWidth"))
4326 if self.lexer_ and self.lexer_.alwaysKeepTabs():
4327 self.setIndentationsUseTabs(True)
4328 else:
4329 self.setIndentationsUseTabs(
4330 self.__getEditorConfig("TabForIndentation"))
4331
4332 def __setTextDisplay(self):
4333 """
4334 Private method to configure the text display.
4335 """
4336 self.__setTabAndIndent()
4337
4338 self.setTabIndents(Preferences.getEditor("TabIndents"))
4339 self.setBackspaceUnindents(Preferences.getEditor("TabIndents"))
4340 self.setIndentationGuides(Preferences.getEditor("IndentationGuides"))
4341 self.setIndentationGuidesBackgroundColor(
4342 Preferences.getEditorColour("IndentationGuidesBackground"))
4343 self.setIndentationGuidesForegroundColor(
4344 Preferences.getEditorColour("IndentationGuidesForeground"))
4345 if Preferences.getEditor("ShowWhitespace"):
4346 self.setWhitespaceVisibility(QsciScintilla.WsVisible)
4347 try:
4348 self.setWhitespaceForegroundColor(
4349 Preferences.getEditorColour("WhitespaceForeground"))
4350 self.setWhitespaceBackgroundColor(
4351 Preferences.getEditorColour("WhitespaceBackground"))
4352 self.setWhitespaceSize(
4353 Preferences.getEditor("WhitespaceSize"))
4354 except AttributeError:
4355 # QScintilla before 2.5 doesn't support this
4356 pass
4357 else:
4358 self.setWhitespaceVisibility(QsciScintilla.WsInvisible)
4359 self.setEolVisibility(Preferences.getEditor("ShowEOL"))
4360 self.setAutoIndent(Preferences.getEditor("AutoIndentation"))
4361 if Preferences.getEditor("BraceHighlighting"):
4362 self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
4363 else:
4364 self.setBraceMatching(QsciScintilla.NoBraceMatch)
4365 self.setMatchedBraceForegroundColor(
4366 Preferences.getEditorColour("MatchingBrace"))
4367 self.setMatchedBraceBackgroundColor(
4368 Preferences.getEditorColour("MatchingBraceBack"))
4369 self.setUnmatchedBraceForegroundColor(
4370 Preferences.getEditorColour("NonmatchingBrace"))
4371 self.setUnmatchedBraceBackgroundColor(
4372 Preferences.getEditorColour("NonmatchingBraceBack"))
4373 if Preferences.getEditor("CustomSelectionColours"):
4374 self.setSelectionBackgroundColor(
4375 Preferences.getEditorColour("SelectionBackground"))
4376 else:
4377 self.setSelectionBackgroundColor(
4378 QApplication.palette().color(QPalette.Highlight))
4379 if Preferences.getEditor("ColourizeSelText"):
4380 self.resetSelectionForegroundColor()
4381 elif Preferences.getEditor("CustomSelectionColours"):
4382 self.setSelectionForegroundColor(
4383 Preferences.getEditorColour("SelectionForeground"))
4384 else:
4385 self.setSelectionForegroundColor(
4386 QApplication.palette().color(QPalette.HighlightedText))
4387 self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol"))
4388 self.setCaretForegroundColor(
4389 Preferences.getEditorColour("CaretForeground"))
4390 self.setCaretLineBackgroundColor(
4391 Preferences.getEditorColour("CaretLineBackground"))
4392 self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible"))
4393 self.setCaretLineAlwaysVisible(
4394 Preferences.getEditor("CaretLineAlwaysVisible"))
4395 self.caretWidth = Preferences.getEditor("CaretWidth")
4396 self.setCaretWidth(self.caretWidth)
4397 self.caretLineFrameWidth = Preferences.getEditor("CaretLineFrameWidth")
4398 if QSCINTILLA_VERSION() >= 0x020B00:
4399 self.setCaretLineFrameWidth(self.caretLineFrameWidth)
4400 self.useMonospaced = Preferences.getEditor("UseMonospacedFont")
4401 self.setMonospaced(self.useMonospaced)
4402 edgeMode = Preferences.getEditor("EdgeMode")
4403 edge = QsciScintilla.EdgeMode(edgeMode)
4404 self.setEdgeMode(edge)
4405 if edgeMode:
4406 self.setEdgeColumn(Preferences.getEditor("EdgeColumn"))
4407 self.setEdgeColor(Preferences.getEditorColour("Edge"))
4408
4409 wrapVisualFlag = Preferences.getEditor("WrapVisualFlag")
4410 self.setWrapMode(Preferences.getEditor("WrapLongLinesMode"))
4411 self.setWrapVisualFlags(wrapVisualFlag, wrapVisualFlag)
4412 self.setWrapIndentMode(Preferences.getEditor("WrapIndentMode"))
4413 self.setWrapStartIndent(Preferences.getEditor("WrapStartIndent"))
4414
4415 self.zoomTo(Preferences.getEditor("ZoomFactor"))
4416
4417 self.searchIndicator = QsciScintilla.INDIC_CONTAINER
4418 self.indicatorDefine(
4419 self.searchIndicator, QsciScintilla.INDIC_BOX,
4420 Preferences.getEditorColour("SearchMarkers"))
4421 if not Preferences.getEditor("SearchMarkersEnabled") and \
4422 not Preferences.getEditor("QuickSearchMarkersEnabled") and \
4423 not Preferences.getEditor("MarkOccurrencesEnabled"):
4424 self.clearAllIndicators(self.searchIndicator)
4425
4426 self.spellingIndicator = QsciScintilla.INDIC_CONTAINER + 1
4427 self.indicatorDefine(
4428 self.spellingIndicator, QsciScintilla.INDIC_SQUIGGLE,
4429 Preferences.getEditorColour("SpellingMarkers"))
4430 self.__setSpelling()
4431
4432 self.highlightIndicator = QsciScintilla.INDIC_CONTAINER + 2
4433 self.indicatorDefine(
4434 self.highlightIndicator, QsciScintilla.INDIC_FULLBOX,
4435 Preferences.getEditorColour("HighlightMarker"))
4436
4437 self.setCursorFlashTime(QApplication.cursorFlashTime())
4438
4439 try:
4440 if Preferences.getEditor("AnnotationsEnabled"):
4441 self.setAnnotationDisplay(QsciScintilla.AnnotationBoxed)
4442 else:
4443 self.setAnnotationDisplay(QsciScintilla.AnnotationHidden)
4444 except AttributeError:
4445 pass
4446 self.__setAnnotationStyles()
4447
4448 if Preferences.getEditor("OverrideEditAreaColours"):
4449 self.setColor(Preferences.getEditorColour("EditAreaForeground"))
4450 self.setPaper(Preferences.getEditorColour("EditAreaBackground"))
4451
4452 self.setVirtualSpaceOptions(
4453 Preferences.getEditor("VirtualSpaceOptions"))
4454
4455 self.__markerMap.setEnabled(True)
4456
4457 def __setEolMode(self):
4458 """
4459 Private method to configure the eol mode of the editor.
4460 """
4461 if self.fileName and \
4462 self.project.isOpen() and \
4463 self.project.isProjectFile(self.fileName):
4464 eolMode = self.__getEditorConfig("EOLMode", nodefault=True)
4465 if eolMode is None:
4466 eolStr = self.project.getEolString()
4467 self.setEolModeByEolString(eolStr)
4468 else:
4469 self.setEolMode(eolMode)
4470 else:
4471 eolMode = self.__getEditorConfig("EOLMode")
4472 eolMode = QsciScintilla.EolMode(eolMode)
4473 self.setEolMode(eolMode)
4474 self.__eolChanged()
4475
4476 def __setAutoCompletion(self):
4477 """
4478 Private method to configure the autocompletion function.
4479 """
4480 if self.lexer_:
4481 self.setAutoCompletionFillupsEnabled(
4482 Preferences.getEditor("AutoCompletionFillups"))
4483 self.setAutoCompletionCaseSensitivity(
4484 Preferences.getEditor("AutoCompletionCaseSensitivity"))
4485 self.setAutoCompletionReplaceWord(
4486 Preferences.getEditor("AutoCompletionReplaceWord"))
4487 self.setAutoCompletionThreshold(0)
4488 try:
4489 self.setAutoCompletionUseSingle(
4490 Preferences.getEditor("AutoCompletionShowSingle"))
4491 except AttributeError:
4492 self.setAutoCompletionShowSingle(
4493 Preferences.getEditor("AutoCompletionShowSingle"))
4494 autoCompletionSource = Preferences.getEditor("AutoCompletionSource")
4495 if autoCompletionSource == QsciScintilla.AcsDocument:
4496 self.setAutoCompletionSource(QsciScintilla.AcsDocument)
4497 elif autoCompletionSource == QsciScintilla.AcsAPIs:
4498 self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
4499 else:
4500 self.setAutoCompletionSource(QsciScintilla.AcsAll)
4501
4502 self.maxLines = Preferences.getEditor("AutoCompletionMaxLines")
4503 self.maxChars = Preferences.getEditor("AutoCompletionMaxChars")
4504
4505 def __setCallTips(self):
4506 """
4507 Private method to configure the calltips function.
4508 """
4509 self.setCallTipsBackgroundColor(
4510 Preferences.getEditorColour("CallTipsBackground"))
4511 self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
4512 calltipsStyle = Preferences.getEditor("CallTipsStyle")
4513 try:
4514 self.setCallTipsPosition(
4515 Preferences.getEditor("CallTipsPosition"))
4516 except AttributeError:
4517 pass
4518
4519 if Preferences.getEditor("CallTipsEnabled"):
4520 if calltipsStyle == QsciScintilla.CallTipsNoContext:
4521 self.setCallTipsStyle(QsciScintilla.CallTipsNoContext)
4522 elif calltipsStyle == \
4523 QsciScintilla.CallTipsNoAutoCompletionContext:
4524 self.setCallTipsStyle(
4525 QsciScintilla.CallTipsNoAutoCompletionContext)
4526 else:
4527 self.setCallTipsStyle(QsciScintilla.CallTipsContext)
4528 else:
4529 self.setCallTipsStyle(QsciScintilla.CallTipsNone)
4530
4531 ###########################################################################
4532 ## Autocompletion handling methods below
4533 ###########################################################################
4534
4535 def canAutoCompleteFromAPIs(self):
4536 """
4537 Public method to check for API availablity.
4538
4539 @return flag indicating autocompletion from APIs is available (boolean)
4540 """
4541 return self.acAPI
4542
4543 def autoCompleteQScintilla(self):
4544 """
4545 Public method to perform an autocompletion using QScintilla methods.
4546 """
4547 self.__acText = ' ' # Prevent long running ACs to add results
4548 self.__acWatchdog.stop()
4549 if self.__acCompletions:
4550 return
4551
4552 acs = Preferences.getEditor("AutoCompletionSource")
4553 if acs == QsciScintilla.AcsDocument:
4554 self.autoCompleteFromDocument()
4555 elif acs == QsciScintilla.AcsAPIs:
4556 self.autoCompleteFromAPIs()
4557 elif acs == QsciScintilla.AcsAll:
4558 self.autoCompleteFromAll()
4559 else:
4560 E5MessageBox.information(
4561 self,
4562 self.tr("Autocompletion"),
4563 self.tr(
4564 """Autocompletion is not available because"""
4565 """ there is no autocompletion source set."""))
4566
4567 def setAutoCompletionEnabled(self, enable):
4568 """
4569 Public method to enable/disable autocompletion.
4570
4571 @param enable flag indicating the desired autocompletion status
4572 (boolean)
4573 """
4574 if enable:
4575 autoCompletionSource = \
4576 Preferences.getEditor("AutoCompletionSource")
4577 if autoCompletionSource == QsciScintilla.AcsDocument:
4578 self.setAutoCompletionSource(QsciScintilla.AcsDocument)
4579 elif autoCompletionSource == QsciScintilla.AcsAPIs:
4580 self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
4581 else:
4582 self.setAutoCompletionSource(QsciScintilla.AcsAll)
4583
4584 def __toggleAutoCompletionEnable(self):
4585 """
4586 Private slot to handle the Enable Autocompletion context menu entry.
4587 """
4588 if self.menuActs["AutoCompletionEnable"].isChecked():
4589 self.setAutoCompletionEnabled(True)
4590 else:
4591 self.setAutoCompletionEnabled(False)
4592
4593 #################################################################
4594 ## Support for autocompletion hook methods
4595 #################################################################
4596
4597 def __charAdded(self, charNumber):
4598 """
4599 Private slot called to handle the user entering a character.
4600
4601 @param charNumber value of the character entered (integer)
4602 """
4603 char = chr(charNumber)
4604 # update code documentation viewer
4605 if char == "(" and \
4606 Preferences.getDocuViewer("ShowInfoOnOpenParenthesis"):
4607 self.vm.showEditorInfo(self)
4608
4609 if self.isListActive():
4610 if self.__isStartChar(char):
4611 self.cancelList()
4612 self.autoComplete(auto=True, context=True)
4613 return
4614 elif char == '(':
4615 self.cancelList()
4616 else:
4617 self.__acTimer.stop()
4618
4619 if self.callTipsStyle() != QsciScintilla.CallTipsNone and \
4620 self.lexer_ is not None and chr(charNumber) in '()':
4621 self.callTip()
4622
4623 if not self.isCallTipActive():
4624 char = chr(charNumber)
4625 if self.__isStartChar(char):
4626 self.autoComplete(auto=True, context=True)
4627 return
4628
4629 line, col = self.getCursorPosition()
4630 txt = self.getWordLeft(line, col)
4631 if len(txt) >= Preferences.getEditor("AutoCompletionThreshold"):
4632 self.autoComplete(auto=True, context=False)
4633 return
4634
4635 def __isStartChar(self, ch):
4636 """
4637 Private method to check, if a character is an autocompletion start
4638 character.
4639
4640 @param ch character to be checked (one character string)
4641 @return flag indicating the result (boolean)
4642 """
4643 if self.lexer_ is None:
4644 return False
4645
4646 wseps = self.lexer_.autoCompletionWordSeparators()
4647 for wsep in wseps:
4648 if wsep.endswith(ch):
4649 return True
4650
4651 return False
4652
4653 def __autocompletionCancelled(self):
4654 """
4655 Private slot to handle the cancellation of an auto-completion list.
4656 """
4657 self.__acWatchdog.stop()
4658
4659 self.__acText = ""
4660
4661 #################################################################
4662 ## auto-completion hook interfaces
4663 #################################################################
4664
4665 def addCompletionListHook(self, key, func, asynchroneous=False):
4666 """
4667 Public method to set an auto-completion list provider.
4668
4669 @param key name of the provider
4670 @type str
4671 @param func function providing completion list. func
4672 should be a function taking a reference to the editor and
4673 a boolean indicating to complete a context. It should return
4674 the possible completions as a list of strings.
4675 @type function(editor, bool) -> list of str in case async is False
4676 and function(editor, bool, str) returning nothing in case async
4677 is True
4678 @param asynchroneous flag indicating an asynchroneous function
4679 @type bool
4680 """
4681 if key in self.__completionListHookFunctions or \
4682 key in self.__completionListAsyncHookFunctions:
4683 # it was already registered
4684 E5MessageBox.warning(
4685 self,
4686 self.tr("Auto-Completion Provider"),
4687 self.tr("""The completion list provider '{0}' was already"""
4688 """ registered. Ignoring duplicate request.""")
4689 .format(key))
4690 return
4691
4692 if asynchroneous:
4693 self.__completionListAsyncHookFunctions[key] = func
4694 else:
4695 self.__completionListHookFunctions[key] = func
4696
4697 def removeCompletionListHook(self, key):
4698 """
4699 Public method to remove a previously registered completion list
4700 provider.
4701
4702 @param key name of the provider
4703 @type str
4704 """
4705 if key in self.__completionListHookFunctions:
4706 del self.__completionListHookFunctions[key]
4707 elif key in self.__completionListAsyncHookFunctions:
4708 del self.__completionListAsyncHookFunctions[key]
4709
4710 def getCompletionListHook(self, key):
4711 """
4712 Public method to get the registered completion list provider.
4713
4714 @param key name of the provider
4715 @type str
4716 @return function providing completion list
4717 @rtype function or None
4718 """
4719 return (self.__completionListHookFunctions.get(key) or
4720 self.__completionListAsyncHookFunctions.get(key))
4721
4722 def autoComplete(self, auto=False, context=True):
4723 """
4724 Public method to start auto-completion.
4725
4726 @keyparam auto flag indicating a call from the __charAdded method
4727 (boolean)
4728 @keyparam context flag indicating to complete a context (boolean)
4729 """
4730 if auto and not Preferences.getEditor("AutoCompletionEnabled"):
4731 # auto-completion is disabled
4732 return
4733
4734 if self.isListActive():
4735 self.cancelList()
4736
4737 if self.__completionListHookFunctions or \
4738 self.__completionListAsyncHookFunctions:
4739 # Avoid delayed auto-completion after cursor repositioning
4740 self.__acText = self.__getAcText()
4741 if auto and Preferences.getEditor("AutoCompletionTimeout"):
4742 self.__acTimer.stop()
4743 self.__acContext = context
4744 self.__acTimer.start()
4745 else:
4746 self.__autoComplete(auto, context)
4747 elif not auto:
4748 self.autoCompleteQScintilla()
4749 elif self.autoCompletionSource() != QsciScintilla.AcsNone:
4750 self.autoCompleteQScintilla()
4751
4752 def __getAcText(self):
4753 """
4754 Private method to get the text from cursor position for autocompleting.
4755
4756 @return text left of cursor position
4757 @rtype str
4758 """
4759 line, col = self.getCursorPosition()
4760 text = self.text(line)
4761 try:
4762 if self.__isStartChar(text[col - 1]):
4763 acText = self.getWordLeft(line, col - 1) + text[col - 1]
4764 else:
4765 acText = self.getWordLeft(line, col)
4766 except IndexError:
4767 acText = ""
4768
4769 return acText
4770
4771 def __autoComplete(self, auto=True, context=None):
4772 """
4773 Private method to start auto-completion via plug-ins.
4774
4775 @keyparam auto flag indicating a call from the __charAdded method
4776 (boolean)
4777 @keyparam context flag indicating to complete a context
4778 @type bool or None
4779 """
4780 self.__acCompletions.clear()
4781 self.__acCompletionsFinished = 0
4782
4783 # Suppress empty completions
4784 if auto and self.__acText == '':
4785 return
4786
4787 if self.__acCacheEnabled:
4788 completions = self.__acCache.get(self.__acText)
4789 else:
4790 completions = None
4791 if completions is not None:
4792 # show list with cached entries
4793 if self.isListActive():
4794 self.cancelList()
4795
4796 self.__showCompletionsList(completions)
4797 else:
4798 if context is None:
4799 context = self.__acContext
4800
4801 for key in self.__completionListAsyncHookFunctions:
4802 self.__completionListAsyncHookFunctions[key](
4803 self, context, self.__acText)
4804
4805 for key in self.__completionListHookFunctions:
4806 completions = self.__completionListHookFunctions[key](
4807 self, context)
4808 self.completionsListReady(completions, self.__acText)
4809
4810 if Preferences.getEditor("AutoCompletionScintillaOnFail"):
4811 self.__acWatchdog.start()
4812
4813 def completionsListReady(self, completions, acText):
4814 """
4815 Public method to show the completions determined by a completions
4816 provider.
4817
4818 @param completions list of possible completions
4819 @type list of str or set of str
4820 @param acText text to be completed
4821 @type str
4822 """
4823 currentWord = self.__getAcText() or ' '
4824 # process the list only, if not already obsolete ...
4825 if acText != self.__acText or not self.__acText.endswith(currentWord):
4826 # Suppress auto-completion done by QScintilla as fallback
4827 self.__acWatchdog.stop()
4828 return
4829
4830 self.__acCompletions.update(set(completions))
4831
4832 self.__acCompletionsFinished += 1
4833 # Got all results from auto completer?
4834 if self.__acCompletionsFinished >= (
4835 len(self.__completionListAsyncHookFunctions) +
4836 len(self.__completionListHookFunctions)
4837 ):
4838 self.__acWatchdog.stop()
4839
4840 # Autocomplete with QScintilla if no results present
4841 if Preferences.getEditor("AutoCompletionScintillaOnFail") and \
4842 not self.__acCompletions:
4843 self.autoCompleteQScintilla()
4844 return
4845
4846 # ... or completions are not empty
4847 if not bool(completions):
4848 return
4849
4850 if self.isListActive():
4851 self.cancelList()
4852
4853 if self.__acCompletions:
4854 if self.__acCacheEnabled:
4855 self.__acCache.add(acText, set(self.__acCompletions))
4856 self.__showCompletionsList(self.__acCompletions)
4857
4858 def __showCompletionsList(self, completions):
4859 """
4860 Private method to show the completions list.
4861
4862 @param completions completions to be shown
4863 @type list of str or set of str
4864 """
4865 if Preferences.getEditor("AutoCompletionReversedList"):
4866 acCompletions = sorted(
4867 list(completions),
4868 key=self.__replaceLeadingUnderscores)
4869 else:
4870 acCompletions = sorted(list(completions))
4871 self.showUserList(EditorAutoCompletionListID, acCompletions)
4872
4873 def __replaceLeadingUnderscores(self, txt):
4874 """
4875 Private method to replace the first two underlines for invers sorting.
4876
4877 @param txt completion text
4878 @type str
4879 @return modified completion text
4880 @rtype str
4881 """
4882 if txt.startswith('_'):
4883 return txt[:2].replace('_', '~') + txt[2:]
4884 else:
4885 return txt
4886
4887 def __clearCompletionsCache(self):
4888 """
4889 Private method to clear the auto-completions cache.
4890 """
4891 self.__acCache.clear()
4892
4893 def __completionListSelected(self, listId, txt):
4894 """
4895 Private slot to handle the selection from the completion list.
4896
4897 @param listId the ID of the user list (should be 1 or 2) (integer)
4898 @param txt the selected text (string)
4899 """
4900 if listId == EditorAutoCompletionListID:
4901 lst = txt.split()
4902 if len(lst) > 1:
4903 txt = lst[0]
4904
4905 self.beginUndoAction()
4906 if Preferences.getEditor("AutoCompletionReplaceWord"):
4907 self.selectCurrentWord()
4908 self.removeSelectedText()
4909 line, col = self.getCursorPosition()
4910 else:
4911 line, col = self.getCursorPosition()
4912 wLeft = self.getWordLeft(line, col)
4913 if not txt.startswith(wLeft):
4914 self.selectCurrentWord()
4915 self.removeSelectedText()
4916 line, col = self.getCursorPosition()
4917 elif wLeft:
4918 txt = txt[len(wLeft):]
4919 self.insert(txt)
4920 self.endUndoAction()
4921 self.setCursorPosition(line, col + len(txt))
4922 elif listId == TemplateCompletionListID:
4923 self.__applyTemplate(txt, self.getLanguage())
4924
4925 def canProvideDynamicAutoCompletion(self):
4926 """
4927 Public method to test the dynamic auto-completion availability.
4928
4929 @return flag indicating the availability of dynamic auto-completion
4930 (boolean)
4931 """
4932 return (self.acAPI or
4933 bool(self.__completionListHookFunctions) or
4934 bool(self.__completionListAsyncHookFunctions))
4935
4936 #################################################################
4937 ## call-tip hook interfaces
4938 #################################################################
4939
4940 def addCallTipHook(self, key, func):
4941 """
4942 Public method to set a calltip provider.
4943
4944 @param key name of the provider
4945 @type str
4946 @param func function providing calltips. func
4947 should be a function taking a reference to the editor,
4948 a position into the text and the amount of commas to the
4949 left of the cursor. It should return the possible
4950 calltips as a list of strings.
4951 @type function(editor, int, int) -> list of str
4952 """
4953 if key in self.__ctHookFunctions:
4954 # it was already registered
4955 E5MessageBox.warning(
4956 self,
4957 self.tr("Call-Tips Provider"),
4958 self.tr("""The call-tips provider '{0}' was already"""
4959 """ registered. Ignoring duplicate request.""")
4960 .format(key))
4961 return
4962
4963 self.__ctHookFunctions[key] = func
4964
4965 def removeCallTipHook(self, key):
4966 """
4967 Public method to remove a previously registered calltip provider.
4968
4969 @param key name of the provider
4970 @type str
4971 """
4972 if key in self.__ctHookFunctions:
4973 del self.__ctHookFunctions[key]
4974
4975 def getCallTipHook(self, key):
4976 """
4977 Public method to get the registered calltip provider.
4978
4979 @param key name of the provider
4980 @type str
4981 @return function providing calltips
4982 @rtype function or None
4983 """
4984 if key in self.__ctHookFunctions:
4985 return self.__ctHookFunctions[key]
4986 else:
4987 return None
4988
4989 def canProvideCallTipps(self):
4990 """
4991 Public method to test the calltips availability.
4992
4993 @return flag indicating the availability of calltips (boolean)
4994 """
4995 return (self.acAPI or
4996 bool(self.__ctHookFunctions))
4997
4998 def callTip(self):
4999 """
5000 Public method to show calltips.
5001 """
5002 if bool(self.__ctHookFunctions):
5003 self.__callTip()
5004 else:
5005 super(Editor, self).callTip()
5006
5007 def __callTip(self):
5008 """
5009 Private method to show call tips provided by a plugin.
5010 """
5011 pos = self.currentPosition()
5012
5013 # move backward to the start of the current calltip working out
5014 # which argument to highlight
5015 commas = 0
5016 found = False
5017 ch, pos = self.__getCharacter(pos)
5018 while ch:
5019 if ch == ',':
5020 commas += 1
5021 elif ch == ')':
5022 depth = 1
5023
5024 # ignore everything back to the start of the corresponding
5025 # parenthesis
5026 ch, pos = self.__getCharacter(pos)
5027 while ch:
5028 if ch == ')':
5029 depth += 1
5030 elif ch == '(':
5031 depth -= 1
5032 if depth == 0:
5033 break
5034 ch, pos = self.__getCharacter(pos)
5035 elif ch == '(':
5036 found = True
5037 break
5038
5039 ch, pos = self.__getCharacter(pos)
5040
5041 self.SendScintilla(QsciScintilla.SCI_CALLTIPCANCEL)
5042
5043 if not found:
5044 return
5045
5046 if self.__ctHookFunctions:
5047 callTips = []
5048 for key in self.__ctHookFunctions:
5049 callTips.extend(self.__ctHookFunctions[key](self, pos, commas))
5050 callTips = list(set(callTips))
5051 callTips.sort()
5052 if len(callTips) == 0:
5053 if Preferences.getEditor("CallTipsScintillaOnFail"):
5054 # try QScintilla calltips
5055 super(Editor, self).callTip()
5056 return
5057
5058 ctshift = 0
5059 for ct in callTips:
5060 shift = ct.index("(")
5061 if ctshift < shift:
5062 ctshift = shift
5063
5064 cv = self.callTipsVisible()
5065 if cv > 0:
5066 # this is just a safe guard
5067 ct = self._encodeString("\n".join(callTips[:cv]))
5068 else:
5069 # until here and unindent below
5070 ct = self._encodeString("\n".join(callTips))
5071
5072 self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW,
5073 self.__adjustedCallTipPosition(ctshift, pos), ct)
5074 if b'\n' in ct:
5075 return
5076
5077 # Highlight the current argument
5078 if commas == 0:
5079 astart = ct.find(b'(')
5080 else:
5081 astart = ct.find(b',')
5082 commas -= 1
5083 while astart != -1 and commas > 0:
5084 astart = ct.find(b',', astart + 1)
5085 commas -= 1
5086
5087 if astart == -1:
5088 return
5089
5090 depth = 0
5091 for aend in range(astart + 1, len(ct)):
5092 ch = ct[aend:aend + 1]
5093
5094 if ch == b',' and depth == 0:
5095 break
5096 elif ch == b'(':
5097 depth += 1
5098 elif ch == b')':
5099 if depth == 0:
5100 break
5101
5102 depth -= 1
5103
5104 if astart != aend:
5105 self.SendScintilla(QsciScintilla.SCI_CALLTIPSETHLT,
5106 astart + 1, aend)
5107
5108 def __adjustedCallTipPosition(self, ctshift, pos):
5109 """
5110 Private method to calculate an adjusted position for showing calltips.
5111
5112 @param ctshift amount the calltip shall be shifted (integer)
5113 @param pos position into the text (integer)
5114 @return new position for the calltip (integer)
5115 """
5116 ct = pos
5117 if ctshift:
5118 ctmin = self.SendScintilla(
5119 QsciScintilla.SCI_POSITIONFROMLINE,
5120 self.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, ct))
5121 if ct - ctshift < ctmin:
5122 ct = ctmin
5123 else:
5124 ct = ct - ctshift
5125 return ct
5126
5127 #################################################################
5128 ## Methods needed by the code documentation viewer
5129 #################################################################
5130
5131 def __showCodeInfo(self):
5132 """
5133 Private slot to handle the context menu action to show code info.
5134 """
5135 self.vm.showEditorInfo(self)
5136
5137 #################################################################
5138 ## Methods needed by the context menu
5139 #################################################################
5140
5141 def __marginNumber(self, xPos):
5142 """
5143 Private method to calculate the margin number based on a x position.
5144
5145 @param xPos x position (integer)
5146 @return margin number (integer, -1 for no margin)
5147 """
5148 width = 0
5149 for margin in range(5):
5150 width += self.marginWidth(margin)
5151 if xPos <= width:
5152 return margin
5153 return -1
5154
5155 def contextMenuEvent(self, evt):
5156 """
5157 Protected method implementing the context menu event.
5158
5159 @param evt the context menu event (QContextMenuEvent)
5160 """
5161 evt.accept()
5162 if self.__marginNumber(evt.x()) == -1:
5163 self.spellingMenuPos = self.positionFromPoint(evt.pos())
5164 if self.spellingMenuPos >= 0 and \
5165 self.spell is not None and \
5166 self.hasIndicator(self.spellingIndicator, self.spellingMenuPos):
5167 self.spellingMenu.popup(evt.globalPos())
5168 else:
5169 self.menu.popup(evt.globalPos())
5170 else:
5171 self.line = self.lineAt(evt.pos())
5172 if self.__marginNumber(evt.x()) in [self.__bmMargin,
5173 self.__linenoMargin]:
5174 self.bmMarginMenu.popup(evt.globalPos())
5175 elif self.__marginNumber(evt.x()) == self.__bpMargin:
5176 self.bpMarginMenu.popup(evt.globalPos())
5177 elif self.__marginNumber(evt.x()) == self.__indicMargin:
5178 self.indicMarginMenu.popup(evt.globalPos())
5179 elif self.__marginNumber(evt.x()) == self.__foldMargin:
5180 self.foldMarginMenu.popup(evt.globalPos())
5181
5182 def __showContextMenu(self):
5183 """
5184 Private slot handling the aboutToShow signal of the context menu.
5185 """
5186 self.menuActs["Reopen"].setEnabled(
5187 not self.isModified() and bool(self.fileName))
5188 self.menuActs["Save"].setEnabled(self.isModified())
5189 self.menuActs["Undo"].setEnabled(self.isUndoAvailable())
5190 self.menuActs["Redo"].setEnabled(self.isRedoAvailable())
5191 self.menuActs["Revert"].setEnabled(self.isModified())
5192 self.menuActs["Cut"].setEnabled(self.hasSelectedText())
5193 self.menuActs["Copy"].setEnabled(self.hasSelectedText())
5194 if self.menuActs["ExecuteSelection"] is not None:
5195 self.menuActs["ExecuteSelection"].setEnabled(
5196 self.hasSelectedText())
5197 self.menuActs["Paste"].setEnabled(self.canPaste())
5198 if not self.isResourcesFile:
5199 if self.fileName and self.isPyFile():
5200 self.menuActs["Show"].setEnabled(True)
5201 else:
5202 self.menuActs["Show"].setEnabled(False)
5203 if self.fileName and \
5204 (self.isPyFile() or self.isRubyFile()):
5205 self.menuActs["Diagrams"].setEnabled(True)
5206 else:
5207 self.menuActs["Diagrams"].setEnabled(False)
5208 if not self.miniMenu:
5209 if self.lexer_ is not None:
5210 self.menuActs["Comment"].setEnabled(
5211 self.lexer_.canBlockComment())
5212 self.menuActs["Uncomment"].setEnabled(
5213 self.lexer_.canBlockComment())
5214 self.menuActs["StreamComment"].setEnabled(
5215 self.lexer_.canStreamComment())
5216 self.menuActs["BoxComment"].setEnabled(
5217 self.lexer_.canBoxComment())
5218 else:
5219 self.menuActs["Comment"].setEnabled(False)
5220 self.menuActs["Uncomment"].setEnabled(False)
5221 self.menuActs["StreamComment"].setEnabled(False)
5222 self.menuActs["BoxComment"].setEnabled(False)
5223
5224 self.menuActs["TypingAidsEnabled"].setEnabled(
5225 self.completer is not None)
5226 self.menuActs["TypingAidsEnabled"].setChecked(
5227 self.completer is not None and self.completer.isEnabled())
5228
5229 if not self.isResourcesFile:
5230 self.menuActs["calltip"].setEnabled(self.canProvideCallTipps())
5231 self.menuActs["codeInfo"].setEnabled(
5232 self.vm.isEditorInfoSupported(self.getLanguage()))
5233
5234 from .SpellChecker import SpellChecker
5235 spellingAvailable = SpellChecker.isAvailable()
5236 self.menuActs["SpellCheck"].setEnabled(spellingAvailable)
5237 self.menuActs["SpellCheckSelection"].setEnabled(
5238 spellingAvailable and self.hasSelectedText())
5239 self.menuActs["SpellCheckRemove"].setEnabled(
5240 spellingAvailable and self.spellingMenuPos >= 0)
5241
5242 if self.menuActs["OpenRejections"]:
5243 if self.fileName:
5244 rej = "{0}.rej".format(self.fileName)
5245 self.menuActs["OpenRejections"].setEnabled(os.path.exists(rej))
5246 else:
5247 self.menuActs["OpenRejections"].setEnabled(False)
5248
5249 self.menuActs["MonospacedFont"].setEnabled(self.lexer_ is None)
5250
5251 splitOrientation = self.vm.getSplitOrientation()
5252 if splitOrientation == Qt.Horizontal:
5253 self.menuActs["NewSplit"].setIcon(
5254 UI.PixmapCache.getIcon("splitHorizontal.png"))
5255 else:
5256 self.menuActs["NewSplit"].setIcon(
5257 UI.PixmapCache.getIcon("splitVertical.png"))
5258
5259 self.menuActs["Tools"].setEnabled(not self.toolsMenu.isEmpty())
5260
5261 self.showMenu.emit("Main", self.menu, self)
5262
5263 def __showContextMenuAutocompletion(self):
5264 """
5265 Private slot called before the autocompletion menu is shown.
5266 """
5267 self.menuActs["acDynamic"].setEnabled(
5268 self.canProvideDynamicAutoCompletion())
5269 self.menuActs["acClearCache"].setEnabled(
5270 self.canProvideDynamicAutoCompletion())
5271 self.menuActs["acAPI"].setEnabled(self.acAPI)
5272 self.menuActs["acAPIDocument"].setEnabled(self.acAPI)
5273
5274 self.showMenu.emit("Autocompletion", self.autocompletionMenu, self)
5275
5276 def __showContextMenuShow(self):
5277 """
5278 Private slot called before the show menu is shown.
5279 """
5280 prEnable = False
5281 coEnable = False
5282
5283 # first check if the file belongs to a project
5284 if self.project.isOpen() and \
5285 self.project.isProjectSource(self.fileName):
5286 fn = self.project.getMainScript(True)
5287 if fn is not None:
5288 tfn = Utilities.getTestFileName(fn)
5289 basename = os.path.splitext(fn)[0]
5290 tbasename = os.path.splitext(tfn)[0]
5291 prEnable = prEnable or \
5292 os.path.isfile("{0}.profile".format(basename)) or \
5293 os.path.isfile("{0}.profile".format(tbasename))
5294 coEnable = (
5295 coEnable or
5296 os.path.isfile("{0}.coverage".format(basename)) or
5297 os.path.isfile("{0}.coverage".format(tbasename))) and \
5298 (self.project.isPy3Project() or
5299 self.project.isPy2Project())
5300
5301 # now check ourselves
5302 fn = self.getFileName()
5303 if fn is not None:
5304 tfn = Utilities.getTestFileName(fn)
5305 basename = os.path.splitext(fn)[0]
5306 tbasename = os.path.splitext(tfn)[0]
5307 prEnable = prEnable or \
5308 os.path.isfile("{0}.profile".format(basename)) or \
5309 os.path.isfile("{0}.profile".format(tbasename))
5310 coEnable = (
5311 coEnable or
5312 os.path.isfile("{0}.coverage".format(basename)) or
5313 os.path.isfile("{0}.coverage".format(tbasename))) and \
5314 self.isPyFile()
5315
5316 # now check for syntax errors
5317 if self.hasSyntaxErrors():
5318 coEnable = False
5319
5320 self.profileMenuAct.setEnabled(prEnable)
5321 self.coverageMenuAct.setEnabled(coEnable)
5322 self.coverageShowAnnotationMenuAct.setEnabled(
5323 coEnable and len(self.notcoveredMarkers) == 0)
5324 self.coverageHideAnnotationMenuAct.setEnabled(
5325 len(self.notcoveredMarkers) > 0)
5326
5327 self.showMenu.emit("Show", self.menuShow, self)
5328
5329 def __showContextMenuGraphics(self):
5330 """
5331 Private slot handling the aboutToShow signal of the diagrams context
5332 menu.
5333 """
5334 if self.project.isOpen() and \
5335 self.project.isProjectSource(self.fileName):
5336 self.applicationDiagramMenuAct.setEnabled(True)
5337 else:
5338 self.applicationDiagramMenuAct.setEnabled(False)
5339
5340 self.showMenu.emit("Graphics", self.graphicsMenu, self)
5341
5342 def __showContextMenuMargin(self, menu):
5343 """
5344 Private slot handling the aboutToShow signal of the margins context
5345 menu.
5346
5347 @param menu reference to the menu to be shown
5348 @type QMenu
5349 """
5350 if menu is self.bpMarginMenu:
5351 supportsDebugger = bool(self.fileName and self.isPyFile())
5352 hasBreakpoints = bool(self.breaks)
5353 hasBreakpoint = bool(
5354 self.markersAtLine(self.line) & self.breakpointMask)
5355
5356 self.marginMenuActs["Breakpoint"].setEnabled(supportsDebugger)
5357 self.marginMenuActs["TempBreakpoint"].setEnabled(supportsDebugger)
5358 self.marginMenuActs["NextBreakpoint"].setEnabled(
5359 supportsDebugger and hasBreakpoints)
5360 self.marginMenuActs["PreviousBreakpoint"].setEnabled(
5361 supportsDebugger and hasBreakpoints)
5362 self.marginMenuActs["ClearBreakpoint"].setEnabled(
5363 supportsDebugger and hasBreakpoints)
5364 self.marginMenuActs["EditBreakpoint"].setEnabled(
5365 supportsDebugger and hasBreakpoint)
5366 self.marginMenuActs["EnableBreakpoint"].setEnabled(
5367 supportsDebugger and hasBreakpoint)
5368 if supportsDebugger:
5369 if self.markersAtLine(self.line) & (1 << self.dbreakpoint):
5370 self.marginMenuActs["EnableBreakpoint"].setText(
5371 self.tr('Enable breakpoint'))
5372 else:
5373 self.marginMenuActs["EnableBreakpoint"].setText(
5374 self.tr('Disable breakpoint'))
5375
5376 if menu is self.bmMarginMenu:
5377 hasBookmarks = bool(self.bookmarks)
5378
5379 self.marginMenuActs["NextBookmark"].setEnabled(hasBookmarks)
5380 self.marginMenuActs["PreviousBookmark"].setEnabled(hasBookmarks)
5381 self.marginMenuActs["ClearBookmark"].setEnabled(hasBookmarks)
5382
5383 if menu is self.foldMarginMenu:
5384 isFoldHeader = bool(self.SendScintilla(
5385 QsciScintilla.SCI_GETFOLDLEVEL, self.line) &
5386 QsciScintilla.SC_FOLDLEVELHEADERFLAG)
5387
5388 self.marginMenuActs["ExpandChildren"].setEnabled(isFoldHeader)
5389 self.marginMenuActs["CollapseChildren"].setEnabled(isFoldHeader)
5390
5391 if menu is self.indicMarginMenu:
5392 hasSyntaxErrors = bool(self.syntaxerrors)
5393 hasWarnings = bool(self.warnings)
5394 hasNotCoveredMarkers = bool(self.notcoveredMarkers)
5395
5396 self.marginMenuActs["GotoSyntaxError"].setEnabled(hasSyntaxErrors)
5397 self.marginMenuActs["ClearSyntaxError"].setEnabled(hasSyntaxErrors)
5398 if hasSyntaxErrors and \
5399 self.markersAtLine(self.line) & (1 << self.syntaxerror):
5400 self.marginMenuActs["ShowSyntaxError"].setEnabled(True)
5401 else:
5402 self.marginMenuActs["ShowSyntaxError"].setEnabled(False)
5403
5404 self.marginMenuActs["NextWarningMarker"].setEnabled(hasWarnings)
5405 self.marginMenuActs["PreviousWarningMarker"].setEnabled(
5406 hasWarnings)
5407 self.marginMenuActs["ClearWarnings"].setEnabled(hasWarnings)
5408 if hasWarnings and \
5409 self.markersAtLine(self.line) & (1 << self.warning):
5410 self.marginMenuActs["ShowWarning"].setEnabled(True)
5411 else:
5412 self.marginMenuActs["ShowWarning"].setEnabled(False)
5413
5414 self.marginMenuActs["NextCoverageMarker"].setEnabled(
5415 hasNotCoveredMarkers)
5416 self.marginMenuActs["PreviousCoverageMarker"].setEnabled(
5417 hasNotCoveredMarkers)
5418
5419 self.marginMenuActs["PreviousTaskMarker"].setEnabled(
5420 self.__hasTaskMarkers)
5421 self.marginMenuActs["NextTaskMarker"].setEnabled(
5422 self.__hasTaskMarkers)
5423
5424 self.marginMenuActs["PreviousChangeMarker"].setEnabled(
5425 self.__hasChangeMarkers)
5426 self.marginMenuActs["NextChangeMarker"].setEnabled(
5427 self.__hasChangeMarkers)
5428 self.marginMenuActs["ClearChangeMarkers"].setEnabled(
5429 self.__hasChangeMarkers)
5430
5431 self.showMenu.emit("Margin", menu, self)
5432
5433 def __showContextMenuChecks(self):
5434 """
5435 Private slot handling the aboutToShow signal of the checks context
5436 menu.
5437 """
5438 self.showMenu.emit("Checks", self.checksMenu, self)
5439
5440 def __showContextMenuTools(self):
5441 """
5442 Private slot handling the aboutToShow signal of the tools context
5443 menu.
5444 """
5445 self.showMenu.emit("Tools", self.toolsMenu, self)
5446
5447 def __reopenWithEncodingMenuTriggered(self, act):
5448 """
5449 Private method to handle the rereading of the file with a selected
5450 encoding.
5451
5452 @param act reference to the action that was triggered (QAction)
5453 """
5454 encoding = act.data()
5455 self.readFile(self.fileName, encoding=encoding)
5456 self.__checkEncoding()
5457
5458 def __contextSave(self):
5459 """
5460 Private slot handling the save context menu entry.
5461 """
5462 ok = self.saveFile()
5463 if ok:
5464 self.vm.setEditorName(self, self.fileName)
5465
5466 def __contextSaveAs(self):
5467 """
5468 Private slot handling the save as context menu entry.
5469 """
5470 ok = self.saveFileAs()
5471 if ok:
5472 self.vm.setEditorName(self, self.fileName)
5473
5474 def __contextSaveCopy(self):
5475 """
5476 Private slot handling the save copy context menu entry.
5477 """
5478 self.saveFileCopy()
5479
5480 def __contextClose(self):
5481 """
5482 Private slot handling the close context menu entry.
5483 """
5484 self.vm.closeEditor(self)
5485
5486 def __contextOpenRejections(self):
5487 """
5488 Private slot handling the open rejections file context menu entry.
5489 """
5490 if self.fileName:
5491 rej = "{0}.rej".format(self.fileName)
5492 if os.path.exists(rej):
5493 self.vm.openSourceFile(rej)
5494
5495 def __newView(self):
5496 """
5497 Private slot to create a new view to an open document.
5498 """
5499 self.vm.newEditorView(self.fileName, self, self.filetype)
5500
5501 def __newViewNewSplit(self):
5502 """
5503 Private slot to create a new view to an open document.
5504 """
5505 self.vm.addSplit()
5506 self.vm.newEditorView(self.fileName, self, self.filetype)
5507
5508 def __selectAll(self):
5509 """
5510 Private slot handling the select all context menu action.
5511 """
5512 self.selectAll(True)
5513
5514 def __deselectAll(self):
5515 """
5516 Private slot handling the deselect all context menu action.
5517 """
5518 self.selectAll(False)
5519
5520 def joinLines(self):
5521 """
5522 Public slot to join the current line with the next one.
5523 """
5524 curLine = self.getCursorPosition()[0]
5525 if curLine == self.lines() - 1:
5526 return
5527
5528 line0Text = self.text(curLine)
5529 line1Text = self.text(curLine + 1)
5530 if line1Text in ["", "\r", "\n", "\r\n"]:
5531 return
5532
5533 if line0Text.rstrip("\r\n\\ \t").endswith(("'", '"')) and \
5534 line1Text.lstrip().startswith(("'", '"')):
5535 # merging multi line strings
5536 startChars = "\r\n\\ \t'\""
5537 endChars = " \t'\""
5538 else:
5539 startChars = "\r\n\\ \t"
5540 endChars = " \t"
5541
5542 # determine start index
5543 startIndex = len(line0Text)
5544 while startIndex > 0 and line0Text[startIndex - 1] in startChars:
5545 startIndex -= 1
5546 if startIndex == 0:
5547 return
5548
5549 # determine end index
5550 endIndex = 0
5551 while line1Text[endIndex] in endChars:
5552 endIndex += 1
5553
5554 self.setSelection(curLine, startIndex, curLine + 1, endIndex)
5555 self.beginUndoAction()
5556 self.removeSelectedText()
5557 self.insertAt(" ", curLine, startIndex)
5558 self.endUndoAction()
5559
5560 def shortenEmptyLines(self):
5561 """
5562 Public slot to compress lines consisting solely of whitespace
5563 characters.
5564 """
5565 searchRE = r"^[ \t]+$"
5566
5567 ok = self.findFirstTarget(searchRE, True, False, False, 0, 0)
5568 self.beginUndoAction()
5569 while ok:
5570 self.replaceTarget("")
5571 ok = self.findNextTarget()
5572 self.endUndoAction()
5573
5574 def __autosaveEnable(self):
5575 """
5576 Private slot handling the autosave enable context menu action.
5577 """
5578 if self.menuActs["AutosaveEnable"].isChecked():
5579 self.autosaveManuallyDisabled = False
5580 else:
5581 self.autosaveManuallyDisabled = True
5582
5583 def shouldAutosave(self):
5584 """
5585 Public slot to check the autosave flags.
5586
5587 @return flag indicating this editor should be saved (boolean)
5588 """
5589 return bool(self.fileName) and \
5590 not self.autosaveManuallyDisabled and \
5591 not self.isReadOnly()
5592
5593 def checkSyntax(self):
5594 """
5595 Public method to perform an automatic syntax check of the file.
5596 """
5597 if self.syntaxCheckService is None or \
5598 self.filetype not in self.syntaxCheckService.getLanguages():
5599 return
5600
5601 if Preferences.getEditor("AutoCheckSyntax"):
5602 if Preferences.getEditor("OnlineSyntaxCheck"):
5603 self.__onlineSyntaxCheckTimer.stop()
5604
5605 self.syntaxCheckService.syntaxCheck(
5606 self.filetype, self.fileName or "(Unnamed)", self.text())
5607
5608 def __processSyntaxCheckError(self, fn, msg):
5609 """
5610 Private slot to report an error message of a syntax check.
5611
5612 @param fn filename of the file
5613 @type str
5614 @param msg error message
5615 @type str
5616 """
5617 if fn != self.fileName and (
5618 bool(self.fileName) or fn != "(Unnamed)"):
5619 return
5620
5621 self.clearSyntaxError()
5622 self.clearFlakesWarnings()
5623
5624 self.toggleWarning(0, 0, True, msg)
5625
5626 self.updateVerticalScrollBar()
5627
5628 def __processSyntaxCheckResult(self, fn, problems):
5629 """
5630 Private slot to report the resulting messages of a syntax check.
5631
5632 @param fn filename of the checked file (str)
5633 @param problems dictionary with the keys 'error' and 'warnings' which
5634 hold a list containing details about the error/ warnings
5635 (file name, line number, column, codestring (only at syntax
5636 errors), the message) (dict)
5637 """
5638 # Check if it's the requested file, otherwise ignore signal
5639 if fn != self.fileName and (
5640 bool(self.fileName) or fn != "(Unnamed)"):
5641 return
5642
5643 self.clearSyntaxError()
5644 self.clearFlakesWarnings()
5645
5646 error = problems.get('error')
5647 if error:
5648 _fn, lineno, col, code, msg = error
5649 self.toggleSyntaxError(lineno, col, True, msg)
5650
5651 warnings = problems.get('warnings', [])
5652 for _fn, lineno, col, _code, msg in warnings:
5653 self.toggleWarning(lineno, col, True, msg)
5654
5655 self.updateVerticalScrollBar()
5656
5657 def __initOnlineSyntaxCheck(self):
5658 """
5659 Private slot to initialize the online syntax check.
5660 """
5661 self.__onlineSyntaxCheckTimer = QTimer(self)
5662 self.__onlineSyntaxCheckTimer.setSingleShot(True)
5663 self.__onlineSyntaxCheckTimer.setInterval(
5664 Preferences.getEditor("OnlineSyntaxCheckInterval") * 1000)
5665 self.__onlineSyntaxCheckTimer.timeout.connect(self.checkSyntax)
5666 self.textChanged.connect(self.__resetOnlineSyntaxCheckTimer)
5667
5668 def __resetOnlineSyntaxCheckTimer(self):
5669 """
5670 Private method to reset the online syntax check timer.
5671 """
5672 if Preferences.getEditor("OnlineSyntaxCheck"):
5673 self.__onlineSyntaxCheckTimer.stop()
5674 self.__onlineSyntaxCheckTimer.start()
5675
5676 def __showCodeMetrics(self):
5677 """
5678 Private method to handle the code metrics context menu action.
5679 """
5680 if not self.checkDirty():
5681 return
5682
5683 from DataViews.CodeMetricsDialog import CodeMetricsDialog
5684 self.codemetrics = CodeMetricsDialog()
5685 self.codemetrics.show()
5686 self.codemetrics.start(self.fileName)
5687
5688 def __getCodeCoverageFile(self):
5689 """
5690 Private method to get the file name of the file containing coverage
5691 info.
5692
5693 @return file name of the coverage file (string)
5694 """
5695 files = []
5696
5697 # first check if the file belongs to a project and there is
5698 # a project coverage file
5699 if self.project.isOpen() and \
5700 self.project.isProjectSource(self.fileName):
5701 fn = self.project.getMainScript(True)
5702 if fn is not None:
5703 tfn = Utilities.getTestFileName(fn)
5704 basename = os.path.splitext(fn)[0]
5705 tbasename = os.path.splitext(tfn)[0]
5706
5707 f = "{0}.coverage".format(basename)
5708 tf = "{0}.coverage".format(tbasename)
5709 if os.path.isfile(f):
5710 files.append(f)
5711 if os.path.isfile(tf):
5712 files.append(tf)
5713
5714 # now check, if there are coverage files belonging to ourselves
5715 fn = self.getFileName()
5716 if fn is not None:
5717 tfn = Utilities.getTestFileName(fn)
5718 basename = os.path.splitext(fn)[0]
5719 tbasename = os.path.splitext(tfn)[0]
5720
5721 f = "{0}.coverage".format(basename)
5722 tf = "{0}.coverage".format(tbasename)
5723 if os.path.isfile(f) and f not in files:
5724 files.append(f)
5725 if os.path.isfile(tf) and tf not in files:
5726 files.append(tf)
5727
5728 if files:
5729 if len(files) > 1:
5730 fn, ok = QInputDialog.getItem(
5731 self,
5732 self.tr("Code Coverage"),
5733 self.tr("Please select a coverage file"),
5734 files,
5735 0, False)
5736 if not ok:
5737 return ""
5738 else:
5739 fn = files[0]
5740 else:
5741 fn = None
5742
5743 return fn
5744
5745 def __showCodeCoverage(self):
5746 """
5747 Private method to handle the code coverage context menu action.
5748 """
5749 fn = self.__getCodeCoverageFile()
5750 if fn:
5751 from DataViews.PyCoverageDialog import PyCoverageDialog
5752 self.codecoverage = PyCoverageDialog()
5753 self.codecoverage.show()
5754 self.codecoverage.start(fn, self.fileName)
5755
5756 def refreshCoverageAnnotations(self):
5757 """
5758 Public method to refresh the code coverage annotations.
5759 """
5760 if self.showingNotcoveredMarkers:
5761 self.codeCoverageShowAnnotations(silent=True)
5762
5763 def codeCoverageShowAnnotations(self, silent=False):
5764 """
5765 Public method to handle the show code coverage annotations context
5766 menu action.
5767
5768 @param silent flag indicating to not show any dialog (boolean)
5769 """
5770 self.__codeCoverageHideAnnotations()
5771
5772 fn = self.__getCodeCoverageFile()
5773 if fn:
5774 from coverage import Coverage
5775 cover = Coverage(data_file=fn)
5776 cover.load()
5777 missing = cover.analysis2(self.fileName)[3]
5778 if missing:
5779 for line in missing:
5780 handle = self.markerAdd(line - 1, self.notcovered)
5781 self.notcoveredMarkers.append(handle)
5782 self.coverageMarkersShown.emit(True)
5783 self.__markerMap.update()
5784 else:
5785 if not silent:
5786 E5MessageBox.information(
5787 self,
5788 self.tr("Show Code Coverage Annotations"),
5789 self.tr("""All lines have been covered."""))
5790 self.showingNotcoveredMarkers = True
5791 else:
5792 if not silent:
5793 E5MessageBox.warning(
5794 self,
5795 self.tr("Show Code Coverage Annotations"),
5796 self.tr("""There is no coverage file available."""))
5797
5798 def __codeCoverageHideAnnotations(self):
5799 """
5800 Private method to handle the hide code coverage annotations context
5801 menu action.
5802 """
5803 for handle in self.notcoveredMarkers:
5804 self.markerDeleteHandle(handle)
5805 self.notcoveredMarkers = []
5806 self.coverageMarkersShown.emit(False)
5807 self.showingNotcoveredMarkers = False
5808 self.__markerMap.update()
5809
5810 def getCoverageLines(self):
5811 """
5812 Public method to get the lines containing a coverage marker.
5813
5814 @return list of lines containing a coverage marker (list of integer)
5815 """
5816 lines = []
5817 line = -1
5818 while True:
5819 line = self.markerFindNext(line + 1, 1 << self.notcovered)
5820 if line < 0:
5821 break
5822 else:
5823 lines.append(line)
5824 return lines
5825
5826 def hasCoverageMarkers(self):
5827 """
5828 Public method to test, if there are coverage markers.
5829
5830 @return flag indicating the presence of coverage markers (boolean)
5831 """
5832 return len(self.notcoveredMarkers) > 0
5833
5834 def nextUncovered(self):
5835 """
5836 Public slot to handle the 'Next uncovered' context menu action.
5837 """
5838 line, index = self.getCursorPosition()
5839 if line == self.lines() - 1:
5840 line = 0
5841 else:
5842 line += 1
5843 ucline = self.markerFindNext(line, 1 << self.notcovered)
5844 if ucline < 0:
5845 # wrap around
5846 ucline = self.markerFindNext(0, 1 << self.notcovered)
5847 if ucline >= 0:
5848 self.setCursorPosition(ucline, 0)
5849 self.ensureLineVisible(ucline)
5850
5851 def previousUncovered(self):
5852 """
5853 Public slot to handle the 'Previous uncovered' context menu action.
5854 """
5855 line, index = self.getCursorPosition()
5856 if line == 0:
5857 line = self.lines() - 1
5858 else:
5859 line -= 1
5860 ucline = self.markerFindPrevious(line, 1 << self.notcovered)
5861 if ucline < 0:
5862 # wrap around
5863 ucline = self.markerFindPrevious(
5864 self.lines() - 1, 1 << self.notcovered)
5865 if ucline >= 0:
5866 self.setCursorPosition(ucline, 0)
5867 self.ensureLineVisible(ucline)
5868
5869 def __showProfileData(self):
5870 """
5871 Private method to handle the show profile data context menu action.
5872 """
5873 files = []
5874
5875 # first check if the file belongs to a project and there is
5876 # a project profile file
5877 if self.project.isOpen() and \
5878 self.project.isProjectSource(self.fileName):
5879 fn = self.project.getMainScript(True)
5880 if fn is not None:
5881 tfn = Utilities.getTestFileName(fn)
5882 basename = os.path.splitext(fn)[0]
5883 tbasename = os.path.splitext(tfn)[0]
5884
5885 f = "{0}.profile".format(basename)
5886 tf = "{0}.profile".format(tbasename)
5887 if os.path.isfile(f):
5888 files.append(f)
5889 if os.path.isfile(tf):
5890 files.append(tf)
5891
5892 # now check, if there are profile files belonging to ourselves
5893 fn = self.getFileName()
5894 if fn is not None:
5895 tfn = Utilities.getTestFileName(fn)
5896 basename = os.path.splitext(fn)[0]
5897 tbasename = os.path.splitext(tfn)[0]
5898
5899 f = "{0}.profile".format(basename)
5900 tf = "{0}.profile".format(tbasename)
5901 if os.path.isfile(f) and f not in files:
5902 files.append(f)
5903 if os.path.isfile(tf) and tf not in files:
5904 files.append(tf)
5905
5906 if files:
5907 if len(files) > 1:
5908 fn, ok = QInputDialog.getItem(
5909 self,
5910 self.tr("Profile Data"),
5911 self.tr("Please select a profile file"),
5912 files,
5913 0, False)
5914 if not ok:
5915 return
5916 else:
5917 fn = files[0]
5918 else:
5919 return
5920
5921 from DataViews.PyProfileDialog import PyProfileDialog
5922 self.profiledata = PyProfileDialog()
5923 self.profiledata.show()
5924 self.profiledata.start(fn, self.fileName)
5925
5926 def __lmBbookmarks(self):
5927 """
5928 Private method to handle the 'LMB toggles bookmark' context menu
5929 action.
5930 """
5931 self.marginMenuActs["LMBbookmarks"].setChecked(True)
5932 self.marginMenuActs["LMBbreakpoints"].setChecked(False)
5933
5934 def __lmBbreakpoints(self):
5935 """
5936 Private method to handle the 'LMB toggles breakpoint' context menu
5937 action.
5938 """
5939 self.marginMenuActs["LMBbookmarks"].setChecked(True)
5940 self.marginMenuActs["LMBbreakpoints"].setChecked(False)
5941
5942 ###########################################################################
5943 ## Syntax error handling methods below
5944 ###########################################################################
5945
5946 def toggleSyntaxError(self, line, index, error, msg="", show=False):
5947 """
5948 Public method to toggle a syntax error indicator.
5949
5950 @param line line number of the syntax error (integer)
5951 @param index index number of the syntax error (integer)
5952 @param error flag indicating if the error marker should be
5953 set or deleted (boolean)
5954 @param msg error message (string)
5955 @keyparam show flag indicating to set the cursor to the error position
5956 (boolean)
5957 """
5958 if line == 0:
5959 line = 1
5960 # hack to show a syntax error marker, if line is reported to be 0
5961 if error:
5962 # set a new syntax error marker
5963 markers = self.markersAtLine(line - 1)
5964 index += self.indentation(line - 1)
5965 if not (markers & (1 << self.syntaxerror)):
5966 handle = self.markerAdd(line - 1, self.syntaxerror)
5967 self.syntaxerrors[handle] = [(msg, index)]
5968 self.syntaxerrorToggled.emit(self)
5969 else:
5970 for handle in list(self.syntaxerrors.keys()):
5971 if self.markerLine(handle) == line - 1 and \
5972 (msg, index) not in self.syntaxerrors[handle]:
5973 self.syntaxerrors[handle].append((msg, index))
5974 if show:
5975 self.setCursorPosition(line - 1, index)
5976 self.ensureLineVisible(line - 1)
5977 else:
5978 for handle in list(self.syntaxerrors.keys()):
5979 if self.markerLine(handle) == line - 1:
5980 del self.syntaxerrors[handle]
5981 self.markerDeleteHandle(handle)
5982 self.syntaxerrorToggled.emit(self)
5983
5984 self.__setAnnotation(line - 1)
5985 self.__markerMap.update()
5986
5987 def getSyntaxErrors(self):
5988 """
5989 Public method to retrieve the syntax error markers.
5990
5991 @return sorted list of all lines containing a syntax error
5992 (list of integer)
5993 """
5994 selist = []
5995 for handle in list(self.syntaxerrors.keys()):
5996 selist.append(self.markerLine(handle) + 1)
5997
5998 selist.sort()
5999 return selist
6000
6001 def getSyntaxErrorLines(self):
6002 """
6003 Public method to get the lines containing a syntax error.
6004
6005 @return list of lines containing a syntax error (list of integer)
6006 """
6007 lines = []
6008 line = -1
6009 while True:
6010 line = self.markerFindNext(line + 1, 1 << self.syntaxerror)
6011 if line < 0:
6012 break
6013 else:
6014 lines.append(line)
6015 return lines
6016
6017 def hasSyntaxErrors(self):
6018 """
6019 Public method to check for the presence of syntax errors.
6020
6021 @return flag indicating the presence of syntax errors (boolean)
6022 """
6023 return len(self.syntaxerrors) > 0
6024
6025 def gotoSyntaxError(self):
6026 """
6027 Public slot to handle the 'Goto syntax error' context menu action.
6028 """
6029 seline = self.markerFindNext(0, 1 << self.syntaxerror)
6030 if seline >= 0:
6031 index = 0
6032 for handle in self.syntaxerrors.keys():
6033 if self.markerLine(handle) == seline:
6034 index = self.syntaxerrors[handle][0][1]
6035 self.setCursorPosition(seline, index)
6036 self.ensureLineVisible(seline)
6037
6038 def clearSyntaxError(self):
6039 """
6040 Public slot to handle the 'Clear all syntax error' context menu action.
6041 """
6042 for handle in list(self.syntaxerrors.keys()):
6043 line = self.markerLine(handle) + 1
6044 self.toggleSyntaxError(line, 0, False)
6045
6046 self.syntaxerrors = {}
6047 self.syntaxerrorToggled.emit(self)
6048
6049 def __showSyntaxError(self, line=-1):
6050 """
6051 Private slot to handle the 'Show syntax error message'
6052 context menu action.
6053
6054 @param line line number to show the syntax error for (integer)
6055 """
6056 if line == -1:
6057 line = self.line
6058
6059 for handle in list(self.syntaxerrors.keys()):
6060 if self.markerLine(handle) == line:
6061 errors = [e[0] for e in self.syntaxerrors[handle]]
6062 E5MessageBox.critical(
6063 self,
6064 self.tr("Syntax Error"),
6065 "\n".join(errors))
6066 break
6067 else:
6068 E5MessageBox.critical(
6069 self,
6070 self.tr("Syntax Error"),
6071 self.tr("No syntax error message available."))
6072
6073 ###########################################################################
6074 ## VCS conflict marker handling methods below
6075 ###########################################################################
6076
6077 def getVcsConflictMarkerLines(self):
6078 """
6079 Public method to determine the lines containing a VCS conflict marker.
6080
6081 @return list of line numbers containg a VCS conflict marker
6082 @rtype list of int
6083 """
6084 conflictMarkerLines = []
6085
6086 regExp = re.compile("|".join(Editor.VcsConflictMarkerLineRegExpList),
6087 re.MULTILINE)
6088 matches = [m for m in regExp.finditer(self.text())]
6089 for match in matches:
6090 line, _ = self.lineIndexFromPosition(match.start())
6091 conflictMarkerLines.append(line)
6092
6093 return conflictMarkerLines
6094
6095 ###########################################################################
6096 ## Warning handling methods below
6097 ###########################################################################
6098
6099 def toggleWarning(
6100 self, line, col, warning, msg="", warningType=WarningCode):
6101 """
6102 Public method to toggle a warning indicator.
6103
6104 Note: This method is used to set pyflakes and code style warnings.
6105
6106 @param line line number of the warning
6107 @param col column of the warning
6108 @param warning flag indicating if the warning marker should be
6109 set or deleted (boolean)
6110 @param msg warning message (string)
6111 @keyparam warningType type of warning message (integer)
6112 """
6113 if line == 0:
6114 line = 1
6115 # hack to show a warning marker, if line is reported to be 0
6116 if warning:
6117 # set/amend a new warning marker
6118 warn = (msg, warningType)
6119 markers = self.markersAtLine(line - 1)
6120 if not (markers & (1 << self.warning)):
6121 handle = self.markerAdd(line - 1, self.warning)
6122 self.warnings[handle] = [warn]
6123 self.syntaxerrorToggled.emit(self)
6124 else:
6125 for handle in list(self.warnings.keys()):
6126 if self.markerLine(handle) == line - 1 and \
6127 warn not in self.warnings[handle]:
6128 self.warnings[handle].append(warn)
6129 else:
6130 for handle in list(self.warnings.keys()):
6131 if self.markerLine(handle) == line - 1:
6132 del self.warnings[handle]
6133 self.markerDeleteHandle(handle)
6134 self.syntaxerrorToggled.emit(self)
6135
6136 self.__setAnnotation(line - 1)
6137 self.__markerMap.update()
6138
6139 def getWarnings(self):
6140 """
6141 Public method to retrieve the warning markers.
6142
6143 @return sorted list of all lines containing a warning
6144 (list of integer)
6145 """
6146 fwlist = []
6147 for handle in list(self.warnings.keys()):
6148 fwlist.append(self.markerLine(handle) + 1)
6149
6150 fwlist.sort()
6151 return fwlist
6152
6153 def getWarningLines(self):
6154 """
6155 Public method to get the lines containing a warning.
6156
6157 @return list of lines containing a warning (list of integer)
6158 """
6159 lines = []
6160 line = -1
6161 while True:
6162 line = self.markerFindNext(line + 1, 1 << self.warning)
6163 if line < 0:
6164 break
6165 else:
6166 lines.append(line)
6167 return lines
6168
6169 def hasWarnings(self):
6170 """
6171 Public method to check for the presence of warnings.
6172
6173 @return flag indicating the presence of warnings (boolean)
6174 """
6175 return len(self.warnings) > 0
6176
6177 def nextWarning(self):
6178 """
6179 Public slot to handle the 'Next warning' context menu action.
6180 """
6181 line, index = self.getCursorPosition()
6182 if line == self.lines() - 1:
6183 line = 0
6184 else:
6185 line += 1
6186 fwline = self.markerFindNext(line, 1 << self.warning)
6187 if fwline < 0:
6188 # wrap around
6189 fwline = self.markerFindNext(0, 1 << self.warning)
6190 if fwline >= 0:
6191 self.setCursorPosition(fwline, 0)
6192 self.ensureLineVisible(fwline)
6193
6194 def previousWarning(self):
6195 """
6196 Public slot to handle the 'Previous warning' context menu action.
6197 """
6198 line, index = self.getCursorPosition()
6199 if line == 0:
6200 line = self.lines() - 1
6201 else:
6202 line -= 1
6203 fwline = self.markerFindPrevious(line, 1 << self.warning)
6204 if fwline < 0:
6205 # wrap around
6206 fwline = self.markerFindPrevious(
6207 self.lines() - 1, 1 << self.warning)
6208 if fwline >= 0:
6209 self.setCursorPosition(fwline, 0)
6210 self.ensureLineVisible(fwline)
6211
6212 def clearFlakesWarnings(self):
6213 """
6214 Public slot to clear all pyflakes warnings.
6215 """
6216 self.__clearTypedWarning(Editor.WarningCode)
6217
6218 def clearStyleWarnings(self):
6219 """
6220 Public slot to clear all style warnings.
6221 """
6222 self.__clearTypedWarning(Editor.WarningStyle)
6223
6224 def __clearTypedWarning(self, warningKind):
6225 """
6226 Private method to clear warnings of a specific kind.
6227
6228 @param warningKind kind of warning to clear (Editor.WarningCode,
6229 Editor.WarningStyle)
6230 """
6231 assert warningKind in [Editor.WarningCode, Editor.WarningStyle]
6232
6233 for handle in list(self.warnings.keys()):
6234 warnings = []
6235 for msg, warningType in self.warnings[handle]:
6236 if warningType == warningKind:
6237 continue
6238
6239 warnings.append((msg, warningType))
6240
6241 if warnings:
6242 self.warnings[handle] = warnings
6243 self.__setAnnotation(self.markerLine(handle))
6244 else:
6245 del self.warnings[handle]
6246 self.__setAnnotation(self.markerLine(handle))
6247 self.markerDeleteHandle(handle)
6248 self.syntaxerrorToggled.emit(self)
6249 self.__markerMap.update()
6250
6251 def clearWarnings(self):
6252 """
6253 Public slot to clear all warnings.
6254 """
6255 for handle in self.warnings:
6256 self.warnings[handle] = []
6257 self.__setAnnotation(self.markerLine(handle))
6258 self.markerDeleteHandle(handle)
6259 self.warnings = {}
6260 self.syntaxerrorToggled.emit(self)
6261 self.__markerMap.update()
6262
6263 def __showWarning(self, line=-1):
6264 """
6265 Private slot to handle the 'Show warning' context menu action.
6266
6267 @param line line number to show the warning for (integer)
6268 """
6269 if line == -1:
6270 line = self.line
6271
6272 for handle in list(self.warnings.keys()):
6273 if self.markerLine(handle) == line:
6274 E5MessageBox.warning(
6275 self,
6276 self.tr("Warning"),
6277 '\n'.join([w[0] for w in self.warnings[handle]]))
6278 break
6279 else:
6280 E5MessageBox.warning(
6281 self,
6282 self.tr("Warning"),
6283 self.tr("No warning messages available."))
6284
6285 ###########################################################################
6286 ## Annotation handling methods below
6287 ###########################################################################
6288
6289 def __setAnnotationStyles(self):
6290 """
6291 Private slot to define the style used by inline annotations.
6292 """
6293 if hasattr(QsciScintilla, "annotate"):
6294 self.annotationWarningStyle = \
6295 QsciScintilla.STYLE_LASTPREDEFINED + 1
6296 self.SendScintilla(
6297 QsciScintilla.SCI_STYLESETFORE,
6298 self.annotationWarningStyle,
6299 Preferences.getEditorColour("AnnotationsWarningForeground"))
6300 self.SendScintilla(
6301 QsciScintilla.SCI_STYLESETBACK,
6302 self.annotationWarningStyle,
6303 Preferences.getEditorColour("AnnotationsWarningBackground"))
6304
6305 self.annotationErrorStyle = self.annotationWarningStyle + 1
6306 self.SendScintilla(
6307 QsciScintilla.SCI_STYLESETFORE,
6308 self.annotationErrorStyle,
6309 Preferences.getEditorColour("AnnotationsErrorForeground"))
6310 self.SendScintilla(
6311 QsciScintilla.SCI_STYLESETBACK,
6312 self.annotationErrorStyle,
6313 Preferences.getEditorColour("AnnotationsErrorBackground"))
6314
6315 self.annotationStyleStyle = self.annotationErrorStyle + 1
6316 self.SendScintilla(
6317 QsciScintilla.SCI_STYLESETFORE,
6318 self.annotationStyleStyle,
6319 Preferences.getEditorColour("AnnotationsStyleForeground"))
6320 self.SendScintilla(
6321 QsciScintilla.SCI_STYLESETBACK,
6322 self.annotationStyleStyle,
6323 Preferences.getEditorColour("AnnotationsStyleBackground"))
6324
6325 def __setAnnotation(self, line):
6326 """
6327 Private method to set the annotations for the given line.
6328
6329 @param line number of the line that needs annotation (integer)
6330 """
6331 if hasattr(QsciScintilla, "annotate"):
6332 warningAnnotations = []
6333 errorAnnotations = []
6334 styleAnnotations = []
6335
6336 # step 1: do warnings
6337 for handle in self.warnings.keys():
6338 if self.markerLine(handle) == line:
6339 for msg, warningType in self.warnings[handle]:
6340 if warningType == self.WarningStyle:
6341 styleAnnotations.append(
6342 self.tr("Style: {0}").format(msg))
6343 else:
6344 warningAnnotations.append(
6345 self.tr("Warning: {0}").format(msg))
6346
6347 # step 2: do syntax errors
6348 for handle in self.syntaxerrors.keys():
6349 if self.markerLine(handle) == line:
6350 for msg, _ in self.syntaxerrors[handle]:
6351 errorAnnotations.append(
6352 self.tr("Error: {0}").format(msg))
6353
6354 annotations = []
6355 if styleAnnotations:
6356 annotationStyleTxt = "\n".join(styleAnnotations)
6357 if warningAnnotations or errorAnnotations:
6358 annotationStyleTxt += "\n"
6359 annotations.append(QsciStyledText(
6360 annotationStyleTxt, self.annotationStyleStyle))
6361
6362 if warningAnnotations:
6363 annotationWarningTxt = "\n".join(warningAnnotations)
6364 if errorAnnotations:
6365 annotationWarningTxt += "\n"
6366 annotations.append(QsciStyledText(
6367 annotationWarningTxt, self.annotationWarningStyle))
6368
6369 if errorAnnotations:
6370 annotationErrorTxt = "\n".join(errorAnnotations)
6371 annotations.append(QsciStyledText(
6372 annotationErrorTxt, self.annotationErrorStyle))
6373
6374 if annotations:
6375 self.annotate(line, annotations)
6376 else:
6377 self.clearAnnotations(line)
6378
6379 def __refreshAnnotations(self):
6380 """
6381 Private method to refresh the annotations.
6382 """
6383 if hasattr(QsciScintilla, "annotate"):
6384 self.clearAnnotations()
6385 for handle in list(self.warnings.keys()) + \
6386 list(self.syntaxerrors.keys()):
6387 line = self.markerLine(handle)
6388 self.__setAnnotation(line)
6389
6390 #################################################################
6391 ## Fold handling methods
6392 #################################################################
6393
6394 def toggleCurrentFold(self):
6395 """
6396 Public slot to toggle the fold containing the current line.
6397 """
6398 line, index = self.getCursorPosition()
6399 self.foldLine(line)
6400
6401 def expandFoldWithChildren(self, line=-1):
6402 """
6403 Public slot to expand the current fold including its children.
6404
6405 @param line number of line to be expanded
6406 @type int
6407 """
6408 if line == -1:
6409 line, index = self.getCursorPosition()
6410
6411 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line,
6412 QsciScintilla.SC_FOLDACTION_EXPAND)
6413
6414 def collapseFoldWithChildren(self, line=-1):
6415 """
6416 Public slot to collapse the current fold including its children.
6417
6418 @param line number of line to be expanded
6419 @type int
6420 """
6421 if line == -1:
6422 line, index = self.getCursorPosition()
6423
6424 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line,
6425 QsciScintilla.SC_FOLDACTION_CONTRACT)
6426
6427 def __contextMenuExpandFoldWithChildren(self):
6428 """
6429 Private slot to handle the context menu expand with children action.
6430 """
6431 self.expandFoldWithChildren(self.line)
6432
6433 def __contextMenuCollapseFoldWithChildren(self):
6434 """
6435 Private slot to handle the context menu collapse with children action.
6436 """
6437 self.collapseFoldWithChildren(self.line)
6438
6439 #################################################################
6440 ## Macro handling methods
6441 #################################################################
6442
6443 def __getMacroName(self):
6444 """
6445 Private method to select a macro name from the list of macros.
6446
6447 @return Tuple of macro name and a flag, indicating, if the user
6448 pressed ok or canceled the operation. (string, boolean)
6449 """
6450 qs = []
6451 for s in list(self.macros.keys()):
6452 qs.append(s)
6453 qs.sort()
6454 return QInputDialog.getItem(
6455 self,
6456 self.tr("Macro Name"),
6457 self.tr("Select a macro name:"),
6458 qs,
6459 0, False)
6460
6461 def macroRun(self):
6462 """
6463 Public method to execute a macro.
6464 """
6465 name, ok = self.__getMacroName()
6466 if ok and name:
6467 self.macros[name].play()
6468
6469 def macroDelete(self):
6470 """
6471 Public method to delete a macro.
6472 """
6473 name, ok = self.__getMacroName()
6474 if ok and name:
6475 del self.macros[name]
6476
6477 def macroLoad(self):
6478 """
6479 Public method to load a macro from a file.
6480 """
6481 configDir = Utilities.getConfigDir()
6482 fname = E5FileDialog.getOpenFileName(
6483 self,
6484 self.tr("Load macro file"),
6485 configDir,
6486 self.tr("Macro files (*.macro)"))
6487
6488 if not fname:
6489 return # user aborted
6490
6491 try:
6492 f = open(fname, "r", encoding="utf-8")
6493 lines = f.readlines()
6494 f.close()
6495 except IOError:
6496 E5MessageBox.critical(
6497 self,
6498 self.tr("Error loading macro"),
6499 self.tr(
6500 "<p>The macro file <b>{0}</b> could not be read.</p>")
6501 .format(fname))
6502 return
6503
6504 if len(lines) != 2:
6505 E5MessageBox.critical(
6506 self,
6507 self.tr("Error loading macro"),
6508 self.tr("<p>The macro file <b>{0}</b> is corrupt.</p>")
6509 .format(fname))
6510 return
6511
6512 macro = QsciMacro(lines[1], self)
6513 self.macros[lines[0].strip()] = macro
6514
6515 def macroSave(self):
6516 """
6517 Public method to save a macro to a file.
6518 """
6519 configDir = Utilities.getConfigDir()
6520
6521 name, ok = self.__getMacroName()
6522 if not ok or not name:
6523 return # user abort
6524
6525 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
6526 self,
6527 self.tr("Save macro file"),
6528 configDir,
6529 self.tr("Macro files (*.macro)"),
6530 "",
6531 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
6532
6533 if not fname:
6534 return # user aborted
6535
6536 ext = QFileInfo(fname).suffix()
6537 if not ext:
6538 ex = selectedFilter.split("(*")[1].split(")")[0]
6539 if ex:
6540 fname += ex
6541 if QFileInfo(fname).exists():
6542 res = E5MessageBox.yesNo(
6543 self,
6544 self.tr("Save macro"),
6545 self.tr("<p>The macro file <b>{0}</b> already exists."
6546 " Overwrite it?</p>").format(fname),
6547 icon=E5MessageBox.Warning)
6548 if not res:
6549 return
6550 fname = Utilities.toNativeSeparators(fname)
6551
6552 try:
6553 f = open(fname, "w", encoding="utf-8")
6554 f.write("{0}{1}".format(name, "\n"))
6555 f.write(self.macros[name].save())
6556 f.close()
6557 except IOError:
6558 E5MessageBox.critical(
6559 self,
6560 self.tr("Error saving macro"),
6561 self.tr(
6562 "<p>The macro file <b>{0}</b> could not be written.</p>")
6563 .format(fname))
6564 return
6565
6566 def macroRecordingStart(self):
6567 """
6568 Public method to start macro recording.
6569 """
6570 if self.recording:
6571 res = E5MessageBox.yesNo(
6572 self,
6573 self.tr("Start Macro Recording"),
6574 self.tr("Macro recording is already active. Start new?"),
6575 icon=E5MessageBox.Warning,
6576 yesDefault=True)
6577 if res:
6578 self.macroRecordingStop()
6579 else:
6580 return
6581 else:
6582 self.recording = True
6583
6584 self.curMacro = QsciMacro(self)
6585 self.curMacro.startRecording()
6586
6587 def macroRecordingStop(self):
6588 """
6589 Public method to stop macro recording.
6590 """
6591 if not self.recording:
6592 return # we are not recording
6593
6594 self.curMacro.endRecording()
6595 self.recording = False
6596
6597 name, ok = QInputDialog.getText(
6598 self,
6599 self.tr("Macro Recording"),
6600 self.tr("Enter name of the macro:"),
6601 QLineEdit.Normal)
6602
6603 if ok and name:
6604 self.macros[name] = self.curMacro
6605
6606 self.curMacro = None
6607
6608 #################################################################
6609 ## Overwritten methods
6610 #################################################################
6611
6612 def undo(self):
6613 """
6614 Public method to undo the last recorded change.
6615 """
6616 super(Editor, self).undo()
6617 self.undoAvailable.emit(self.isUndoAvailable())
6618 self.redoAvailable.emit(self.isRedoAvailable())
6619
6620 def redo(self):
6621 """
6622 Public method to redo the last recorded change.
6623 """
6624 super(Editor, self).redo()
6625 self.undoAvailable.emit(self.isUndoAvailable())
6626 self.redoAvailable.emit(self.isRedoAvailable())
6627
6628 def close(self, alsoDelete=False):
6629 """
6630 Public method called when the window gets closed.
6631
6632 This overwritten method redirects the action to our
6633 ViewManager.closeEditor, which in turn calls our closeIt
6634 method.
6635
6636 @param alsoDelete ignored
6637 @return flag indicating a successful close of the editor (boolean)
6638 """
6639 return self.vm.closeEditor(self)
6640
6641 def closeIt(self):
6642 """
6643 Public method called by the viewmanager to finally get rid of us.
6644 """
6645 if Preferences.getEditor("ClearBreaksOnClose") and not self.__clones:
6646 self.__menuClearBreakpoints()
6647
6648 for clone in self.__clones[:]:
6649 self.removeClone(clone)
6650 clone.removeClone(self)
6651
6652 self.breakpointModel.rowsAboutToBeRemoved.disconnect(
6653 self.__deleteBreakPoints)
6654 self.breakpointModel.dataAboutToBeChanged.disconnect(
6655 self.__breakPointDataAboutToBeChanged)
6656 self.breakpointModel.dataChanged.disconnect(
6657 self.__changeBreakPoints)
6658 self.breakpointModel.rowsInserted.disconnect(
6659 self.__addBreakPoints)
6660
6661 if self.syntaxCheckService is not None:
6662 self.syntaxCheckService.syntaxChecked.disconnect(
6663 self.__processSyntaxCheckResult)
6664 self.syntaxCheckService.error.disconnect(
6665 self.__processSyntaxCheckError)
6666
6667 if self.spell:
6668 self.spell.stopIncrementalCheck()
6669
6670 try:
6671 self.project.projectPropertiesChanged.disconnect(
6672 self.__projectPropertiesChanged)
6673 except TypeError:
6674 pass
6675
6676 if self.fileName:
6677 self.taskViewer.clearFileTasks(self.fileName, True)
6678
6679 super(Editor, self).close()
6680
6681 def keyPressEvent(self, ev):
6682 """
6683 Protected method to handle the user input a key at a time.
6684
6685 @param ev key event (QKeyEvent)
6686 """
6687 txt = ev.text()
6688
6689 # See it is text to insert.
6690 if len(txt) and txt >= " ":
6691 super(Editor, self).keyPressEvent(ev)
6692 else:
6693 ev.ignore()
6694
6695 def focusInEvent(self, event):
6696 """
6697 Protected method called when the editor receives focus.
6698
6699 This method checks for modifications of the current file and
6700 rereads it upon request. The cursor is placed at the current position
6701 assuming, that it is in the vicinity of the old position after the
6702 reread.
6703
6704 @param event the event object (QFocusEvent)
6705 """
6706 self.recolor()
6707 self.vm.editActGrp.setEnabled(True)
6708 self.vm.editorActGrp.setEnabled(True)
6709 self.vm.copyActGrp.setEnabled(True)
6710 self.vm.viewActGrp.setEnabled(True)
6711 self.vm.searchActGrp.setEnabled(True)
6712 try:
6713 self.setCaretWidth(self.caretWidth)
6714 except AttributeError:
6715 pass
6716 self.__updateReadOnly(False)
6717 if self.vm.editorsCheckFocusInEnabled() and \
6718 not self.inReopenPrompt and self.fileName and \
6719 QFileInfo(self.fileName).lastModified().toString() != \
6720 self.lastModified.toString():
6721 self.inReopenPrompt = True
6722 if Preferences.getEditor("AutoReopen") and not self.isModified():
6723 self.refresh()
6724 else:
6725 msg = self.tr(
6726 """<p>The file <b>{0}</b> has been changed while it"""
6727 """ was opened in eric6. Reread it?</p>""")\
6728 .format(self.fileName)
6729 yesDefault = True
6730 if self.isModified():
6731 msg += self.tr(
6732 """<br><b>Warning:</b> You will lose"""
6733 """ your changes upon reopening it.""")
6734 yesDefault = False
6735 res = E5MessageBox.yesNo(
6736 self,
6737 self.tr("File changed"), msg,
6738 icon=E5MessageBox.Warning,
6739 yesDefault=yesDefault)
6740 if res:
6741 self.refresh()
6742 else:
6743 # do not prompt for this change again...
6744 self.lastModified = QFileInfo(self.fileName).lastModified()
6745 self.inReopenPrompt = False
6746
6747 self.setCursorFlashTime(QApplication.cursorFlashTime())
6748
6749 super(Editor, self).focusInEvent(event)
6750
6751 def focusOutEvent(self, event):
6752 """
6753 Protected method called when the editor loses focus.
6754
6755 @param event the event object (QFocusEvent)
6756 """
6757 self.vm.editorActGrp.setEnabled(False)
6758 self.setCaretWidth(0)
6759
6760 super(Editor, self).focusOutEvent(event)
6761
6762 def changeEvent(self, evt):
6763 """
6764 Protected method called to process an event.
6765
6766 This implements special handling for the events showMaximized,
6767 showMinimized and showNormal. The windows caption is shortened
6768 for the minimized mode and reset to the full filename for the
6769 other modes. This is to make the editor windows work nicer
6770 with the QWorkspace.
6771
6772 @param evt the event, that was generated (QEvent)
6773 """
6774 if evt.type() == QEvent.WindowStateChange and \
6775 bool(self.fileName):
6776 if self.windowState() == Qt.WindowStates(Qt.WindowMinimized):
6777 cap = os.path.basename(self.fileName)
6778 else:
6779 cap = self.fileName
6780 if self.isReadOnly():
6781 cap = self.tr("{0} (ro)").format(cap)
6782 self.setWindowTitle(cap)
6783
6784 super(Editor, self).changeEvent(evt)
6785
6786 def mousePressEvent(self, event):
6787 """
6788 Protected method to handle the mouse press event.
6789
6790 @param event the mouse press event (QMouseEvent)
6791 """
6792 self.vm.eventFilter(self, event)
6793 super(Editor, self).mousePressEvent(event)
6794
6795 def wheelEvent(self, evt):
6796 """
6797 Protected method to handle wheel events.
6798
6799 @param evt reference to the wheel event (QWheelEvent)
6800 """
6801 if qVersionTuple() >= (5, 0, 0):
6802 delta = evt.angleDelta().y()
6803 else:
6804 delta = evt.delta()
6805 if evt.modifiers() & Qt.ControlModifier:
6806 if delta < 0:
6807 self.zoomOut()
6808 elif delta > 0:
6809 self.zoomIn()
6810 evt.accept()
6811 return
6812
6813 if evt.modifiers() & Qt.ShiftModifier:
6814 if delta < 0:
6815 self.gotoMethodClass(False)
6816 elif delta > 0:
6817 self.gotoMethodClass(True)
6818 evt.accept()
6819 return
6820
6821 super(Editor, self).wheelEvent(evt)
6822
6823 def event(self, evt):
6824 """
6825 Public method handling events.
6826
6827 @param evt reference to the event (QEvent)
6828 @return flag indicating, if the event was handled (boolean)
6829 """
6830 if evt.type() == QEvent.Gesture:
6831 self.gestureEvent(evt)
6832 return True
6833
6834 return super(Editor, self).event(evt)
6835
6836 def gestureEvent(self, evt):
6837 """
6838 Protected method handling gesture events.
6839
6840 @param evt reference to the gesture event (QGestureEvent
6841 """
6842 pinch = evt.gesture(Qt.PinchGesture)
6843 if pinch:
6844 if pinch.state() == Qt.GestureStarted:
6845 zoom = (self.getZoom() + 10) / 10.0
6846 pinch.setTotalScaleFactor(zoom)
6847 elif pinch.state() == Qt.GestureUpdated:
6848 zoom = int(pinch.totalScaleFactor() * 10) - 10
6849 if zoom <= -9:
6850 zoom = -9
6851 pinch.setTotalScaleFactor(0.1)
6852 elif zoom >= 20:
6853 zoom = 20
6854 pinch.setTotalScaleFactor(3.0)
6855 self.zoomTo(zoom)
6856 evt.accept()
6857
6858 def resizeEvent(self, evt):
6859 """
6860 Protected method handling resize events.
6861
6862 @param evt reference to the resize event (QResizeEvent)
6863 """
6864 super(Editor, self).resizeEvent(evt)
6865 self.__markerMap.calculateGeometry()
6866
6867 def viewportEvent(self, evt):
6868 """
6869 Protected method handling event of the viewport.
6870
6871 @param evt reference to the event (QEvent)
6872 @return flag indiating that the event was handled (boolean)
6873 """
6874 self.__markerMap.calculateGeometry()
6875 return super(Editor, self).viewportEvent(evt)
6876
6877 def __updateReadOnly(self, bForce=True):
6878 """
6879 Private method to update the readOnly information for this editor.
6880
6881 If bForce is True, then updates everything regardless if
6882 the attributes have actually changed, such as during
6883 initialization time. A signal is emitted after the
6884 caption change.
6885
6886 @param bForce True to force change, False to only update and emit
6887 signal if there was an attribute change.
6888 """
6889 if self.fileName == "":
6890 return
6891
6892 readOnly = not QFileInfo(self.fileName).isWritable() or \
6893 self.isReadOnly()
6894 if not bForce and (readOnly == self.isReadOnly()):
6895 return
6896
6897 cap = self.fileName
6898 if readOnly:
6899 cap = self.tr("{0} (ro)".format(cap))
6900 self.setReadOnly(readOnly)
6901 self.setWindowTitle(cap)
6902 self.captionChanged.emit(cap, self)
6903
6904 def refresh(self):
6905 """
6906 Public slot to refresh the editor contents.
6907 """
6908 # save cursor position
6909 cline, cindex = self.getCursorPosition()
6910
6911 # save bookmarks and breakpoints and clear them
6912 bmlist = self.getBookmarks()
6913 self.clearBookmarks()
6914
6915 # clear syntax error markers
6916 self.clearSyntaxError()
6917
6918 # clear flakes warning markers
6919 self.clearWarnings()
6920
6921 # clear breakpoint markers
6922 for handle in list(self.breaks.keys()):
6923 self.markerDeleteHandle(handle)
6924 self.breaks = {}
6925
6926 if not os.path.exists(self.fileName):
6927 # close the file, if it was deleted in the background
6928 self.close()
6929 return
6930
6931 # reread the file
6932 try:
6933 self.readFile(self.fileName)
6934 except IOError:
6935 # do not prompt for this change again...
6936 self.lastModified = QDateTime.currentDateTime()
6937 self.setModified(False)
6938
6939 # re-initialize the online change tracer
6940 self.__reinitOnlineChangeTrace()
6941
6942 # reset cursor position
6943 self.setCursorPosition(cline, cindex)
6944 self.ensureCursorVisible()
6945
6946 # reset bookmarks and breakpoints to their old position
6947 if bmlist:
6948 for bm in bmlist:
6949 self.toggleBookmark(bm)
6950 self.__restoreBreakpoints()
6951
6952 self.editorSaved.emit(self.fileName)
6953 self.checkSyntax()
6954
6955 self.__markerMap.update()
6956
6957 self.refreshed.emit()
6958
6959 def setMonospaced(self, on):
6960 """
6961 Public method to set/reset a monospaced font.
6962
6963 @param on flag to indicate usage of a monospace font (boolean)
6964 """
6965 if on:
6966 if not self.lexer_:
6967 f = Preferences.getEditorOtherFonts("MonospacedFont")
6968 self.monospacedStyles(f)
6969 else:
6970 if not self.lexer_:
6971 self.clearStyles()
6972 self.__setMarginsDisplay()
6973 self.setFont(Preferences.getEditorOtherFonts("DefaultFont"))
6974
6975 self.useMonospaced = on
6976
6977 def clearStyles(self):
6978 """
6979 Public method to set the styles according the selected Qt style
6980 or the selected editor colours.
6981 """
6982 super(Editor, self).clearStyles()
6983 if Preferences.getEditor("OverrideEditAreaColours"):
6984 self.setColor(Preferences.getEditorColour("EditAreaForeground"))
6985 self.setPaper(Preferences.getEditorColour("EditAreaBackground"))
6986
6987 #################################################################
6988 ## Drag and Drop Support
6989 #################################################################
6990
6991 def dragEnterEvent(self, event):
6992 """
6993 Protected method to handle the drag enter event.
6994
6995 @param event the drag enter event (QDragEnterEvent)
6996 """
6997 self.inDragDrop = event.mimeData().hasUrls()
6998 if self.inDragDrop:
6999 event.acceptProposedAction()
7000 else:
7001 super(Editor, self).dragEnterEvent(event)
7002
7003 def dragMoveEvent(self, event):
7004 """
7005 Protected method to handle the drag move event.
7006
7007 @param event the drag move event (QDragMoveEvent)
7008 """
7009 if self.inDragDrop:
7010 event.accept()
7011 else:
7012 super(Editor, self).dragMoveEvent(event)
7013
7014 def dragLeaveEvent(self, event):
7015 """
7016 Protected method to handle the drag leave event.
7017
7018 @param event the drag leave event (QDragLeaveEvent)
7019 """
7020 if self.inDragDrop:
7021 self.inDragDrop = False
7022 event.accept()
7023 else:
7024 super(Editor, self).dragLeaveEvent(event)
7025
7026 def dropEvent(self, event):
7027 """
7028 Protected method to handle the drop event.
7029
7030 @param event the drop event (QDropEvent)
7031 """
7032 if event.mimeData().hasUrls():
7033 for url in event.mimeData().urls():
7034 fname = url.toLocalFile()
7035 if fname:
7036 if not QFileInfo(fname).isDir():
7037 self.vm.openSourceFile(fname)
7038 else:
7039 E5MessageBox.information(
7040 self,
7041 self.tr("Drop Error"),
7042 self.tr("""<p><b>{0}</b> is not a file.</p>""")
7043 .format(fname))
7044 event.acceptProposedAction()
7045 else:
7046 super(Editor, self).dropEvent(event)
7047
7048 self.inDragDrop = False
7049
7050 #################################################################
7051 ## Support for Qt resources files
7052 #################################################################
7053
7054 def __initContextMenuResources(self):
7055 """
7056 Private method used to setup the Resources context sub menu.
7057
7058 @return reference to the generated menu (QMenu)
7059 """
7060 menu = QMenu(self.tr('Resources'))
7061
7062 menu.addAction(
7063 self.tr('Add file...'), self.__addFileResource)
7064 menu.addAction(
7065 self.tr('Add files...'), self.__addFileResources)
7066 menu.addAction(
7067 self.tr('Add aliased file...'),
7068 self.__addFileAliasResource)
7069 menu.addAction(
7070 self.tr('Add localized resource...'),
7071 self.__addLocalizedResource)
7072 menu.addSeparator()
7073 menu.addAction(
7074 self.tr('Add resource frame'), self.__addResourceFrame)
7075
7076 menu.aboutToShow.connect(self.__showContextMenuResources)
7077
7078 return menu
7079
7080 def __showContextMenuResources(self):
7081 """
7082 Private slot handling the aboutToShow signal of the resources context
7083 menu.
7084 """
7085 self.showMenu.emit("Resources", self.resourcesMenu, self)
7086
7087 def __addFileResource(self):
7088 """
7089 Private method to handle the Add file context menu action.
7090 """
7091 dirStr = os.path.dirname(self.fileName)
7092 file = E5FileDialog.getOpenFileName(
7093 self,
7094 self.tr("Add file resource"),
7095 dirStr,
7096 "")
7097 if file:
7098 relFile = QDir(dirStr).relativeFilePath(file)
7099 line, index = self.getCursorPosition()
7100 self.insert(" <file>{0}</file>\n".format(relFile))
7101 self.setCursorPosition(line + 1, index)
7102
7103 def __addFileResources(self):
7104 """
7105 Private method to handle the Add files context menu action.
7106 """
7107 dirStr = os.path.dirname(self.fileName)
7108 files = E5FileDialog.getOpenFileNames(
7109 self,
7110 self.tr("Add file resources"),
7111 dirStr,
7112 "")
7113 if files:
7114 myDir = QDir(dirStr)
7115 filesText = ""
7116 for file in files:
7117 relFile = myDir.relativeFilePath(file)
7118 filesText += " <file>{0}</file>\n".format(relFile)
7119 line, index = self.getCursorPosition()
7120 self.insert(filesText)
7121 self.setCursorPosition(line + len(files), index)
7122
7123 def __addFileAliasResource(self):
7124 """
7125 Private method to handle the Add aliased file context menu action.
7126 """
7127 dirStr = os.path.dirname(self.fileName)
7128 file = E5FileDialog.getOpenFileName(
7129 self,
7130 self.tr("Add aliased file resource"),
7131 dirStr,
7132 "")
7133 if file:
7134 relFile = QDir(dirStr).relativeFilePath(file)
7135 alias, ok = QInputDialog.getText(
7136 self,
7137 self.tr("Add aliased file resource"),
7138 self.tr("Alias for file <b>{0}</b>:").format(relFile),
7139 QLineEdit.Normal,
7140 relFile)
7141 if ok and alias:
7142 line, index = self.getCursorPosition()
7143 self.insert(' <file alias="{1}">{0}</file>\n'
7144 .format(relFile, alias))
7145 self.setCursorPosition(line + 1, index)
7146
7147 def __addLocalizedResource(self):
7148 """
7149 Private method to handle the Add localized resource context menu
7150 action.
7151 """
7152 from Project.AddLanguageDialog import AddLanguageDialog
7153 dlg = AddLanguageDialog(self)
7154 if dlg.exec_() == QDialog.Accepted:
7155 lang = dlg.getSelectedLanguage()
7156 line, index = self.getCursorPosition()
7157 self.insert('<qresource lang="{0}">\n</qresource>\n'.format(lang))
7158 self.setCursorPosition(line + 2, index)
7159
7160 def __addResourceFrame(self):
7161 """
7162 Private method to handle the Add resource frame context menu action.
7163 """
7164 line, index = self.getCursorPosition()
7165 self.insert('<!DOCTYPE RCC>\n'
7166 '<RCC version="1.0">\n'
7167 '<qresource>\n'
7168 '</qresource>\n'
7169 '</RCC>\n')
7170 self.setCursorPosition(line + 5, index)
7171
7172 #################################################################
7173 ## Support for diagrams below
7174 #################################################################
7175
7176 def __showClassDiagram(self):
7177 """
7178 Private method to handle the Class Diagram context menu action.
7179 """
7180 from Graphics.UMLDialog import UMLDialog
7181 if not self.checkDirty():
7182 return
7183
7184 self.classDiagram = UMLDialog(
7185 UMLDialog.ClassDiagram, self.project, self.fileName,
7186 self, noAttrs=False)
7187 self.classDiagram.show()
7188
7189 def __showPackageDiagram(self):
7190 """
7191 Private method to handle the Package Diagram context menu action.
7192 """
7193 from Graphics.UMLDialog import UMLDialog
7194 if not self.checkDirty():
7195 return
7196
7197 package = os.path.isdir(self.fileName) and \
7198 self.fileName or os.path.dirname(self.fileName)
7199 res = E5MessageBox.yesNo(
7200 self,
7201 self.tr("Package Diagram"),
7202 self.tr("""Include class attributes?"""),
7203 yesDefault=True)
7204 self.packageDiagram = UMLDialog(
7205 UMLDialog.PackageDiagram, self.project, package,
7206 self, noAttrs=not res)
7207 self.packageDiagram.show()
7208
7209 def __showImportsDiagram(self):
7210 """
7211 Private method to handle the Imports Diagram context menu action.
7212 """
7213 from Graphics.UMLDialog import UMLDialog
7214 if not self.checkDirty():
7215 return
7216
7217 package = os.path.isdir(self.fileName) and self.fileName \
7218 or os.path.dirname(self.fileName)
7219 res = E5MessageBox.yesNo(
7220 self,
7221 self.tr("Imports Diagram"),
7222 self.tr("""Include imports from external modules?"""))
7223 self.importsDiagram = UMLDialog(
7224 UMLDialog.ImportsDiagram, self.project, package,
7225 self, showExternalImports=res)
7226 self.importsDiagram.show()
7227
7228 def __showApplicationDiagram(self):
7229 """
7230 Private method to handle the Imports Diagram context menu action.
7231 """
7232 from Graphics.UMLDialog import UMLDialog
7233 res = E5MessageBox.yesNo(
7234 self,
7235 self.tr("Application Diagram"),
7236 self.tr("""Include module names?"""),
7237 yesDefault=True)
7238 self.applicationDiagram = UMLDialog(
7239 UMLDialog.ApplicationDiagram, self.project,
7240 self, noModules=not res)
7241 self.applicationDiagram.show()
7242
7243 def __loadDiagram(self):
7244 """
7245 Private slot to load a diagram from file.
7246 """
7247 from Graphics.UMLDialog import UMLDialog
7248 self.loadedDiagram = UMLDialog(
7249 UMLDialog.NoDiagram, self.project, parent=self)
7250 if self.loadedDiagram.load():
7251 self.loadedDiagram.show(fromFile=True)
7252 else:
7253 self.loadedDiagram = None
7254
7255 #######################################################################
7256 ## Typing aids related methods below
7257 #######################################################################
7258
7259 def __toggleTypingAids(self):
7260 """
7261 Private slot to toggle the typing aids.
7262 """
7263 if self.menuActs["TypingAidsEnabled"].isChecked():
7264 self.completer.setEnabled(True)
7265 else:
7266 self.completer.setEnabled(False)
7267
7268 #######################################################################
7269 ## Auto-completing templates
7270 #######################################################################
7271
7272 def editorCommand(self, cmd):
7273 """
7274 Public method to perform a simple editor command.
7275
7276 @param cmd the scintilla command to be performed
7277 """
7278 if cmd == QsciScintilla.SCI_TAB:
7279 try:
7280 templateViewer = e5App().getObject("TemplateViewer")
7281 except KeyError:
7282 # template viewer is not active
7283 templateViewer = None
7284
7285 if templateViewer is not None:
7286 line, index = self.getCursorPosition()
7287 tmplName = self.getWordLeft(line, index)
7288 if tmplName:
7289 if templateViewer.hasTemplate(tmplName,
7290 self.getLanguage()):
7291 self.__applyTemplate(tmplName, self.getLanguage())
7292 return
7293 else:
7294 templateNames = templateViewer.getTemplateNames(
7295 tmplName, self.getLanguage())
7296 if len(templateNames) == 1:
7297 self.__applyTemplate(templateNames[0],
7298 self.getLanguage())
7299 return
7300 elif len(templateNames) > 1:
7301 self.showUserList(
7302 TemplateCompletionListID,
7303 ["{0}?{1:d}".format(t, self.TemplateImageID)
7304 for t in templateNames])
7305 return
7306
7307 super(Editor, self).editorCommand(cmd)
7308
7309 def __applyTemplate(self, templateName, language):
7310 """
7311 Private method to apply a template by name.
7312
7313 @param templateName name of the template to apply (string)
7314 @param language name of the language (group) to get the template
7315 from (string)
7316 """
7317 try:
7318 templateViewer = e5App().getObject("TemplateViewer")
7319 except KeyError:
7320 # template viewer is not active
7321 return
7322
7323 if templateViewer.hasTemplate(templateName, self.getLanguage()):
7324 self.extendSelectionWordLeft()
7325 templateViewer.applyNamedTemplate(templateName,
7326 self.getLanguage())
7327
7328 #######################################################################
7329 ## Project related methods
7330 #######################################################################
7331
7332 def __projectPropertiesChanged(self):
7333 """
7334 Private slot to handle changes of the project properties.
7335 """
7336 if self.spell:
7337 pwl, pel = self.project.getProjectDictionaries()
7338 self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
7339 pwl=pwl, pel=pel)
7340
7341 editorConfigEol = self.__getEditorConfig("EOLMode", nodefault=True)
7342 if editorConfigEol is not None:
7343 self.setEolMode(editorConfigEol)
7344 else:
7345 self.setEolModeByEolString(self.project.getEolString())
7346 self.convertEols(self.eolMode())
7347
7348 def addedToProject(self):
7349 """
7350 Public method to signal, that this editor has been added to a project.
7351 """
7352 if self.spell:
7353 pwl, pel = self.project.getProjectDictionaries()
7354 self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
7355 pwl=pwl, pel=pel)
7356
7357 self.project.projectPropertiesChanged.connect(
7358 self.__projectPropertiesChanged)
7359
7360 def projectOpened(self):
7361 """
7362 Public slot to handle the opening of a project.
7363 """
7364 if self.fileName and \
7365 self.project.isProjectSource(self.fileName):
7366 self.project.projectPropertiesChanged.connect(
7367 self.__projectPropertiesChanged)
7368 self.setSpellingForProject()
7369
7370 def projectClosed(self):
7371 """
7372 Public slot to handle the closing of a project.
7373 """
7374 try:
7375 self.project.projectPropertiesChanged.disconnect(
7376 self.__projectPropertiesChanged)
7377 except TypeError:
7378 pass
7379
7380 #######################################################################
7381 ## Spell checking related methods
7382 #######################################################################
7383
7384 def __setSpellingLanguage(self, language, pwl="", pel=""):
7385 """
7386 Private slot to set the spell checking language.
7387
7388 @param language spell checking language to be set (string)
7389 @keyparam pwl name of the personal/project word list (string)
7390 @keyparam pel name of the personal/project exclude list (string)
7391 """
7392 if self.spell and self.spell.getLanguage() != language:
7393 self.spell.setLanguage(language, pwl=pwl, pel=pel)
7394 self.spell.checkDocumentIncrementally()
7395
7396 def __setSpelling(self):
7397 """
7398 Private method to initialize the spell checking functionality.
7399 """
7400 if Preferences.getEditor("SpellCheckingEnabled"):
7401 self.__spellCheckStringsOnly = Preferences.getEditor(
7402 "SpellCheckStringsOnly")
7403 if self.spell is None:
7404 from .SpellChecker import SpellChecker
7405 self.spell = SpellChecker(self, self.spellingIndicator,
7406 checkRegion=self.isSpellCheckRegion)
7407 self.setSpellingForProject()
7408 self.spell.setMinimumWordSize(
7409 Preferences.getEditor("SpellCheckingMinWordSize"))
7410
7411 self.setAutoSpellChecking()
7412 else:
7413 self.spell = None
7414 self.clearAllIndicators(self.spellingIndicator)
7415
7416 def setSpellingForProject(self):
7417 """
7418 Public method to set the spell checking options for files belonging
7419 to the current project.
7420 """
7421 if self.fileName and \
7422 self.project.isOpen() and \
7423 self.project.isProjectSource(self.fileName):
7424 pwl, pel = self.project.getProjectDictionaries()
7425 self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
7426 pwl=pwl, pel=pel)
7427
7428 def setAutoSpellChecking(self):
7429 """
7430 Public method to set the automatic spell checking.
7431 """
7432 if Preferences.getEditor("AutoSpellCheckingEnabled"):
7433 try:
7434 self.SCN_CHARADDED.connect(
7435 self.__spellCharAdded, Qt.UniqueConnection)
7436 except TypeError:
7437 pass
7438 self.spell.checkDocumentIncrementally()
7439 else:
7440 try:
7441 self.SCN_CHARADDED.disconnect(self.__spellCharAdded)
7442 except TypeError:
7443 pass
7444 self.clearAllIndicators(self.spellingIndicator)
7445
7446 def isSpellCheckRegion(self, pos):
7447 """
7448 Public method to check, if the given position is within a region, that
7449 should be spell checked.
7450
7451 @param pos position to be checked (integer)
7452 @return flag indicating pos is in a spell check region (boolean)
7453 """
7454 if self.__spellCheckStringsOnly:
7455 style = self.styleAt(pos)
7456 if self.lexer_ is not None:
7457 return self.lexer_.isCommentStyle(style) or \
7458 self.lexer_.isStringStyle(style)
7459 return True
7460
7461 @pyqtSlot(int)
7462 def __spellCharAdded(self, charNumber):
7463 """
7464 Private slot called to handle the user entering a character.
7465
7466 @param charNumber value of the character entered (integer)
7467 """
7468 if self.spell:
7469 if not chr(charNumber).isalnum():
7470 self.spell.checkWord(
7471 self.positionBefore(self.currentPosition()), True)
7472 elif self.hasIndicator(
7473 self.spellingIndicator, self.currentPosition()):
7474 self.spell.checkWord(self.currentPosition())
7475
7476 def checkSpelling(self):
7477 """
7478 Public slot to perform an interactive spell check of the document.
7479 """
7480 if self.spell:
7481 cline, cindex = self.getCursorPosition()
7482 from .SpellCheckingDialog import SpellCheckingDialog
7483 dlg = SpellCheckingDialog(self.spell, 0, self.length(), self)
7484 dlg.exec_()
7485 self.setCursorPosition(cline, cindex)
7486 if Preferences.getEditor("AutoSpellCheckingEnabled"):
7487 self.spell.checkDocumentIncrementally()
7488
7489 def __checkSpellingSelection(self):
7490 """
7491 Private slot to spell check the current selection.
7492 """
7493 from .SpellCheckingDialog import SpellCheckingDialog
7494 sline, sindex, eline, eindex = self.getSelection()
7495 startPos = self.positionFromLineIndex(sline, sindex)
7496 endPos = self.positionFromLineIndex(eline, eindex)
7497 dlg = SpellCheckingDialog(self.spell, startPos, endPos, self)
7498 dlg.exec_()
7499
7500 def __checkSpellingWord(self):
7501 """
7502 Private slot to check the word below the spelling context menu.
7503 """
7504 from .SpellCheckingDialog import SpellCheckingDialog
7505 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
7506 wordStart, wordEnd = self.getWordBoundaries(line, index)
7507 wordStartPos = self.positionFromLineIndex(line, wordStart)
7508 wordEndPos = self.positionFromLineIndex(line, wordEnd)
7509 dlg = SpellCheckingDialog(self.spell, wordStartPos, wordEndPos, self)
7510 dlg.exec_()
7511
7512 def __showContextMenuSpelling(self):
7513 """
7514 Private slot to set up the spelling menu before it is shown.
7515 """
7516 self.spellingMenu.clear()
7517 self.spellingSuggActs = []
7518 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
7519 word = self.getWord(line, index)
7520 suggestions = self.spell.getSuggestions(word)
7521 for suggestion in suggestions[:5]:
7522 self.spellingSuggActs.append(
7523 self.spellingMenu.addAction(suggestion))
7524 if suggestions:
7525 self.spellingMenu.addSeparator()
7526 self.spellingMenu.addAction(
7527 UI.PixmapCache.getIcon("spellchecking.png"),
7528 self.tr("Check spelling..."), self.__checkSpellingWord)
7529 self.spellingMenu.addAction(
7530 self.tr("Add to dictionary"), self.__addToSpellingDictionary)
7531 self.spellingMenu.addAction(
7532 self.tr("Ignore All"), self.__ignoreSpellingAlways)
7533
7534 self.showMenu.emit("Spelling", self.spellingMenu, self)
7535
7536 def __contextMenuSpellingTriggered(self, action):
7537 """
7538 Private slot to handle the selection of a suggestion of the spelling
7539 context menu.
7540
7541 @param action reference to the action that was selected (QAction)
7542 """
7543 if action in self.spellingSuggActs:
7544 replacement = action.text()
7545 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
7546 wordStart, wordEnd = self.getWordBoundaries(line, index)
7547 self.setSelection(line, wordStart, line, wordEnd)
7548 self.beginUndoAction()
7549 self.removeSelectedText()
7550 self.insert(replacement)
7551 self.endUndoAction()
7552
7553 def __addToSpellingDictionary(self):
7554 """
7555 Private slot to add the word below the spelling context menu to the
7556 dictionary.
7557 """
7558 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
7559 word = self.getWord(line, index)
7560 self.spell.add(word)
7561
7562 wordStart, wordEnd = self.getWordBoundaries(line, index)
7563 self.clearIndicator(self.spellingIndicator, line, wordStart,
7564 line, wordEnd)
7565 if Preferences.getEditor("AutoSpellCheckingEnabled"):
7566 self.spell.checkDocumentIncrementally()
7567
7568 def __removeFromSpellingDictionary(self):
7569 """
7570 Private slot to remove the word below the context menu to the
7571 dictionary.
7572 """
7573 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
7574 word = self.getWord(line, index)
7575 self.spell.remove(word)
7576
7577 if Preferences.getEditor("AutoSpellCheckingEnabled"):
7578 self.spell.checkDocumentIncrementally()
7579
7580 def __ignoreSpellingAlways(self):
7581 """
7582 Private to always ignore the word below the spelling context menu.
7583 """
7584 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
7585 word = self.getWord(line, index)
7586 self.spell.ignoreAlways(word)
7587 if Preferences.getEditor("AutoSpellCheckingEnabled"):
7588 self.spell.checkDocumentIncrementally()
7589
7590 #######################################################################
7591 ## Cooperation related methods
7592 #######################################################################
7593
7594 def getSharingStatus(self):
7595 """
7596 Public method to get some share status info.
7597
7598 @return tuple indicating, if the editor is sharable, the sharing
7599 status, if it is inside a locally initiated shared edit session
7600 and if it is inside a remotely initiated shared edit session
7601 (boolean, boolean, boolean, boolean)
7602 """
7603 return bool(self.fileName) and \
7604 self.project.isOpen() and \
7605 self.project.isProjectFile(self.fileName), \
7606 self.__isShared, self.__inSharedEdit, self.__inRemoteSharedEdit
7607
7608 def shareConnected(self, connected):
7609 """
7610 Public slot to handle a change of the connected state.
7611
7612 @param connected flag indicating the connected state (boolean)
7613 """
7614 if not connected:
7615 self.__inRemoteSharedEdit = False
7616 self.setReadOnly(False)
7617 self.__updateReadOnly()
7618 self.cancelSharedEdit(send=False)
7619 self.__isSyncing = False
7620 self.__receivedWhileSyncing = []
7621
7622 def shareEditor(self, share):
7623 """
7624 Public slot to set the shared status of the editor.
7625
7626 @param share flag indicating the share status (boolean)
7627 """
7628 self.__isShared = share
7629 if not share:
7630 self.shareConnected(False)
7631
7632 def startSharedEdit(self):
7633 """
7634 Public slot to start a shared edit session for the editor.
7635 """
7636 self.__inSharedEdit = True
7637 self.__savedText = self.text()
7638 hashStr = str(
7639 QCryptographicHash.hash(
7640 Utilities.encode(self.__savedText, self.encoding)[0],
7641 QCryptographicHash.Sha1).toHex(),
7642 encoding="utf-8")
7643 self.__send(Editor.StartEditToken, hashStr)
7644
7645 def sendSharedEdit(self):
7646 """
7647 Public slot to end a shared edit session for the editor and
7648 send the changes.
7649 """
7650 commands = self.__calculateChanges(self.__savedText, self.text())
7651 self.__send(Editor.EndEditToken, commands)
7652 self.__inSharedEdit = False
7653 self.__savedText = ""
7654
7655 def cancelSharedEdit(self, send=True):
7656 """
7657 Public slot to cancel a shared edit session for the editor.
7658
7659 @keyparam send flag indicating to send the CancelEdit command (boolean)
7660 """
7661 self.__inSharedEdit = False
7662 self.__savedText = ""
7663 if send:
7664 self.__send(Editor.CancelEditToken)
7665
7666 def __send(self, token, args=None):
7667 """
7668 Private method to send an editor command to remote editors.
7669
7670 @param token command token (string)
7671 @param args arguments for the command (string)
7672 """
7673 if self.vm.isConnected():
7674 msg = ""
7675 if token in (Editor.StartEditToken,
7676 Editor.EndEditToken,
7677 Editor.RequestSyncToken,
7678 Editor.SyncToken):
7679 msg = "{0}{1}{2}".format(
7680 token,
7681 Editor.Separator,
7682 args
7683 )
7684 elif token == Editor.CancelEditToken:
7685 msg = "{0}{1}c".format(
7686 token,
7687 Editor.Separator
7688 )
7689
7690 self.vm.send(self.fileName, msg)
7691
7692 def receive(self, command):
7693 """
7694 Public slot to handle received editor commands.
7695
7696 @param command command string (string)
7697 """
7698 if self.__isShared:
7699 if self.__isSyncing and \
7700 not command.startswith(Editor.SyncToken + Editor.Separator):
7701 self.__receivedWhileSyncing.append(command)
7702 else:
7703 self.__dispatchCommand(command)
7704
7705 def __dispatchCommand(self, command):
7706 """
7707 Private method to dispatch received commands.
7708
7709 @param command command to be processed (string)
7710 """
7711 token, argsString = command.split(Editor.Separator, 1)
7712 if token == Editor.StartEditToken:
7713 self.__processStartEditCommand(argsString)
7714 elif token == Editor.CancelEditToken:
7715 self.shareConnected(False)
7716 elif token == Editor.EndEditToken:
7717 self.__processEndEditCommand(argsString)
7718 elif token == Editor.RequestSyncToken:
7719 self.__processRequestSyncCommand(argsString)
7720 elif token == Editor.SyncToken:
7721 self.__processSyncCommand(argsString)
7722
7723 def __processStartEditCommand(self, argsString):
7724 """
7725 Private slot to process a remote StartEdit command.
7726
7727 @param argsString string containing the command parameters (string)
7728 """
7729 if not self.__inSharedEdit and not self.__inRemoteSharedEdit:
7730 self.__inRemoteSharedEdit = True
7731 self.setReadOnly(True)
7732 self.__updateReadOnly()
7733 hashStr = str(
7734 QCryptographicHash.hash(
7735 Utilities.encode(self.text(), self.encoding)[0],
7736 QCryptographicHash.Sha1).toHex(),
7737 encoding="utf-8")
7738 if hashStr != argsString:
7739 # text is different to the remote site, request to sync it
7740 self.__isSyncing = True
7741 self.__send(Editor.RequestSyncToken, argsString)
7742
7743 def __calculateChanges(self, old, new):
7744 """
7745 Private method to determine change commands to convert old text into
7746 new text.
7747
7748 @param old old text (string)
7749 @param new new text (string)
7750 @return commands to change old into new (string)
7751 """
7752 oldL = old.splitlines()
7753 newL = new.splitlines()
7754 matcher = difflib.SequenceMatcher(None, oldL, newL)
7755
7756 formatStr = "@@{0} {1} {2} {3}"
7757 commands = []
7758 for token, i1, i2, j1, j2 in matcher.get_opcodes():
7759 if token == "insert":
7760 commands.append(formatStr.format("i", j1, j2 - j1, -1))
7761 commands.extend(newL[j1:j2])
7762 elif token == "delete":
7763 commands.append(formatStr.format("d", j1, i2 - i1, -1))
7764 elif token == "replace":
7765 commands.append(formatStr.format("r", j1, i2 - i1, j2 - j1))
7766 commands.extend(newL[j1:j2])
7767
7768 return "\n".join(commands) + "\n"
7769
7770 def __processEndEditCommand(self, argsString):
7771 """
7772 Private slot to process a remote EndEdit command.
7773
7774 @param argsString string containing the command parameters (string)
7775 """
7776 commands = argsString.splitlines()
7777 sep = self.getLineSeparator()
7778 cur = self.getCursorPosition()
7779
7780 self.setReadOnly(False)
7781 self.beginUndoAction()
7782 while commands:
7783 commandLine = commands.pop(0)
7784 if not commandLine.startswith("@@"):
7785 continue
7786
7787 args = commandLine.split()
7788 command = args.pop(0)
7789 pos, l1, l2 = [int(arg) for arg in args]
7790 if command == "@@i":
7791 txt = sep.join(commands[0:l1]) + sep
7792 self.insertAt(txt, pos, 0)
7793 del commands[0:l1]
7794 elif command == "@@d":
7795 self.setSelection(pos, 0, pos + l1, 0)
7796 self.removeSelectedText()
7797 elif command == "@@r":
7798 self.setSelection(pos, 0, pos + l1, 0)
7799 self.removeSelectedText()
7800 txt = sep.join(commands[0:l2]) + sep
7801 self.insertAt(txt, pos, 0)
7802 del commands[0:l2]
7803 self.endUndoAction()
7804 self.__updateReadOnly()
7805 self.__inRemoteSharedEdit = False
7806
7807 self.setCursorPosition(*cur)
7808
7809 def __processRequestSyncCommand(self, argsString):
7810 """
7811 Private slot to process a remote RequestSync command.
7812
7813 @param argsString string containing the command parameters (string)
7814 """
7815 if self.__inSharedEdit:
7816 hashStr = str(
7817 QCryptographicHash.hash(
7818 Utilities.encode(self.__savedText, self.encoding)[0],
7819 QCryptographicHash.Sha1).toHex(),
7820 encoding="utf-8")
7821
7822 if hashStr == argsString:
7823 self.__send(Editor.SyncToken, self.__savedText)
7824
7825 def __processSyncCommand(self, argsString):
7826 """
7827 Private slot to process a remote Sync command.
7828
7829 @param argsString string containing the command parameters (string)
7830 """
7831 if self.__isSyncing:
7832 cur = self.getCursorPosition()
7833
7834 self.setReadOnly(False)
7835 self.beginUndoAction()
7836 self.selectAll()
7837 self.removeSelectedText()
7838 self.insertAt(argsString, 0, 0)
7839 self.endUndoAction()
7840 self.setReadOnly(True)
7841
7842 self.setCursorPosition(*cur)
7843
7844 while self.__receivedWhileSyncing:
7845 command = self.__receivedWhileSyncing.pop(0)
7846 self.__dispatchCommand(command)
7847
7848 self.__isSyncing = False
7849
7850 #######################################################################
7851 ## Special search related methods
7852 #######################################################################
7853
7854 def searchCurrentWordForward(self):
7855 """
7856 Public slot to search the current word forward.
7857 """
7858 self.__searchCurrentWord(forward=True)
7859
7860 def searchCurrentWordBackward(self):
7861 """
7862 Public slot to search the current word backward.
7863 """
7864 self.__searchCurrentWord(forward=False)
7865
7866 def __searchCurrentWord(self, forward=True):
7867 """
7868 Private slot to search the next occurrence of the current word.
7869
7870 @param forward flag indicating the search direction (boolean)
7871 """
7872 self.hideFindIndicator()
7873 line, index = self.getCursorPosition()
7874 word = self.getCurrentWord()
7875 wordStart, wordEnd = self.getCurrentWordBoundaries()
7876 wordStartPos = self.positionFromLineIndex(line, wordStart)
7877 wordEndPos = self.positionFromLineIndex(line, wordEnd)
7878
7879 regExp = re.compile(r"\b{0}\b".format(word))
7880 if forward:
7881 startPos = wordEndPos
7882 else:
7883 startPos = wordStartPos
7884
7885 matches = [m for m in regExp.finditer(self.text())]
7886 if matches:
7887 if forward:
7888 matchesAfter = [m for m in matches if m.start() >= startPos]
7889 if matchesAfter:
7890 match = matchesAfter[0]
7891 else:
7892 # wrap around
7893 match = matches[0]
7894 else:
7895 matchesBefore = [m for m in matches if m.start() < startPos]
7896 if matchesBefore:
7897 match = matchesBefore[-1]
7898 else:
7899 # wrap around
7900 match = matches[-1]
7901 line, index = self.lineIndexFromPosition(match.start())
7902 self.setSelection(line, index + len(match.group(0)), line, index)
7903 self.showFindIndicator(line, index,
7904 line, index + len(match.group(0)))
7905
7906 #######################################################################
7907 ## Sort related methods
7908 #######################################################################
7909
7910 def sortLines(self):
7911 """
7912 Public slot to sort the lines spanned by a rectangular selection.
7913 """
7914 if not self.selectionIsRectangle():
7915 return
7916
7917 from .SortOptionsDialog import SortOptionsDialog
7918 dlg = SortOptionsDialog()
7919 if dlg.exec_() == QDialog.Accepted:
7920 ascending, alnum, caseSensitive = dlg.getData()
7921 origStartLine, origStartIndex, origEndLine, origEndIndex = \
7922 self.getRectangularSelection()
7923 # convert to upper-left to lower-right
7924 startLine = min(origStartLine, origEndLine)
7925 startIndex = min(origStartIndex, origEndIndex)
7926 endLine = max(origStartLine, origEndLine)
7927 endIndex = max(origStartIndex, origEndIndex)
7928
7929 # step 1: extract the text of the rectangular selection and
7930 # the lines
7931 selText = {}
7932 txtLines = {}
7933 for line in range(startLine, endLine + 1):
7934 txtLines[line] = self.text(line)
7935 txt = txtLines[line][startIndex:endIndex].strip()
7936 if not alnum:
7937 try:
7938 txt = float(txt)
7939 except ValueError:
7940 E5MessageBox.critical(
7941 self,
7942 self.tr("Sort Lines"),
7943 self.tr(
7944 """The selection contains illegal data for a"""
7945 """ numerical sort."""))
7946 return
7947
7948 if txt in selText:
7949 selText[txt].append(line)
7950 else:
7951 selText[txt] = [line]
7952
7953 # step 2: calculate the sort parameters
7954 reverse = not ascending
7955 if alnum and not caseSensitive:
7956 keyFun = str.lower
7957 else:
7958 keyFun = None
7959
7960 # step 3: sort the lines
7961 eol = self.getLineSeparator()
7962 lastWithEol = True
7963 newLines = []
7964 for txt in sorted(selText.keys(), key=keyFun, reverse=reverse):
7965 for line in selText[txt]:
7966 txt = txtLines[line]
7967 if not txt.endswith(eol):
7968 lastWithEol = False
7969 txt += eol
7970 newLines.append(txt)
7971 if not lastWithEol:
7972 newLines[-1] = newLines[-1][:-len(eol)]
7973
7974 # step 4: replace the lines by the sorted ones
7975 self.setSelection(startLine, 0, endLine + 1, 0)
7976 self.beginUndoAction()
7977 self.replaceSelectedText("".join(newLines))
7978 self.endUndoAction()
7979
7980 # step 5: reset the rectangular selection
7981 self.setRectangularSelection(origStartLine, origStartIndex,
7982 origEndLine, origEndIndex)
7983 self.selectionChanged.emit()
7984
7985 #######################################################################
7986 ## Mouse click handler related methods
7987 #######################################################################
7988
7989 def mouseReleaseEvent(self, evt):
7990 """
7991 Protected method calling a registered mouse click handler function.
7992
7993 @param evt event object
7994 @type QMouseEvent
7995 """
7996 modifiers = evt.modifiers()
7997 button = evt.button()
7998 key = (int(modifiers), int(button))
7999
8000 self.vm.eventFilter(self, evt)
8001 super(Editor, self).mouseReleaseEvent(evt)
8002
8003 if button != Qt.NoButton and \
8004 Preferences.getEditor("MouseClickHandlersEnabled") and \
8005 key in self.__mouseClickHandlers:
8006 evt.accept()
8007 self.__mouseClickHandlers[key][1](self)
8008
8009 def setMouseClickHandler(self, name, modifiers, button, function):
8010 """
8011 Public method to set a mouse click handler.
8012
8013 @param name name of the plug-in (or 'internal') setting this handler
8014 @type str
8015 @param modifiers keyboard modifiers of the handler
8016 @type Qt.KeyboardModifiers or int
8017 @param button mouse button of the handler
8018 @type Qt.MouseButton or int
8019 @param function handler function
8020 @type func
8021 @return flag indicating success
8022 @rtype bool
8023 """
8024 if int(button):
8025 key = (int(modifiers), int(button))
8026 if key in self.__mouseClickHandlers:
8027 E5MessageBox.warning(
8028 self,
8029 self.tr("Register Mouse Click Handler"),
8030 self.tr("""A mouse click handler for "{0}" was already"""
8031 """ registered by "{1}". Aborting request by"""
8032 """ "{2}"...""").format(
8033 MouseUtilities.MouseButtonModifier2String(
8034 modifiers, button),
8035 self.__mouseClickHandlers[key][0],
8036 name))
8037 return False
8038
8039 self.__mouseClickHandlers[key] = (name, function)
8040 return True
8041
8042 return False
8043
8044 def getMouseClickHandler(self, modifiers, button):
8045 """
8046 Public method to get a registered mouse click handler.
8047
8048 @param modifiers keyboard modifiers of the handler
8049 @type Qt.KeyboardModifiers
8050 @param button mouse button of the handler
8051 @type Qt.MouseButton
8052 @return plug-in name and registered function
8053 @rtype tuple of str and func
8054 """
8055 key = (int(modifiers), int(button))
8056 if key in self.__mouseClickHandlers:
8057 return self.__mouseClickHandlers[key]
8058 else:
8059 return ("", None)
8060
8061 def getMouseClickHandlers(self, name):
8062 """
8063 Public method to get all registered mouse click handlers of
8064 a plug-in.
8065
8066 @param name name of the plug-in
8067 @type str
8068 @return registered mouse click handlers as list of modifiers,
8069 mouse button and function
8070 @rtype list of tuple of (Qt.KeyboardModifiers, Qt.MouseButton,func)
8071 """
8072 lst = []
8073 for key, value in self.__mouseClickHandlers.items():
8074 if value[0] == name:
8075 lst.append((key[0], key[1], value[1]))
8076 return lst
8077
8078 def removeMouseClickHandler(self, modifiers, button):
8079 """
8080 Public method to un-registered a mouse click handler.
8081
8082 @param modifiers keyboard modifiers of the handler
8083 @type Qt.KeyboardModifiers
8084 @param button mouse button of the handler
8085 @type Qt.MouseButton
8086 """
8087 key = (int(modifiers), int(button))
8088 if key in self.__mouseClickHandlers:
8089 del self.__mouseClickHandlers[key]
8090
8091 def removeMouseClickHandlers(self, name):
8092 """
8093 Public method to un-registered all mouse click handlers of
8094 a plug-in.
8095
8096 @param name name of the plug-in
8097 @type str
8098 """
8099 keys = []
8100 for key in self.__mouseClickHandlers:
8101 if self.__mouseClickHandlers[key][0] == name:
8102 keys.append(key)
8103 for key in keys:
8104 del self.__mouseClickHandlers[key]
8105
8106 def __executeSelection(self):
8107 """
8108 Private slot to execute the selected text in the shell window.
8109 """
8110 txt = self.selectedText()
8111 e5App().getObject("Shell").executeLines(txt)
8112
8113 #######################################################################
8114 ## Methods implementing the interface to EditorConfig
8115 #######################################################################
8116
8117 def __loadEditorConfig(self, fileName=""):
8118 """
8119 Private method to load the EditorConfig properties.
8120
8121 @param fileName name of the file
8122 @type str
8123 """
8124 if not fileName:
8125 fileName = self.fileName
8126
8127 self.__editorConfig = self.__loadEditorConfigObject(fileName)
8128
8129 if fileName:
8130 self.__setTabAndIndent()
8131
8132 def __loadEditorConfigObject(self, fileName):
8133 """
8134 Private method to load the EditorConfig properties for the given
8135 file name.
8136
8137 @param fileName name of the file
8138 @type str
8139 @return EditorConfig dictionary
8140 @rtype dict
8141 """
8142 editorConfig = {}
8143
8144 if fileName:
8145 try:
8146 editorConfig = editorconfig.get_properties(fileName)
8147 except editorconfig.EditorConfigError:
8148 E5MessageBox.warning(
8149 self,
8150 self.tr("EditorConfig Properties"),
8151 self.tr("""<p>The EditorConfig properties for file"""
8152 """ <b>{0}</b> could not be loaded.</p>""")
8153 .format(fileName))
8154
8155 return editorConfig
8156
8157 def __getEditorConfig(self, option, nodefault=False, config=None):
8158 """
8159 Private method to get the requested option via EditorConfig.
8160
8161 If there is no EditorConfig defined, the equivalent built-in option
8162 will be used (Preferences.getEditor(). The option must be given as the
8163 Preferences option key. The mapping to the EditorConfig option name
8164 will be done within this method.
8165
8166 @param option Preferences option key
8167 @type str
8168 @param nodefault flag indicating to not get the default value from
8169 Preferences but return None instead
8170 @type bool
8171 @param config reference to an EditorConfig object or None
8172 @type dict
8173 @return value of requested setting or None if nothing was found and
8174 nodefault parameter was True
8175 @rtype any
8176 """
8177 if config is None:
8178 config = self.__editorConfig
8179
8180 if not config:
8181 if nodefault:
8182 return None
8183 else:
8184 return Preferences.getEditor(option)
8185
8186 try:
8187 if option == "EOLMode":
8188 value = config["end_of_line"]
8189 if value == "lf":
8190 value = QsciScintilla.EolUnix
8191 elif value == "crlf":
8192 value = QsciScintilla.EolWindows
8193 elif value == "cr":
8194 value = QsciScintilla.EolMac
8195 else:
8196 value = None
8197 elif option == "DefaultEncoding":
8198 value = config["charset"]
8199 elif option == "InsertFinalNewline":
8200 value = Utilities.toBool(config["insert_final_newline"])
8201 elif option == "StripTrailingWhitespace":
8202 value = Utilities.toBool(config["trim_trailing_whitespace"])
8203 elif option == "TabWidth":
8204 value = int(config["tab_width"])
8205 elif option == "IndentWidth":
8206 value = config["indent_size"]
8207 if value == "tab":
8208 value = self.__getEditorConfig("TabWidth", config=config)
8209 else:
8210 value = int(value)
8211 elif option == "TabForIndentation":
8212 value = config["indent_style"] == "tab"
8213 except KeyError:
8214 value = None
8215
8216 if value is None and not nodefault:
8217 # use Preferences in case of error
8218 value = Preferences.getEditor(option)
8219
8220 return value
8221
8222 def getEditorConfig(self, option):
8223 """
8224 Public method to get the requested option via EditorConfig.
8225
8226 @param option Preferences option key
8227 @type str
8228 @return value of requested setting
8229 @rtype any
8230 """
8231 return self.__getEditorConfig(option)
8232
8233 def mouseDoubleClickEvent(self, evt):
8234 """
8235 Protected method to handle mouse double click events.
8236
8237 @param evt reference to the mouse event
8238 @type QMouseEvent
8239 """
8240 super(Editor, self).mouseDoubleClickEvent(evt)
8241
8242 self.mouseDoubleClick.emit(evt.pos(), evt.buttons())

eric ide

mercurial