src/eric7/QScintilla/Editor.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9203
b201a2ffe174
child 9214
bd28e56047d7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/QScintilla/Editor.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,8936 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the editor component of the eric IDE.
+"""
+
+import bisect
+import collections
+import contextlib
+import difflib
+import os
+import pathlib
+import re
+
+import editorconfig
+
+from PyQt6.QtCore import (
+    pyqtSignal, pyqtSlot, Qt, QDir, QTimer, QModelIndex, QCryptographicHash,
+    QEvent, QDateTime, QPoint, QSize
+)
+from PyQt6.QtGui import QPalette, QFont, QPixmap, QPainter, QActionGroup
+from PyQt6.QtWidgets import (
+    QLineEdit, QDialog, QInputDialog, QApplication, QMenu
+)
+from PyQt6.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog
+from PyQt6.Qsci import QsciScintilla, QsciMacro, QsciStyledText
+
+from EricWidgets.EricApplication import ericApp
+from EricWidgets import EricFileDialog, EricMessageBox
+from EricGui.EricOverrideCursor import EricOverrideCursor
+
+from EricUtilities.EricCache import EricCache
+
+from .QsciScintillaCompat import QsciScintillaCompat
+from .EditorMarkerMap import EditorMarkerMap
+from .SpellChecker import SpellChecker
+
+from Globals import recentNameBreakpointConditions
+
+import Preferences
+import Utilities
+from Utilities import MouseUtilities
+
+import UI.PixmapCache
+
+from UI import PythonDisViewer
+
+EditorAutoCompletionListID = 1
+TemplateCompletionListID = 2
+ReferencesListID = 3
+
+ReferenceItem = collections.namedtuple(
+    "ReferenceItem", ["modulePath", "codeLine", "line", "column"])
+
+
+class Editor(QsciScintillaCompat):
+    """
+    Class implementing the editor component of the eric IDE.
+    
+    @signal modificationStatusChanged(bool, QsciScintillaCompat) emitted when
+        the modification status has changed
+    @signal undoAvailable(bool) emitted to signal the undo availability
+    @signal redoAvailable(bool) emitted to signal the redo availability
+    @signal cursorChanged(str, int, int) emitted when the cursor position
+        was changed
+    @signal cursorLineChanged(int) emitted when the cursor line was changed
+    @signal editorAboutToBeSaved(str) emitted before the editor is saved
+    @signal editorSaved(str) emitted after the editor has been saved
+    @signal editorRenamed(str) emitted after the editor got a new name
+        (i.e. after a 'Save As')
+    @signal captionChanged(str, QsciScintillaCompat) emitted when the caption
+        is updated. Typically due to a readOnly attribute change.
+    @signal breakpointToggled(QsciScintillaCompat) emitted when a breakpoint
+        is toggled
+    @signal bookmarkToggled(QsciScintillaCompat) emitted when a bookmark is
+        toggled
+    @signal syntaxerrorToggled(QsciScintillaCompat) emitted when a syntax error
+        was discovered
+    @signal autoCompletionAPIsAvailable(bool) emitted after the autocompletion
+        function has been configured
+    @signal coverageMarkersShown(bool) emitted after the coverage markers have
+        been shown or cleared
+    @signal taskMarkersUpdated(QsciScintillaCompat) emitted when the task
+        markers were updated
+    @signal changeMarkersUpdated(QsciScintillaCompat) emitted when the change
+        markers were updated
+    @signal showMenu(str, QMenu, QsciScintillaCompat) emitted when a menu is
+        about to be shown. The name of the menu, a reference to the menu and
+        a reference to the editor are given.
+    @signal languageChanged(str) emitted when the editors language was set. The
+        language is passed as a parameter.
+    @signal eolChanged(str) emitted when the editors eol type was set. The eol
+        string is passed as a parameter.
+    @signal encodingChanged(str) emitted when the editors encoding was set. The
+            encoding name is passed as a parameter.
+    @signal spellLanguageChanged(str) emitted when the editor spell check
+            language was set. The language is passed as a parameter.
+    @signal lastEditPositionAvailable() emitted when a last edit position is
+        available
+    @signal refreshed() emitted to signal a refresh of the editor contents
+    @signal settingsRead() emitted to signal, that the settings have been read
+        and set
+    @signal mouseDoubleClick(position, buttons) emitted to signal a mouse
+        double click somewhere in the editor area
+    """
+    modificationStatusChanged = pyqtSignal(bool, QsciScintillaCompat)
+    undoAvailable = pyqtSignal(bool)
+    redoAvailable = pyqtSignal(bool)
+    cursorChanged = pyqtSignal(str, int, int)
+    cursorLineChanged = pyqtSignal(int)
+    editorAboutToBeSaved = pyqtSignal(str)
+    editorSaved = pyqtSignal(str)
+    editorRenamed = pyqtSignal(str)
+    captionChanged = pyqtSignal(str, QsciScintillaCompat)
+    breakpointToggled = pyqtSignal(QsciScintillaCompat)
+    bookmarkToggled = pyqtSignal(QsciScintillaCompat)
+    syntaxerrorToggled = pyqtSignal(QsciScintillaCompat)
+    autoCompletionAPIsAvailable = pyqtSignal(bool)
+    coverageMarkersShown = pyqtSignal(bool)
+    taskMarkersUpdated = pyqtSignal(QsciScintillaCompat)
+    changeMarkersUpdated = pyqtSignal(QsciScintillaCompat)
+    showMenu = pyqtSignal(str, QMenu, QsciScintillaCompat)
+    languageChanged = pyqtSignal(str)
+    eolChanged = pyqtSignal(str)
+    encodingChanged = pyqtSignal(str)
+    spellLanguageChanged = pyqtSignal(str)
+    lastEditPositionAvailable = pyqtSignal()
+    refreshed = pyqtSignal()
+    settingsRead = pyqtSignal()
+    mouseDoubleClick = pyqtSignal(QPoint, int)
+    
+    WarningCode = 1
+    WarningStyle = 2
+    
+    # Autocompletion icon definitions
+    ClassID = 1
+    ClassProtectedID = 2
+    ClassPrivateID = 3
+    MethodID = 4
+    MethodProtectedID = 5
+    MethodPrivateID = 6
+    AttributeID = 7
+    AttributeProtectedID = 8
+    AttributePrivateID = 9
+    EnumID = 10
+    KeywordsID = 11
+    ModuleID = 12
+    
+    FromDocumentID = 99
+    
+    TemplateImageID = 100
+    
+    # Cooperation related definitions
+    Separator = "@@@"
+    
+    StartEditToken = "START_EDIT"
+    EndEditToken = "END_EDIT"
+    CancelEditToken = "CANCEL_EDIT"
+    RequestSyncToken = "REQUEST_SYNC"
+    SyncToken = "SYNC"
+    
+    VcsConflictMarkerLineRegExpList = (
+        r"""^<<<<<<< .*?$""",
+        r"""^\|\|\|\|\|\|\| .*?$""",
+        r"""^=======.*?$""",
+        r"""^>>>>>>> .*?$""",
+    )
+    
+    EncloseChars = {
+        '"': '"',
+        "'": "'",
+        "(": "()",
+        ")": "()",
+        "{": "{}",          # __IGNORE_WARNING_M613__
+        "}": "{}",          # __IGNORE_WARNING_M613__
+        "[": "[]",
+        "]": "[]",
+        "<": "<>",
+        ">": "<>",
+    }
+    
+    def __init__(self, dbs, fn="", vm=None,
+                 filetype="", editor=None, tv=None,
+                 parent=None):
+        """
+        Constructor
+        
+        @param dbs reference to the debug server object
+        @type DebugServer
+        @param fn name of the file to be opened. If it is None, a new (empty)
+            editor is opened.
+        @type str
+        @param vm reference to the view manager object
+        @type ViewManager
+        @param filetype type of the source file
+        @type str
+        @param editor reference to an Editor object, if this is a cloned view
+        @type Editor
+        @param tv reference to the task viewer object
+        @type TaskViewer
+        @param parent reference to the parent widget
+        @type QWidget
+        @exception OSError raised to indicate an issue accessing the file
+        """
+        super().__init__(parent)
+        self.setAttribute(Qt.WidgetAttribute.WA_KeyCompression)
+        self.setUtf8(True)
+        
+        self.enableMultiCursorSupport()
+        
+        self.dbs = dbs
+        self.taskViewer = tv
+        self.__setFileName(fn)
+        self.vm = vm
+        self.filetype = filetype
+        self.filetypeByFlag = False
+        self.noName = ""
+        self.project = ericApp().getObject("Project")
+        
+        # clear some variables
+        self.lastHighlight = None   # remember the last highlighted line
+        self.lastErrorMarker = None   # remember the last error line
+        self.lastCurrMarker = None   # remember the last current line
+        
+        self.breaks = {}
+        # key:   marker handle,
+        # value: (lineno, condition, temporary,
+        #         enabled, ignorecount)
+        self.bookmarks = []
+        # bookmarks are just a list of handles to the
+        # bookmark markers
+        self.syntaxerrors = {}
+        # key:   marker handle
+        # value: list of (error message, error index)
+        self.warnings = {}
+        # key:   marker handle
+        # value: list of (warning message, warning type)
+        self.notcoveredMarkers = []  # just a list of marker handles
+        self.showingNotcoveredMarkers = False
+        
+        self.lexer_ = None
+        self.apiLanguage = ''
+        
+        self.__loadEditorConfig()
+        
+        self.__lexerReset = False
+        self.completer = None
+        self.encoding = self.__getEditorConfig("DefaultEncoding")
+        self.lastModified = 0
+        self.line = -1
+        self.inReopenPrompt = False
+        # true if the prompt to reload a changed source is present
+        self.inFileRenamed = False
+        # true if we are propagating a rename action
+        self.inLanguageChanged = False
+        # true if we are propagating a language change
+        self.inEolChanged = False
+        # true if we are propagating an eol change
+        self.inEncodingChanged = False
+        # true if we are propagating an encoding change
+        self.inDragDrop = False
+        # true if we are in drop mode
+        self.inLinesChanged = False
+        # true if we are propagating a lines changed event
+        self.__hasTaskMarkers = False
+        # no task markers present
+            
+        self.macros = {}    # list of defined macros
+        self.curMacro = None
+        self.recording = False
+        
+        self.acAPI = False
+        
+        self.__lastEditPosition = None
+        self.__annotationLines = 0
+        
+        self.__docstringGenerator = None
+        
+        # list of clones
+        self.__clones = []
+        
+        # clear QScintilla defined keyboard commands
+        # we do our own handling through the view manager
+        self.clearAlternateKeys()
+        self.clearKeys()
+        
+        self.__markerMap = EditorMarkerMap(self)
+        
+        # initialize the mark occurrences timer
+        self.__markOccurrencesTimer = QTimer(self)
+        self.__markOccurrencesTimer.setSingleShot(True)
+        self.__markOccurrencesTimer.setInterval(
+            Preferences.getEditor("MarkOccurrencesTimeout"))
+        self.__markOccurrencesTimer.timeout.connect(self.__markOccurrences)
+        self.__markedText = ""
+        self.__searchIndicatorLines = []
+        
+        # initialize some spellchecking stuff
+        self.spell = None
+        self.lastLine = 0
+        self.lastIndex = 0
+        self.__inSpellLanguageChanged = False
+        
+        # initialize some cooperation stuff
+        self.__isSyncing = False
+        self.__receivedWhileSyncing = []
+        self.__savedText = ""
+        self.__inSharedEdit = False
+        self.__isShared = False
+        self.__inRemoteSharedEdit = False
+        
+        # connect signals before loading the text
+        self.modificationChanged.connect(self.__modificationChanged)
+        self.cursorPositionChanged.connect(self.__cursorPositionChanged)
+        self.modificationAttempted.connect(self.__modificationReadOnly)
+        
+        # define the margins markers
+        self.__changeMarkerSaved = self.markerDefine(
+            self.__createChangeMarkerPixmap(
+                "OnlineChangeTraceMarkerSaved"))
+        self.__changeMarkerUnsaved = self.markerDefine(
+            self.__createChangeMarkerPixmap(
+                "OnlineChangeTraceMarkerUnsaved"))
+        self.breakpoint = self.markerDefine(
+            UI.PixmapCache.getPixmap("break"))
+        self.cbreakpoint = self.markerDefine(
+            UI.PixmapCache.getPixmap("cBreak"))
+        self.tbreakpoint = self.markerDefine(
+            UI.PixmapCache.getPixmap("tBreak"))
+        self.tcbreakpoint = self.markerDefine(
+            UI.PixmapCache.getPixmap("tCBreak"))
+        self.dbreakpoint = self.markerDefine(
+            UI.PixmapCache.getPixmap("breakDisabled"))
+        self.bookmark = self.markerDefine(
+            UI.PixmapCache.getPixmap("bookmark16"))
+        self.syntaxerror = self.markerDefine(
+            UI.PixmapCache.getPixmap("syntaxError"))
+        self.notcovered = self.markerDefine(
+            UI.PixmapCache.getPixmap("notcovered"))
+        self.taskmarker = self.markerDefine(
+            UI.PixmapCache.getPixmap("task"))
+        self.warning = self.markerDefine(
+            UI.PixmapCache.getPixmap("warning"))
+        
+        # define the line markers
+        if Preferences.getEditor("LineMarkersBackground"):
+            self.currentline = self.markerDefine(
+                QsciScintilla.MarkerSymbol.Background)
+            self.errorline = self.markerDefine(
+                QsciScintilla.MarkerSymbol.Background)
+            self.__setLineMarkerColours()
+        else:
+            self.currentline = self.markerDefine(
+                UI.PixmapCache.getPixmap("currentLineMarker"))
+            self.errorline = self.markerDefine(
+                UI.PixmapCache.getPixmap("errorLineMarker"))
+        
+        self.breakpointMask = (
+            (1 << self.breakpoint) |
+            (1 << self.cbreakpoint) |
+            (1 << self.tbreakpoint) |
+            (1 << self.tcbreakpoint) |
+            (1 << self.dbreakpoint)
+        )
+        
+        self.changeMarkersMask = (
+            (1 << self.__changeMarkerSaved) |
+            (1 << self.__changeMarkerUnsaved)
+        )
+        
+        # configure the margins
+        self.__setMarginsDisplay()
+        self.linesChanged.connect(self.__resizeLinenoMargin)
+        
+        self.marginClicked.connect(self.__marginClicked)
+        
+        # set the eol mode
+        self.__setEolMode()
+        
+        # set the text display
+        self.__setTextDisplay()
+        
+        # initialize the online syntax check timer
+        try:
+            self.syntaxCheckService = ericApp().getObject('SyntaxCheckService')
+            self.syntaxCheckService.syntaxChecked.connect(
+                self.__processSyntaxCheckResult)
+            self.syntaxCheckService.error.connect(
+                self.__processSyntaxCheckError)
+            self.__initOnlineSyntaxCheck()
+        except KeyError:
+            self.syntaxCheckService = None
+        
+        self.isResourcesFile = False
+        if editor is None:
+            if self.fileName:
+                if (
+                    (pathlib.Path(self.fileName).stat().st_size // 1024) >
+                        Preferences.getEditor("WarnFilesize")
+                ):
+                    res = EricMessageBox.yesNo(
+                        self,
+                        self.tr("Open File"),
+                        self.tr("""<p>The size of the file <b>{0}</b>"""
+                                """ is <b>{1} KB</b>."""
+                                """ Do you really want to load it?</p>""")
+                        .format(
+                            self.fileName,
+                            pathlib.Path(self.fileName).stat().st_size // 1024
+                        ),
+                        icon=EricMessageBox.Warning)
+                    if not res:
+                        raise OSError()
+                self.readFile(self.fileName, True)
+                self.__bindLexer(self.fileName)
+                self.__bindCompleter(self.fileName)
+                self.checkSyntax()
+                self.isResourcesFile = self.fileName.endswith(".qrc")
+                
+                self.__convertTabs()
+                
+                self.recolor()
+        else:
+            # clone the given editor
+            self.setDocument(editor.document())
+            self.breaks = editor.breaks
+            self.bookmarks = editor.bookmarks
+            self.syntaxerrors = editor.syntaxerrors
+            self.notcoveredMarkers = editor.notcoveredMarkers
+            self.showingNotcoveredMarkers = editor.showingNotcoveredMarkers
+            self.isResourcesFile = editor.isResourcesFile
+            self.lastModified = editor.lastModified
+            
+            self.addClone(editor)
+            editor.addClone(self)
+        
+        self.gotoLine(1)
+        
+        # connect the mouse hover signals
+        self.SCN_DWELLSTART.connect(self.__showMouseHoverHelp)
+        self.SCN_DWELLEND.connect(self.__cancelMouseHoverHelp)
+        self.__mouseHoverHelp = None
+        self.__showingMouseHoverHelp = False
+        
+        # set the text display again
+        self.__setTextDisplay()
+        
+        # set the auto-completion function
+        self.__acContext = True
+        self.__acText = ""
+        self.__acCompletions = set()
+        self.__acCompletionsFinished = 0
+        self.__acCache = EricCache(
+            size=Preferences.getEditor("AutoCompletionCacheSize"))
+        self.__acCache.setMaximumCacheTime(
+            Preferences.getEditor("AutoCompletionCacheTime"))
+        self.__acCacheEnabled = Preferences.getEditor(
+            "AutoCompletionCacheEnabled")
+        self.__acTimer = QTimer(self)
+        self.__acTimer.setSingleShot(True)
+        self.__acTimer.setInterval(
+            Preferences.getEditor("AutoCompletionTimeout"))
+        self.__acTimer.timeout.connect(self.__autoComplete)
+        
+        self.__acWatchdog = QTimer(self)
+        self.__acWatchdog.setSingleShot(True)
+        self.__acWatchdog.setInterval(
+            Preferences.getEditor("AutoCompletionWatchdogTime"))
+        self.__acWatchdog.timeout.connect(self.autoCompleteQScintilla)
+        
+        self.userListActivated.connect(self.__completionListSelected)
+        self.SCN_CHARADDED.connect(self.__charAdded)
+        self.SCN_AUTOCCANCELLED.connect(self.__autocompletionCancelled)
+        
+        self.__completionListHookFunctions = {}
+        self.__completionListAsyncHookFunctions = {}
+        self.__setAutoCompletion()
+        
+        # set the call-tips function
+        self.__ctHookFunctions = {}
+        self.__setCallTips()
+        
+        # set the mouse click handlers (fired on mouse release)
+        self.__mouseClickHandlers = {}
+        # dictionary with tuple of keyboard modifier and mouse button as key
+        # and tuple of plug-in name and function as value
+        
+        sh = self.sizeHint()
+        if sh.height() < 300:
+            sh.setHeight(300)
+        self.resize(sh)
+        
+        # Make sure tabbing through a QWorkspace works.
+        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        
+        self.__updateReadOnly(True)
+        
+        self.setWhatsThis(self.tr(
+            """<b>A Source Editor Window</b>"""
+            """<p>This window is used to display and edit a source file."""
+            """  You can open as many of these as you like. The name of the"""
+            """ file is displayed in the window's titlebar.</p>"""
+            """<p>In order to set breakpoints just click in the space"""
+            """ between the line numbers and the fold markers. Via the"""
+            """ context menu of the margins they may be edited.</p>"""
+            """<p>In order to set bookmarks just Shift click in the space"""
+            """ between the line numbers and the fold markers.</p>"""
+            """<p>These actions can be reversed via the context menu.</p>"""
+            """<p>Ctrl clicking on a syntax error marker shows some info"""
+            """ about this error.</p>"""
+        ))
+        
+        # Set the editors size, if it is too big for the view manager.
+        if self.vm is not None:
+            req = self.size()
+            bnd = req.boundedTo(self.vm.size())
+            
+            if bnd.width() < req.width() or bnd.height() < req.height():
+                self.resize(bnd)
+            
+        # set the autosave flag
+        self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
+        self.autosaveManuallyDisabled = False
+        
+        # code coverage related attributes
+        self.__coverageFile = ""
+        
+        self.__initContextMenu()
+        self.__initContextMenuMargins()
+        
+        self.__checkEol()
+        if editor is None:
+            self.__checkLanguage()
+            self.__checkEncoding()
+            self.__checkSpellLanguage()
+        else:
+            # it's a clone
+            self.__languageChanged(editor.apiLanguage, propagate=False)
+            self.__encodingChanged(editor.encoding, propagate=False)
+            self.__spellLanguageChanged(editor.getSpellingLanguage(),
+                                        propagate=False)
+            # link the warnings to the original editor
+            self.warnings = editor.warnings
+        
+        self.setAcceptDrops(True)
+        
+        # breakpoint handling
+        self.breakpointModel = self.dbs.getBreakPointModel()
+        self.__restoreBreakpoints()
+        self.breakpointModel.rowsAboutToBeRemoved.connect(
+            self.__deleteBreakPoints)
+        self.breakpointModel.dataAboutToBeChanged.connect(
+            self.__breakPointDataAboutToBeChanged)
+        self.breakpointModel.dataChanged.connect(
+            self.__changeBreakPoints)
+        self.breakpointModel.rowsInserted.connect(
+            self.__addBreakPoints)
+        self.SCN_MODIFIED.connect(self.__modified)
+        
+        # establish connection to some ViewManager action groups
+        self.addActions(self.vm.editorActGrp.actions())
+        self.addActions(self.vm.editActGrp.actions())
+        self.addActions(self.vm.copyActGrp.actions())
+        self.addActions(self.vm.viewActGrp.actions())
+        
+        # register images to be shown in autocompletion lists
+        self.__registerImages()
+        
+        # connect signals after loading the text
+        self.textChanged.connect(self.__textChanged)
+        
+        # initialize the online change trace timer
+        self.__initOnlineChangeTrace()
+        
+        if (
+            self.fileName and
+            self.project.isOpen() and
+            self.project.isProjectSource(self.fileName)
+        ):
+            self.project.projectPropertiesChanged.connect(
+                self.__projectPropertiesChanged)
+        
+        self.grabGesture(Qt.GestureType.PinchGesture)
+        
+        self.SCN_ZOOM.connect(self.__markerMap.update)
+        self.__markerMap.update()
+    
+    def __setFileName(self, name):
+        """
+        Private method to set the file name of the current file.
+        
+        @param name name of the current file
+        @type str
+        """
+        self.fileName = name
+        
+        if self.fileName:
+            self.__fileNameExtension = (
+                os.path.splitext(self.fileName)[1][1:].lower()
+            )
+        else:
+            self.__fileNameExtension = ""
+    
+    def __registerImages(self):
+        """
+        Private method to register images for autocompletion lists.
+        """
+        # finale size of the completion images
+        imageSize = QSize(22, 22)
+        
+        self.registerImage(
+            self.ClassID,
+            UI.PixmapCache.getPixmap("class", imageSize))
+        self.registerImage(
+            self.ClassProtectedID,
+            UI.PixmapCache.getPixmap("class_protected", imageSize))
+        self.registerImage(
+            self.ClassPrivateID,
+            UI.PixmapCache.getPixmap("class_private", imageSize))
+        self.registerImage(
+            self.MethodID,
+            UI.PixmapCache.getPixmap("method", imageSize))
+        self.registerImage(
+            self.MethodProtectedID,
+            UI.PixmapCache.getPixmap("method_protected", imageSize))
+        self.registerImage(
+            self.MethodPrivateID,
+            UI.PixmapCache.getPixmap("method_private", imageSize))
+        self.registerImage(
+            self.AttributeID,
+            UI.PixmapCache.getPixmap("attribute", imageSize))
+        self.registerImage(
+            self.AttributeProtectedID,
+            UI.PixmapCache.getPixmap("attribute_protected", imageSize))
+        self.registerImage(
+            self.AttributePrivateID,
+            UI.PixmapCache.getPixmap("attribute_private", imageSize))
+        self.registerImage(
+            self.EnumID,
+            UI.PixmapCache.getPixmap("enum", imageSize))
+        self.registerImage(
+            self.KeywordsID,
+            UI.PixmapCache.getPixmap("keywords", imageSize))
+        self.registerImage(
+            self.ModuleID,
+            UI.PixmapCache.getPixmap("module", imageSize))
+        
+        self.registerImage(
+            self.FromDocumentID,
+            UI.PixmapCache.getPixmap("editor", imageSize))
+        
+        self.registerImage(
+            self.TemplateImageID,
+            UI.PixmapCache.getPixmap("templateViewer", imageSize))
+    
+    def addClone(self, editor):
+        """
+        Public method to add a clone to our list.
+        
+        @param editor reference to the cloned editor
+        @type Editor
+        """
+        self.__clones.append(editor)
+        
+        editor.editorRenamed.connect(self.fileRenamed)
+        editor.languageChanged.connect(self.languageChanged)
+        editor.eolChanged.connect(self.__eolChanged)
+        editor.encodingChanged.connect(self.__encodingChanged)
+        editor.spellLanguageChanged.connect(self.__spellLanguageChanged)
+        
+    def removeClone(self, editor):
+        """
+        Public method to remove a clone from our list.
+        
+        @param editor reference to the cloned editor
+        @type Editor
+        """
+        if editor in self.__clones:
+            editor.editorRenamed.disconnect(self.fileRenamed)
+            editor.languageChanged.disconnect(self.languageChanged)
+            editor.eolChanged.disconnect(self.__eolChanged)
+            editor.encodingChanged.disconnect(self.__encodingChanged)
+            editor.spellLanguageChanged.disconnect(self.__spellLanguageChanged)
+            self.__clones.remove(editor)
+        
+    def isClone(self, editor):
+        """
+        Public method to test, if the given editor is a clone.
+        
+        @param editor reference to the cloned editor
+        @type Editor
+        @return flag indicating a clone
+        @rtype bool
+        """
+        return editor in self.__clones
+        
+    def __bindName(self, line0):
+        """
+        Private method to generate a dummy filename for binding a lexer.
+        
+        @param line0 first line of text to use in the generation process
+            (string)
+        @return dummy file name to be used for binding a lexer (string)
+        """
+        bindName = ""
+        line0 = line0.lower()
+        
+        # check first line if it does not start with #!
+        if line0.startswith(("<html", "<!doctype html", "<?php")):
+            bindName = "dummy.html"
+        elif line0.startswith(("<?xml", "<!doctype")):
+            bindName = "dummy.xml"
+        elif line0.startswith("index: "):
+            bindName = "dummy.diff"
+        elif line0.startswith("\\documentclass"):
+            bindName = "dummy.tex"
+        
+        if not bindName and self.filetype:
+            # check filetype
+            from . import Lexers
+            supportedLanguages = Lexers.getSupportedLanguages()
+            if self.filetype in supportedLanguages:
+                bindName = supportedLanguages[self.filetype][1]
+            elif self.filetype in ["Python", "Python3", "MicroPython"]:
+                bindName = "dummy.py"
+        
+        if not bindName and line0.startswith("#!"):
+            # #! marker detection
+            if (
+                "python3" in line0 or
+                "python" in line0 or
+                "pypy3" in line0 or
+                "pypy" in line0
+            ):
+                bindName = "dummy.py"
+                self.filetype = "Python3"
+            elif ("/bash" in line0 or "/sh" in line0):
+                bindName = "dummy.sh"
+            elif "ruby" in line0:
+                bindName = "dummy.rb"
+                self.filetype = "Ruby"
+            elif "perl" in line0:
+                bindName = "dummy.pl"
+            elif "lua" in line0:
+                bindName = "dummy.lua"
+            elif "dmd" in line0:
+                bindName = "dummy.d"
+                self.filetype = "D"
+        
+        if not bindName:
+            # mode line detection: -*- mode: python -*-
+            match = re.search(r"mode[:=]\s*([-\w_.]+)", line0)
+            if match:
+                mode = match.group(1).lower()
+                if mode in ["python3", "pypy3"]:
+                    bindName = "dummy.py"
+                    self.filetype = "Python3"
+                elif mode == "ruby":
+                    bindName = "dummy.rb"
+                    self.filetype = "Ruby"
+                elif mode == "perl":
+                    bindName = "dummy.pl"
+                elif mode == "lua":
+                    bindName = "dummy.lua"
+                elif mode in ["dmd", "d"]:
+                    bindName = "dummy.d"
+                    self.filetype = "D"
+        
+        if not bindName:
+            bindName = self.fileName
+        
+        return bindName
+        
+    def getMenu(self, menuName):
+        """
+        Public method to get a reference to the main context menu or a submenu.
+        
+        @param menuName name of the menu (string)
+        @return reference to the requested menu (QMenu) or None
+        """
+        try:
+            return self.__menus[menuName]
+        except KeyError:
+            return None
+        
+    def hasMiniMenu(self):
+        """
+        Public method to check the miniMenu flag.
+        
+        @return flag indicating a minimized context menu (boolean)
+        """
+        return self.miniMenu
+        
+    def __initContextMenu(self):
+        """
+        Private method used to setup the context menu.
+        """
+        self.miniMenu = Preferences.getEditor("MiniContextMenu")
+        
+        self.menuActs = {}
+        self.menu = QMenu()
+        self.__menus = {
+            "Main": self.menu,
+        }
+        
+        self.languagesMenu = self.__initContextMenuLanguages()
+        self.__menus["Languages"] = self.languagesMenu
+        if self.isResourcesFile:
+            self.resourcesMenu = self.__initContextMenuResources()
+            self.__menus["Resources"] = self.resourcesMenu
+        else:
+            self.checksMenu = self.__initContextMenuChecks()
+            self.menuShow = self.__initContextMenuShow()
+            self.graphicsMenu = self.__initContextMenuGraphics()
+            self.autocompletionMenu = self.__initContextMenuAutocompletion()
+            self.__menus["Checks"] = self.checksMenu
+            self.__menus["Show"] = self.menuShow
+            self.__menus["Graphics"] = self.graphicsMenu
+            self.__menus["Autocompletion"] = self.autocompletionMenu
+        self.toolsMenu = self.__initContextMenuTools()
+        self.__menus["Tools"] = self.toolsMenu
+        self.eolMenu = self.__initContextMenuEol()
+        self.__menus["Eol"] = self.eolMenu
+        self.encodingsMenu = self.__initContextMenuEncodings()
+        self.__menus["Encodings"] = self.encodingsMenu
+        self.spellCheckMenu = self.__initContextMenuSpellCheck()
+        self.__menus["SpellCheck"] = self.spellCheckMenu
+        
+        self.menuActs["Undo"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("editUndo"),
+            self.tr('Undo'), self.undo)
+        self.menuActs["Redo"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("editRedo"),
+            self.tr('Redo'), self.redo)
+        self.menuActs["Revert"] = self.menu.addAction(
+            self.tr("Revert to last saved state"),
+            self.revertToUnmodified)
+        self.menu.addSeparator()
+        self.menuActs["Cut"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("editCut"),
+            self.tr('Cut'), self.cut)
+        self.menuActs["Copy"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("editCopy"),
+            self.tr('Copy'), self.copy)
+        self.menuActs["Paste"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("editPaste"),
+            self.tr('Paste'), self.paste)
+        if not self.miniMenu:
+            self.menu.addSeparator()
+            self.menu.addAction(
+                UI.PixmapCache.getIcon("editIndent"),
+                self.tr('Indent'), self.indentLineOrSelection)
+            self.menu.addAction(
+                UI.PixmapCache.getIcon("editUnindent"),
+                self.tr('Unindent'), self.unindentLineOrSelection)
+            self.menuActs["Comment"] = self.menu.addAction(
+                UI.PixmapCache.getIcon("editComment"),
+                self.tr('Comment'), self.commentLineOrSelection)
+            self.menuActs["Uncomment"] = self.menu.addAction(
+                UI.PixmapCache.getIcon("editUncomment"),
+                self.tr('Uncomment'), self.uncommentLineOrSelection)
+            self.menu.addSeparator()
+            self.menuActs["Docstring"] = self.menu.addAction(
+                self.tr("Generate Docstring"),
+                self.__insertDocstring)
+            self.menu.addSeparator()
+            self.menu.addAction(
+                self.tr('Select to brace'), self.selectToMatchingBrace)
+            self.menu.addAction(self.tr('Select all'), self.__selectAll)
+            self.menu.addAction(
+                self.tr('Deselect all'), self.__deselectAll)
+            self.menuActs["ExecuteSelection"] = self.menu.addAction(
+                self.tr("Execute Selection In Console"),
+                self.__executeSelection)
+        else:
+            self.menuActs["ExecuteSelection"] = None
+        self.menu.addSeparator()
+        self.menu.addMenu(self.spellCheckMenu)
+        self.menu.addSeparator()
+        self.menuActs["Languages"] = self.menu.addMenu(self.languagesMenu)
+        self.menuActs["Encodings"] = self.menu.addMenu(self.encodingsMenu)
+        self.menuActs["Eol"] = self.menu.addMenu(self.eolMenu)
+        self.menu.addSeparator()
+        self.menuActs["MonospacedFont"] = self.menu.addAction(
+            self.tr("Use Monospaced Font"),
+            self.handleMonospacedEnable)
+        self.menuActs["MonospacedFont"].setCheckable(True)
+        self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
+        self.menuActs["AutosaveEnable"] = self.menu.addAction(
+            self.tr("Autosave enabled"), self.__autosaveEnable)
+        self.menuActs["AutosaveEnable"].setCheckable(True)
+        self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled)
+        self.menuActs["TypingAidsEnabled"] = self.menu.addAction(
+            self.tr("Typing aids enabled"), self.__toggleTypingAids)
+        self.menuActs["TypingAidsEnabled"].setCheckable(True)
+        self.menuActs["TypingAidsEnabled"].setEnabled(
+            self.completer is not None)
+        self.menuActs["TypingAidsEnabled"].setChecked(
+            self.completer is not None and self.completer.isEnabled())
+        self.menuActs["AutoCompletionEnable"] = self.menu.addAction(
+            self.tr("Automatic Completion enabled"),
+            self.__toggleAutoCompletionEnable)
+        self.menuActs["AutoCompletionEnable"].setCheckable(True)
+        self.menuActs["AutoCompletionEnable"].setChecked(
+            self.autoCompletionThreshold() != -1)
+        if not self.isResourcesFile:
+            self.menu.addMenu(self.autocompletionMenu)
+            self.menuActs["calltip"] = self.menu.addAction(
+                self.tr('Calltip'), self.callTip)
+            self.menuActs["codeInfo"] = self.menu.addAction(
+                self.tr('Code Info'), self.__showCodeInfo)
+        self.menu.addSeparator()
+        if self.isResourcesFile:
+            self.menu.addMenu(self.resourcesMenu)
+        else:
+            self.menuActs["Check"] = self.menu.addMenu(self.checksMenu)
+            self.menu.addSeparator()
+            self.menuActs["Show"] = self.menu.addMenu(self.menuShow)
+            self.menu.addSeparator()
+            self.menuActs["Diagrams"] = self.menu.addMenu(self.graphicsMenu)
+        self.menu.addSeparator()
+        self.menuActs["Tools"] = self.menu.addMenu(self.toolsMenu)
+        self.menu.addSeparator()
+        self.menu.addAction(
+            UI.PixmapCache.getIcon("documentNewView"),
+            self.tr('New Document View'), self.__newView)
+        self.menuActs["NewSplit"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("splitVertical"),
+            self.tr('New Document View (with new split)'),
+            self.__newViewNewSplit)
+        self.menuActs["NewSplit"].setEnabled(self.vm.canSplit())
+        self.menu.addSeparator()
+        self.reopenEncodingMenu = self.__initContextMenuReopenWithEncoding()
+        self.menuActs["Reopen"] = self.menu.addMenu(self.reopenEncodingMenu)
+        self.menuActs["Save"] = self.menu.addAction(
+            UI.PixmapCache.getIcon("fileSave"),
+            self.tr('Save'), self.__contextSave)
+        self.menu.addAction(
+            UI.PixmapCache.getIcon("fileSaveAs"),
+            self.tr('Save As...'), self.__contextSaveAs)
+        self.menu.addAction(
+            UI.PixmapCache.getIcon("fileSaveCopy"),
+            self.tr('Save Copy...'), self.__contextSaveCopy)
+        
+        self.menu.aboutToShow.connect(self.__showContextMenu)
+        
+        self.spellingMenu = QMenu()
+        self.__menus["Spelling"] = self.spellingMenu
+        
+        self.spellingMenu.aboutToShow.connect(self.__showContextMenuSpelling)
+        self.spellingMenu.triggered.connect(
+            self.__contextMenuSpellingTriggered)
+
+    def __initContextMenuAutocompletion(self):
+        """
+        Private method used to setup the Checks context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr('Complete'))
+        
+        self.menuActs["acDynamic"] = menu.addAction(
+            self.tr('Complete'), self.autoComplete)
+        menu.addSeparator()
+        self.menuActs["acClearCache"] = menu.addAction(
+            self.tr("Clear Completions Cache"), self.__clearCompletionsCache)
+        menu.addSeparator()
+        menu.addAction(
+            self.tr('Complete from Document'), self.autoCompleteFromDocument)
+        self.menuActs["acAPI"] = menu.addAction(
+            self.tr('Complete from APIs'), self.autoCompleteFromAPIs)
+        self.menuActs["acAPIDocument"] = menu.addAction(
+            self.tr('Complete from Document and APIs'),
+            self.autoCompleteFromAll)
+        
+        menu.aboutToShow.connect(self.__showContextMenuAutocompletion)
+        
+        return menu
+    
+    def __initContextMenuChecks(self):
+        """
+        Private method used to setup the Checks context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr('Check'))
+        menu.aboutToShow.connect(self.__showContextMenuChecks)
+        return menu
+
+    def __initContextMenuTools(self):
+        """
+        Private method used to setup the Tools context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr('Tools'))
+        menu.aboutToShow.connect(self.__showContextMenuTools)
+        return menu
+
+    def __initContextMenuShow(self):
+        """
+        Private method used to setup the Show context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr('Show'))
+        
+        menu.addAction(self.tr('Code metrics...'), self.__showCodeMetrics)
+        self.coverageMenuAct = menu.addAction(
+            self.tr('Code coverage...'), self.__showCodeCoverage)
+        self.coverageShowAnnotationMenuAct = menu.addAction(
+            self.tr('Show code coverage annotations'),
+            self.codeCoverageShowAnnotations)
+        self.coverageHideAnnotationMenuAct = menu.addAction(
+            self.tr('Hide code coverage annotations'),
+            self.__codeCoverageHideAnnotations)
+        self.profileMenuAct = menu.addAction(
+            self.tr('Profile data...'), self.__showProfileData)
+        
+        menu.aboutToShow.connect(self.__showContextMenuShow)
+        
+        return menu
+        
+    def __initContextMenuGraphics(self):
+        """
+        Private method used to setup the diagrams context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr('Diagrams'))
+        
+        menu.addAction(
+            self.tr('Class Diagram...'), self.__showClassDiagram)
+        menu.addAction(
+            self.tr('Package Diagram...'), self.__showPackageDiagram)
+        menu.addAction(
+            self.tr('Imports Diagram...'), self.__showImportsDiagram)
+        self.applicationDiagramMenuAct = menu.addAction(
+            self.tr('Application Diagram...'),
+            self.__showApplicationDiagram)
+        menu.addSeparator()
+        menu.addAction(
+            UI.PixmapCache.getIcon("open"),
+            self.tr("Load Diagram..."), self.__loadDiagram)
+        
+        menu.aboutToShow.connect(self.__showContextMenuGraphics)
+        
+        return menu
+
+    def __initContextMenuLanguages(self):
+        """
+        Private method used to setup the Languages context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr("Languages"))
+        
+        self.languagesActGrp = QActionGroup(self)
+        self.noLanguageAct = menu.addAction(
+            UI.PixmapCache.getIcon("fileText"),
+            self.tr("Text"))
+        self.noLanguageAct.setCheckable(True)
+        self.noLanguageAct.setData("None")
+        self.languagesActGrp.addAction(self.noLanguageAct)
+        menu.addSeparator()
+        
+        from . import Lexers
+        self.supportedLanguages = {}
+        supportedLanguages = Lexers.getSupportedLanguages()
+        languages = sorted(supportedLanguages.keys())
+        for language in languages:
+            if language != "Guessed":
+                self.supportedLanguages[language] = (
+                    supportedLanguages[language][:2]
+                )
+                act = menu.addAction(
+                    UI.PixmapCache.getIcon(supportedLanguages[language][2]),
+                    self.supportedLanguages[language][0])
+                act.setCheckable(True)
+                act.setData(language)
+                self.supportedLanguages[language].append(act)
+                self.languagesActGrp.addAction(act)
+        
+        menu.addSeparator()
+        self.pygmentsAct = menu.addAction(self.tr("Guessed"))
+        self.pygmentsAct.setCheckable(True)
+        self.pygmentsAct.setData("Guessed")
+        self.languagesActGrp.addAction(self.pygmentsAct)
+        self.pygmentsSelAct = menu.addAction(self.tr("Alternatives"))
+        self.pygmentsSelAct.setData("Alternatives")
+        
+        menu.triggered.connect(self.__languageMenuTriggered)
+        menu.aboutToShow.connect(self.__showContextMenuLanguages)
+        
+        return menu
+        
+    def __initContextMenuEncodings(self):
+        """
+        Private method used to setup the Encodings context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        self.supportedEncodings = {}
+        
+        menu = QMenu(self.tr("Encodings"))
+        
+        self.encodingsActGrp = QActionGroup(self)
+        
+        for encoding in sorted(Utilities.supportedCodecs):
+            act = menu.addAction(encoding)
+            act.setCheckable(True)
+            act.setData(encoding)
+            self.supportedEncodings[encoding] = act
+            self.encodingsActGrp.addAction(act)
+        
+        menu.triggered.connect(self.__encodingsMenuTriggered)
+        menu.aboutToShow.connect(self.__showContextMenuEncodings)
+        
+        return menu
+        
+    def __initContextMenuReopenWithEncoding(self):
+        """
+        Private method used to setup the Reopen With Encoding context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr("Re-Open With Encoding"))
+        menu.setIcon(UI.PixmapCache.getIcon("open"))
+        
+        for encoding in sorted(Utilities.supportedCodecs):
+            act = menu.addAction(encoding)
+            act.setData(encoding)
+        
+        menu.triggered.connect(self.__reopenWithEncodingMenuTriggered)
+        
+        return menu
+        
+    def __initContextMenuEol(self):
+        """
+        Private method to setup the eol context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        self.supportedEols = {}
+        
+        menu = QMenu(self.tr("End-of-Line Type"))
+        
+        self.eolActGrp = QActionGroup(self)
+        
+        act = menu.addAction(UI.PixmapCache.getIcon("eolLinux"),
+                             self.tr("Unix"))
+        act.setCheckable(True)
+        act.setData('\n')
+        self.supportedEols['\n'] = act
+        self.eolActGrp.addAction(act)
+        
+        act = menu.addAction(UI.PixmapCache.getIcon("eolWindows"),
+                             self.tr("Windows"))
+        act.setCheckable(True)
+        act.setData('\r\n')
+        self.supportedEols['\r\n'] = act
+        self.eolActGrp.addAction(act)
+        
+        act = menu.addAction(UI.PixmapCache.getIcon("eolMac"),
+                             self.tr("Macintosh"))
+        act.setCheckable(True)
+        act.setData('\r')
+        self.supportedEols['\r'] = act
+        self.eolActGrp.addAction(act)
+        
+        menu.triggered.connect(self.__eolMenuTriggered)
+        menu.aboutToShow.connect(self.__showContextMenuEol)
+        
+        return menu
+    
+    def __initContextMenuSpellCheck(self):
+        """
+        Private method used to setup the spell checking context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        menu = QMenu(self.tr("Spelling"))
+        menu.setIcon(UI.PixmapCache.getIcon("spellchecking"))
+        
+        self.spellLanguagesMenu = self.__initContextMenuSpellLanguages()
+        self.__menus["SpellLanguages"] = self.spellLanguagesMenu
+        
+        self.menuActs["SpellCheck"] = menu.addAction(
+            UI.PixmapCache.getIcon("spellchecking"),
+            self.tr('Check spelling...'), self.checkSpelling)
+        self.menuActs["SpellCheckSelection"] = menu.addAction(
+            UI.PixmapCache.getIcon("spellchecking"),
+            self.tr('Check spelling of selection...'),
+            self.__checkSpellingSelection)
+        self.menuActs["SpellCheckRemove"] = menu.addAction(
+            self.tr("Remove from dictionary"),
+            self.__removeFromSpellingDictionary)
+        self.menuActs["SpellCheckLanguages"] = menu.addMenu(
+            self.spellLanguagesMenu)
+        
+        menu.aboutToShow.connect(self.__showContextMenuSpellCheck)
+        
+        return menu
+    
+    def __initContextMenuSpellLanguages(self):
+        """
+        Private method to setup the spell checking languages context sub menu.
+        
+        @return reference to the generated menu
+        @rtype QMenu
+        """
+        self.supportedSpellLanguages = {}
+        
+        menu = QMenu(self.tr("Spell Check Languages"))
+        
+        self.spellLanguagesActGrp = QActionGroup(self)
+        
+        self.noSpellLanguageAct = menu.addAction(
+            self.tr("No Language"))
+        self.noSpellLanguageAct.setCheckable(True)
+        self.noSpellLanguageAct.setData("")
+        self.spellLanguagesActGrp.addAction(self.noSpellLanguageAct)
+        menu.addSeparator()
+        
+        for language in sorted(SpellChecker.getAvailableLanguages()):
+            act = menu.addAction(language)
+            act.setCheckable(True)
+            act.setData(language)
+            self.supportedSpellLanguages[language] = act
+            self.spellLanguagesActGrp.addAction(act)
+        
+        menu.triggered.connect(self.__spellLanguagesMenuTriggered)
+        menu.aboutToShow.connect(self.__showContextMenuSpellLanguages)
+        
+        return menu
+    
+    def __initContextMenuMargins(self):
+        """
+        Private method used to setup the context menu for the margins.
+        """
+        self.marginMenuActs = {}
+        
+        # bookmark margin
+        self.bmMarginMenu = QMenu()
+        
+        self.bmMarginMenu.addAction(
+            self.tr('Toggle bookmark'), self.menuToggleBookmark)
+        self.marginMenuActs["NextBookmark"] = self.bmMarginMenu.addAction(
+            self.tr('Next bookmark'), self.nextBookmark)
+        self.marginMenuActs["PreviousBookmark"] = self.bmMarginMenu.addAction(
+            self.tr('Previous bookmark'), self.previousBookmark)
+        self.marginMenuActs["ClearBookmark"] = self.bmMarginMenu.addAction(
+            self.tr('Clear all bookmarks'), self.clearBookmarks)
+        
+        self.bmMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.bmMarginMenu))
+        
+        # breakpoint margin
+        self.bpMarginMenu = QMenu()
+        
+        self.marginMenuActs["Breakpoint"] = self.bpMarginMenu.addAction(
+            self.tr('Toggle breakpoint'), self.menuToggleBreakpoint)
+        self.marginMenuActs["TempBreakpoint"] = self.bpMarginMenu.addAction(
+            self.tr('Toggle temporary breakpoint'),
+            self.__menuToggleTemporaryBreakpoint)
+        self.marginMenuActs["EditBreakpoint"] = self.bpMarginMenu.addAction(
+            self.tr('Edit breakpoint...'), self.menuEditBreakpoint)
+        self.marginMenuActs["EnableBreakpoint"] = self.bpMarginMenu.addAction(
+            self.tr('Enable breakpoint'),
+            self.__menuToggleBreakpointEnabled)
+        self.marginMenuActs["NextBreakpoint"] = self.bpMarginMenu.addAction(
+            self.tr('Next breakpoint'), self.menuNextBreakpoint)
+        self.marginMenuActs["PreviousBreakpoint"] = (
+            self.bpMarginMenu.addAction(
+                self.tr('Previous breakpoint'),
+                self.menuPreviousBreakpoint)
+        )
+        self.marginMenuActs["ClearBreakpoint"] = self.bpMarginMenu.addAction(
+            self.tr('Clear all breakpoints'), self.__menuClearBreakpoints)
+        
+        self.bpMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.bpMarginMenu))
+        
+        # fold margin
+        self.foldMarginMenu = QMenu()
+        
+        self.marginMenuActs["ToggleAllFolds"] = (
+            self.foldMarginMenu.addAction(
+                self.tr("Toggle all folds"),
+                self.foldAll)
+        )
+        self.marginMenuActs["ToggleAllFoldsAndChildren"] = (
+            self.foldMarginMenu.addAction(
+                self.tr("Toggle all folds (including children)"),
+                lambda: self.foldAll(True))
+        )
+        self.marginMenuActs["ToggleCurrentFold"] = (
+            self.foldMarginMenu.addAction(
+                self.tr("Toggle current fold"),
+                self.toggleCurrentFold)
+        )
+        self.foldMarginMenu.addSeparator()
+        self.marginMenuActs["ExpandChildren"] = (
+            self.foldMarginMenu.addAction(
+                self.tr("Expand (including children)"),
+                self.__contextMenuExpandFoldWithChildren)
+        )
+        self.marginMenuActs["CollapseChildren"] = (
+            self.foldMarginMenu.addAction(
+                self.tr("Collapse (including children)"),
+                self.__contextMenuCollapseFoldWithChildren)
+        )
+        self.foldMarginMenu.addSeparator()
+        self.marginMenuActs["ClearAllFolds"] = (
+            self.foldMarginMenu.addAction(
+                self.tr("Clear all folds"),
+                self.clearFolds)
+        )
+        
+        self.foldMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.foldMarginMenu))
+        
+        # indicator margin
+        self.indicMarginMenu = QMenu()
+        
+        self.marginMenuActs["GotoSyntaxError"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Goto syntax error'), self.gotoSyntaxError)
+        )
+        self.marginMenuActs["ShowSyntaxError"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Show syntax error message'),
+                self.__showSyntaxError)
+        )
+        self.marginMenuActs["ClearSyntaxError"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Clear syntax error'), self.clearSyntaxError)
+        )
+        self.indicMarginMenu.addSeparator()
+        self.marginMenuActs["NextWarningMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr("Next warning"), self.nextWarning)
+        )
+        self.marginMenuActs["PreviousWarningMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr("Previous warning"), self.previousWarning)
+        )
+        self.marginMenuActs["ShowWarning"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Show warning message'), self.__showWarning)
+        )
+        self.marginMenuActs["ClearWarnings"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Clear warnings'), self.clearWarnings)
+        )
+        self.indicMarginMenu.addSeparator()
+        self.marginMenuActs["NextCoverageMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Next uncovered line'), self.nextUncovered)
+        )
+        self.marginMenuActs["PreviousCoverageMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Previous uncovered line'), self.previousUncovered)
+        )
+        self.indicMarginMenu.addSeparator()
+        self.marginMenuActs["NextTaskMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Next task'), self.nextTask)
+        )
+        self.marginMenuActs["PreviousTaskMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Previous task'), self.previousTask)
+        )
+        self.indicMarginMenu.addSeparator()
+        self.marginMenuActs["NextChangeMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Next change'), self.nextChange)
+        )
+        self.marginMenuActs["PreviousChangeMarker"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Previous change'), self.previousChange)
+        )
+        self.marginMenuActs["ClearChangeMarkers"] = (
+            self.indicMarginMenu.addAction(
+                self.tr('Clear changes'), self.__reinitOnlineChangeTrace)
+        )
+        
+        self.indicMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.indicMarginMenu))
+        
+    def exportFile(self, exporterFormat):
+        """
+        Public method to export the file.
+        
+        @param exporterFormat format the file should be exported into (string)
+        """
+        if exporterFormat:
+            from . import Exporters
+            exporter = Exporters.getExporter(exporterFormat, self)
+            if exporter:
+                exporter.exportSource()
+            else:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("Export source"),
+                    self.tr(
+                        """<p>No exporter available for the """
+                        """export format <b>{0}</b>. Aborting...</p>""")
+                    .format(exporterFormat))
+        else:
+            EricMessageBox.critical(
+                self,
+                self.tr("Export source"),
+                self.tr("""No export format given. Aborting..."""))
+        
+    def __showContextMenuLanguages(self):
+        """
+        Private slot handling the aboutToShow signal of the languages context
+        menu.
+        """
+        if self.apiLanguage.startswith("Pygments|"):
+            self.pygmentsSelAct.setText(
+                self.tr("Alternatives ({0})").format(
+                    self.getLanguage(normalized=False)))
+        else:
+            self.pygmentsSelAct.setText(self.tr("Alternatives"))
+        self.showMenu.emit("Languages", self.languagesMenu, self)
+        
+    def __selectPygmentsLexer(self):
+        """
+        Private method to select a specific pygments lexer.
+        
+        @return name of the selected pygments lexer (string)
+        """
+        from pygments.lexers import get_all_lexers
+        lexerList = sorted(lex[0] for lex in get_all_lexers())
+        try:
+            lexerSel = lexerList.index(
+                self.getLanguage(normalized=False, forPygments=True))
+        except ValueError:
+            lexerSel = 0
+        lexerName, ok = QInputDialog.getItem(
+            self,
+            self.tr("Pygments Lexer"),
+            self.tr("Select the Pygments lexer to apply."),
+            lexerList,
+            lexerSel,
+            False)
+        if ok and lexerName:
+            return lexerName
+        else:
+            return ""
+        
+    def __languageMenuTriggered(self, act):
+        """
+        Private method to handle the selection of a lexer language.
+        
+        @param act reference to the action that was triggered (QAction)
+        """
+        if act == self.noLanguageAct:
+            self.__resetLanguage()
+        elif act == self.pygmentsAct:
+            self.setLanguage("dummy.pygments")
+        elif act == self.pygmentsSelAct:
+            language = self.__selectPygmentsLexer()
+            if language:
+                self.setLanguage("dummy.pygments", pyname=language)
+        else:
+            language = act.data()
+            if language:
+                self.filetype = language
+                self.setLanguage(self.supportedLanguages[language][1])
+                self.checkSyntax()
+        
+        self.__docstringGenerator = None
+        
+    def __languageChanged(self, language, propagate=True):
+        """
+        Private slot handling a change of a connected editor's language.
+        
+        @param language language to be set (string)
+        @param propagate flag indicating to propagate the change (boolean)
+        """
+        if language == '':
+            self.__resetLanguage(propagate=propagate)
+        elif language == "Guessed":
+            self.setLanguage("dummy.pygments",
+                             propagate=propagate)
+        elif language.startswith("Pygments|"):
+            pyname = language.split("|", 1)[1]
+            self.setLanguage("dummy.pygments", pyname=pyname,
+                             propagate=propagate)
+        else:
+            self.filetype = language
+            self.setLanguage(self.supportedLanguages[language][1],
+                             propagate=propagate)
+            self.checkSyntax()
+        
+        self.__docstringGenerator = None
+        
+    def __resetLanguage(self, propagate=True):
+        """
+        Private method used to reset the language selection.
+        
+        @param propagate flag indicating to propagate the change (boolean)
+        """
+        if (
+            self.lexer_ is not None and
+            (self.lexer_.lexer() == "container" or
+             self.lexer_.lexer() is None)
+        ):
+            with contextlib.suppress(TypeError):
+                self.SCN_STYLENEEDED.disconnect(self.__styleNeeded)
+        
+        self.apiLanguage = ""
+        self.lexer_ = None
+        self.__lexerReset = True
+        self.setLexer()
+        if self.completer is not None:
+            self.completer.setEnabled(False)
+            self.completer = None
+        useMonospaced = self.useMonospaced
+        self.__setTextDisplay()
+        self.__setMarginsDisplay()
+        self.setMonospaced(useMonospaced)
+        with contextlib.suppress(AttributeError):
+            self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
+        
+        self.__docstringGenerator = None
+        
+        if not self.inLanguageChanged and propagate:
+            self.inLanguageChanged = True
+            self.languageChanged.emit(self.apiLanguage)
+            self.inLanguageChanged = False
+        
+    def setLanguage(self, filename, initTextDisplay=True, propagate=True,
+                    pyname=""):
+        """
+        Public method to set a lexer language.
+        
+        @param filename filename used to determine the associated lexer
+            language (string)
+        @param initTextDisplay flag indicating an initialization of the text
+            display is required as well (boolean)
+        @param propagate flag indicating to propagate the change (boolean)
+        @param pyname name of the pygments lexer to use (string)
+        """
+        # clear all warning and syntax error markers
+        self.clearSyntaxError()
+        self.clearWarnings()
+        
+        self.menuActs["MonospacedFont"].setChecked(False)
+        
+        self.__lexerReset = False
+        self.__bindLexer(filename, pyname=pyname)
+        self.__bindCompleter(filename)
+        self.recolor()
+        self.__checkLanguage()
+        
+        self.__docstringGenerator = None
+        
+        # set the text display
+        if initTextDisplay:
+            self.__setTextDisplay()
+        
+        # set the auto-completion and call-tips function
+        self.__setAutoCompletion()
+        self.__setCallTips()
+        
+        if not self.inLanguageChanged and propagate:
+            self.inLanguageChanged = True
+            self.languageChanged.emit(self.apiLanguage)
+            self.inLanguageChanged = False
+    
+    def __checkLanguage(self):
+        """
+        Private method to check the selected language of the language submenu.
+        """
+        if self.apiLanguage == "":
+            self.noLanguageAct.setChecked(True)
+        elif self.apiLanguage == "Guessed":
+            self.pygmentsAct.setChecked(True)
+        elif self.apiLanguage.startswith("Pygments|"):
+            act = self.languagesActGrp.checkedAction()
+            if act:
+                act.setChecked(False)
+        else:
+            self.supportedLanguages[self.apiLanguage][2].setChecked(True)
+    
+    def projectLexerAssociationsChanged(self):
+        """
+        Public slot to handle changes of the project lexer associations.
+        """
+        self.setLanguage(self.fileName)
+    
+    def __showContextMenuEncodings(self):
+        """
+        Private slot handling the aboutToShow signal of the encodings context
+        menu.
+        """
+        self.showMenu.emit("Encodings", self.encodingsMenu, self)
+        
+    def __encodingsMenuTriggered(self, act):
+        """
+        Private method to handle the selection of an encoding.
+        
+        @param act reference to the action that was triggered (QAction)
+        """
+        encoding = act.data()
+        self.setModified(True)
+        self.__encodingChanged("{0}-selected".format(encoding))
+        
+    def __checkEncoding(self):
+        """
+        Private method to check the selected encoding of the encodings submenu.
+        """
+        with contextlib.suppress(AttributeError, KeyError):
+            (self.supportedEncodings[self.__normalizedEncoding()]
+             .setChecked(True))
+        
+    def __encodingChanged(self, encoding, propagate=True):
+        """
+        Private slot to handle a change of the encoding.
+        
+        @param encoding changed encoding (string)
+        @param propagate flag indicating to propagate the change (boolean)
+        """
+        self.encoding = encoding
+        self.__checkEncoding()
+        
+        if not self.inEncodingChanged and propagate:
+            self.inEncodingChanged = True
+            self.encodingChanged.emit(self.encoding)
+            self.inEncodingChanged = False
+        
+    def __normalizedEncoding(self, encoding=""):
+        """
+        Private method to calculate the normalized encoding string.
+        
+        @param encoding encoding to be normalized (string)
+        @return normalized encoding (string)
+        """
+        if not encoding:
+            encoding = self.encoding
+        return (
+            encoding
+            .replace("-default", "")
+            .replace("-guessed", "")
+            .replace("-selected", "")
+        )
+        
+    def __showContextMenuEol(self):
+        """
+        Private slot handling the aboutToShow signal of the eol context menu.
+        """
+        self.showMenu.emit("Eol", self.eolMenu, self)
+        
+    def __eolMenuTriggered(self, act):
+        """
+        Private method to handle the selection of an eol type.
+        
+        @param act reference to the action that was triggered (QAction)
+        """
+        eol = act.data()
+        self.setEolModeByEolString(eol)
+        self.convertEols(self.eolMode())
+        
+    def __checkEol(self):
+        """
+        Private method to check the selected eol type of the eol submenu.
+        """
+        with contextlib.suppress(AttributeError, TypeError):
+            self.supportedEols[self.getLineSeparator()].setChecked(True)
+        
+    def __eolChanged(self):
+        """
+        Private slot to handle a change of the eol mode.
+        """
+        self.__checkEol()
+        
+        if not self.inEolChanged:
+            self.inEolChanged = True
+            eol = self.getLineSeparator()
+            self.eolChanged.emit(eol)
+            self.inEolChanged = False
+    
+    def __showContextMenuSpellCheck(self):
+        """
+        Private slot handling the aboutToShow signal of the spell check
+        context menu.
+        """
+        spellingAvailable = SpellChecker.isAvailable()
+        self.menuActs["SpellCheck"].setEnabled(spellingAvailable)
+        self.menuActs["SpellCheckSelection"].setEnabled(
+            spellingAvailable and self.hasSelectedText())
+        self.menuActs["SpellCheckRemove"].setEnabled(
+            spellingAvailable and self.spellingMenuPos >= 0)
+        self.menuActs["SpellCheckLanguages"].setEnabled(spellingAvailable)
+        
+        self.showMenu.emit("SpellCheck", self.spellCheckMenu, self)
+    
+    def __showContextMenuSpellLanguages(self):
+        """
+        Private slot handling the aboutToShow signal of the spell check
+        languages context menu.
+        """
+        self.showMenu.emit("SpellLanguage", self.spellLanguagesMenu, self)
+    
+    def __spellLanguagesMenuTriggered(self, act):
+        """
+        Private method to handle the selection of a spell check language.
+        
+        @param act reference to the action that was triggered
+        @type QAction
+        """
+        language = act.data()
+        self.__setSpellingLanguage(language)
+        self.spellLanguageChanged.emit(language)
+    
+    def __checkSpellLanguage(self):
+        """
+        Private slot to check the selected spell check language action.
+        """
+        language = self.getSpellingLanguage()
+        with contextlib.suppress(AttributeError, KeyError):
+            self.supportedSpellLanguages[language].setChecked(True)
+    
+    def __spellLanguageChanged(self, language, propagate=True):
+        """
+        Private slot to handle a change of the spell check language.
+        
+        @param language new spell check language
+        @type str
+        @param propagate flag indicating to propagate the change
+        @type bool
+        """
+        self.__setSpellingLanguage(language)
+        self.__checkSpellLanguage()
+        
+        if not self.__inSpellLanguageChanged and propagate:
+            self.__inSpellLanguageChanged = True
+            self.spellLanguageChanged.emit(language)
+            self.__inSpellLanguageChanged = False
+    
+    def __bindLexer(self, filename, pyname=""):
+        """
+        Private slot to set the correct lexer depending on language.
+        
+        @param filename filename used to determine the associated lexer
+            language (string)
+        @param pyname name of the pygments lexer to use (string)
+        """
+        if (
+            self.lexer_ is not None and
+            (self.lexer_.lexer() == "container" or
+             self.lexer_.lexer() is None)
+        ):
+            self.SCN_STYLENEEDED.disconnect(self.__styleNeeded)
+        
+        language = ""
+        if not self.filetype:
+            if filename:
+                basename = os.path.basename(filename)
+                if (
+                    self.project.isOpen() and
+                    self.project.isProjectFile(filename)
+                ):
+                    language = self.project.getEditorLexerAssoc(basename)
+                if not language:
+                    language = Preferences.getEditorLexerAssoc(basename)
+            if language == "Text":
+                # no highlighting for plain text files
+                self.__resetLanguage()
+                return
+            
+            if not language:
+                bindName = self.__bindName(self.text(0))
+                if bindName:
+                    language = Preferences.getEditorLexerAssoc(bindName)
+            if language == "Python":
+                # correction for Python
+                pyVer = Utilities.determinePythonVersion(
+                    filename, self.text(0), self)
+                language = "Python{0}".format(pyVer)
+            if language in ['Python3', 'MicroPython', 'Cython', 'Ruby',
+                            'JavaScript', 'YAML', 'JSON']:
+                self.filetype = language
+            else:
+                self.filetype = ""
+        else:
+            language = self.filetype
+        
+        if language.startswith("Pygments|"):
+            pyname = language
+            self.filetype = language.split("|")[-1]
+            language = ""
+        
+        from . import Lexers
+        self.lexer_ = Lexers.getLexer(language, self, pyname=pyname)
+        if self.lexer_ is None:
+            self.setLexer()
+            self.apiLanguage = ""
+            return
+        
+        if pyname:
+            if pyname.startswith("Pygments|"):
+                self.apiLanguage = pyname
+            else:
+                self.apiLanguage = "Pygments|{0}".format(pyname)
+        else:
+            if language == "Protocol":
+                self.apiLanguage = language
+            else:
+                # Change API language for lexer where QScintilla reports
+                # an abbreviated name.
+                self.apiLanguage = self.lexer_.language()
+                if self.apiLanguage == "POV":
+                    self.apiLanguage = "Povray"
+                elif self.apiLanguage == "PO":
+                    self.apiLanguage = "Gettext"
+        self.setLexer(self.lexer_)
+        self.__setMarginsDisplay()
+        if self.lexer_.lexer() == "container" or self.lexer_.lexer() is None:
+            self.SCN_STYLENEEDED.connect(self.__styleNeeded)
+        
+        # get the font for style 0 and set it as the default font
+        key = (
+            'Scintilla/Guessed/style0/font'
+            if pyname and pyname.startswith("Pygments|") else
+            'Scintilla/{0}/style0/font'.format(self.lexer_.language())
+        )
+        fdesc = Preferences.getSettings().value(key)
+        if fdesc is not None:
+            font = QFont([fdesc[0]], int(fdesc[1]))
+            self.lexer_.setDefaultFont(font)
+        self.lexer_.readSettings(Preferences.getSettings(), "Scintilla")
+        if self.lexer_.hasSubstyles():
+            self.lexer_.readSubstyles(self)
+        
+        # now set the lexer properties
+        self.lexer_.initProperties()
+        
+        # initialize the lexer APIs settings
+        projectType = (
+            self.project.getProjectType()
+            if self.project.isOpen() and self.project.isProjectFile(filename)
+            else ""
+        )
+        api = self.vm.getAPIsManager().getAPIs(self.apiLanguage,
+                                               projectType=projectType)
+        if api is not None and not api.isEmpty():
+            self.lexer_.setAPIs(api.getQsciAPIs())
+            self.acAPI = True
+        else:
+            self.acAPI = False
+        self.autoCompletionAPIsAvailable.emit(self.acAPI)
+        
+        self.__setAnnotationStyles()
+        
+        self.lexer_.setDefaultColor(self.lexer_.color(0))
+        self.lexer_.setDefaultPaper(self.lexer_.paper(0))
+        
+    def __styleNeeded(self, position):
+        """
+        Private slot to handle the need for more styling.
+        
+        @param position end position, that needs styling (integer)
+        """
+        self.lexer_.styleText(self.getEndStyled(), position)
+        
+    def getLexer(self):
+        """
+        Public method to retrieve a reference to the lexer object.
+        
+        @return the lexer object (Lexer)
+        """
+        return self.lexer_
+        
+    def getLanguage(self, normalized=True, forPygments=False):
+        """
+        Public method to retrieve the language of the editor.
+        
+        @param normalized flag indicating to normalize some Pygments
+            lexer names (boolean)
+        @param forPygments flag indicating to normalize some lexer
+            names for Pygments (boolean)
+        @return language of the editor (string)
+        """
+        if (
+            self.apiLanguage == "Guessed" or
+            self.apiLanguage.startswith("Pygments|")
+        ):
+            lang = self.lexer_.name()
+            if normalized:
+                # adjust some Pygments lexer names
+                if lang in ("Python 2.x", "Python"):
+                    lang = "Python3"
+                elif lang == "Protocol Buffer":
+                    lang = "Protocol"
+                    
+        else:
+            lang = self.apiLanguage
+            if forPygments:
+                # adjust some names to Pygments lexer names
+                if lang == "Python3":
+                    lang = "Python"
+                elif lang == "Protocol":
+                    lang = "Protocol Buffer"
+        return lang
+    
+    def getApiLanguage(self):
+        """
+        Public method to get the API language of the editor.
+        
+        @return API language
+        @rtype str
+        """
+        return self.apiLanguage
+        
+    def __bindCompleter(self, filename):
+        """
+        Private slot to set the correct typing completer depending on language.
+        
+        @param filename filename used to determine the associated typing
+            completer language (string)
+        """
+        if self.completer is not None:
+            self.completer.setEnabled(False)
+            self.completer = None
+        
+        filename = os.path.basename(filename)
+        apiLanguage = Preferences.getEditorLexerAssoc(filename)
+        if apiLanguage == "":
+            pyVer = self.__getPyVersion()
+            if pyVer:
+                apiLanguage = "Python{0}".format(pyVer)
+            elif self.isRubyFile():
+                apiLanguage = "Ruby"
+        
+        from . import TypingCompleters
+        self.completer = TypingCompleters.getCompleter(apiLanguage, self)
+        
+    def getCompleter(self):
+        """
+        Public method to retrieve a reference to the completer object.
+        
+        @return the completer object (CompleterBase)
+        """
+        return self.completer
+        
+    def __modificationChanged(self, m):
+        """
+        Private slot to handle the modificationChanged signal.
+        
+        It emits the signal modificationStatusChanged with parameters
+        m and self.
+        
+        @param m modification status
+        """
+        if not m and bool(self.fileName) and pathlib.Path(self.fileName).exists():
+            self.lastModified = pathlib.Path(self.fileName).stat().st_mtime
+        self.modificationStatusChanged.emit(m, self)
+        self.undoAvailable.emit(self.isUndoAvailable())
+        self.redoAvailable.emit(self.isRedoAvailable())
+        
+    def __cursorPositionChanged(self, line, index):
+        """
+        Private slot to handle the cursorPositionChanged signal.
+        
+        It emits the signal cursorChanged with parameters fileName,
+        line and pos.
+        
+        @param line line number of the cursor
+        @param index position in line of the cursor
+        """
+        self.cursorChanged.emit(self.fileName, line + 1, index)
+        
+        if Preferences.getEditor("MarkOccurrencesEnabled"):
+            self.__markOccurrencesTimer.stop()
+            self.__markOccurrencesTimer.start()
+        
+        if self.lastLine != line:
+            self.cursorLineChanged.emit(line)
+        
+        if self.spell is not None:
+            # do spell checking
+            doSpelling = True
+            if self.lastLine == line:
+                start, end = self.getWordBoundaries(
+                    line, index, useWordChars=False)
+                if start <= self.lastIndex and self.lastIndex <= end:
+                    doSpelling = False
+            if doSpelling:
+                pos = self.positionFromLineIndex(self.lastLine, self.lastIndex)
+                self.spell.checkWord(pos)
+        
+        if self.lastLine != line:
+            self.__markerMap.update()
+        
+        self.lastLine = line
+        self.lastIndex = index
+        
+    def __modificationReadOnly(self):
+        """
+        Private slot to handle the modificationAttempted signal.
+        """
+        EricMessageBox.warning(
+            self,
+            self.tr("Modification of Read Only file"),
+            self.tr("""You are attempting to change a read only file. """
+                    """Please save to a different file first."""))
+        
+    def setNoName(self, noName):
+        """
+        Public method to set the display string for an unnamed editor.
+        
+        @param noName display string for this unnamed editor (string)
+        """
+        self.noName = noName
+        
+    def getNoName(self):
+        """
+        Public method to get the display string for an unnamed editor.
+        
+        @return display string for this unnamed editor (string)
+        """
+        return self.noName
+        
+    def getFileName(self):
+        """
+        Public method to return the name of the file being displayed.
+        
+        @return filename of the displayed file (string)
+        """
+        return self.fileName
+        
+    def getFileType(self):
+        """
+        Public method to return the type of the file being displayed.
+        
+        @return type of the displayed file (string)
+        """
+        return self.filetype
+        
+    def getFileTypeByFlag(self):
+        """
+        Public method to return the type of the file, if it was set by an
+        eflag: marker.
+        
+        @return type of the displayed file, if set by an eflag: marker or an
+            empty string (string)
+        """
+        if self.filetypeByFlag:
+            return self.filetype
+        else:
+            return ""
+        
+    def determineFileType(self):
+        """
+        Public method to determine the file type using various tests.
+        
+        @return type of the displayed file or an empty string (string)
+        """
+        ftype = self.filetype
+        if not ftype:
+            pyVer = self.__getPyVersion()
+            if pyVer:
+                ftype = "Python{0}".format(pyVer)
+            elif self.isRubyFile():
+                ftype = "Ruby"
+            else:
+                ftype = ""
+        
+        return ftype
+        
+    def getEncoding(self):
+        """
+        Public method to return the current encoding.
+        
+        @return current encoding (string)
+        """
+        return self.encoding
+    
+    def __getPyVersion(self):
+        """
+        Private method to return the Python main version or 0 if it's
+        not a Python file at all.
+        
+        @return Python version or 0 if it's not a Python file (int)
+        """
+        return Utilities.determinePythonVersion(
+            self.fileName, self.text(0), self)
+    
+    def isPyFile(self):
+        """
+        Public method to return a flag indicating a Python (2 or 3) file.
+        
+        @return flag indicating a Python3 file (boolean)
+        """
+        return self.__getPyVersion() == 3
+    
+    def isPy3File(self):
+        """
+        Public method to return a flag indicating a Python3 file.
+        
+        @return flag indicating a Python3 file (boolean)
+        """
+        return self.__getPyVersion() == 3
+    
+    def isMicroPythonFile(self):
+        """
+        Public method to return a flag indicating a MicroPython file.
+        
+        @return flag indicating a MicroPython file
+        @rtype bool
+        """
+        if self.filetype == "MicroPython":
+            return True
+        
+        return False
+    
+    def isCythonFile(self):
+        """
+        Public method to return a flag indicating a Cython file.
+        
+        @return flag indicating a Cython file
+        @rtype bool
+        """
+        if self.filetype == "Cython":
+            return True
+        
+        return False
+
+    def isRubyFile(self):
+        """
+        Public method to return a flag indicating a Ruby file.
+        
+        @return flag indicating a Ruby file (boolean)
+        """
+        if self.filetype == "Ruby":
+            return True
+        
+        if self.filetype == "":
+            line0 = self.text(0)
+            if (
+                line0.startswith("#!") and
+                "ruby" in line0
+            ):
+                self.filetype = "Ruby"
+                return True
+            
+            if (
+                bool(self.fileName) and
+                os.path.splitext(self.fileName)[1] in
+                    self.dbs.getExtensions('Ruby')
+            ):
+                self.filetype = "Ruby"
+                return True
+        
+        return False
+
+    def isJavascriptFile(self):
+        """
+        Public method to return a flag indicating a Javascript file.
+        
+        @return flag indicating a Javascript file (boolean)
+        """
+        if self.filetype == "JavaScript":
+            return True
+        
+        if (
+            self.filetype == "" and
+            self.fileName and
+            os.path.splitext(self.fileName)[1] == ".js"
+        ):
+            self.filetype = "JavaScript"
+            return True
+        
+        return False
+    
+    def highlightVisible(self):
+        """
+        Public method to make sure that the highlight is visible.
+        """
+        if self.lastHighlight is not None:
+            lineno = self.markerLine(self.lastHighlight)
+            self.ensureVisible(lineno + 1)
+        
+    def highlight(self, line=None, error=False, syntaxError=False):
+        """
+        Public method to highlight [or de-highlight] a particular line.
+        
+        @param line line number to highlight (integer)
+        @param error flag indicating whether the error highlight should be
+            used (boolean)
+        @param syntaxError flag indicating a syntax error (boolean)
+        """
+        if line is None:
+            self.lastHighlight = None
+            if self.lastErrorMarker is not None:
+                self.markerDeleteHandle(self.lastErrorMarker)
+            self.lastErrorMarker = None
+            if self.lastCurrMarker is not None:
+                self.markerDeleteHandle(self.lastCurrMarker)
+            self.lastCurrMarker = None
+        else:
+            if error:
+                if self.lastErrorMarker is not None:
+                    self.markerDeleteHandle(self.lastErrorMarker)
+                self.lastErrorMarker = self.markerAdd(line - 1, self.errorline)
+                self.lastHighlight = self.lastErrorMarker
+            else:
+                if self.lastCurrMarker is not None:
+                    self.markerDeleteHandle(self.lastCurrMarker)
+                self.lastCurrMarker = self.markerAdd(line - 1,
+                                                     self.currentline)
+                self.lastHighlight = self.lastCurrMarker
+            self.setCursorPosition(line - 1, 0)
+        
+    def getHighlightPosition(self):
+        """
+        Public method to return the position of the highlight bar.
+        
+        @return line number of the highlight bar (integer)
+        """
+        if self.lastHighlight is not None:
+            return self.markerLine(self.lastHighlight)
+        else:
+            return 1
+    
+    ###########################################################################
+    ## Breakpoint handling methods below
+    ###########################################################################
+
+    def __modified(self, pos, mtype, text, length, linesAdded, line, foldNow,
+                   foldPrev, token, annotationLinesAdded):
+        """
+        Private method to handle changes of the number of lines.
+        
+        @param pos start position of change (integer)
+        @param mtype flags identifying the change (integer)
+        @param text text that is given to the Undo system (string)
+        @param length length of the change (integer)
+        @param linesAdded number of added/deleted lines (integer)
+        @param line line number of a fold level or marker change (integer)
+        @param foldNow new fold level (integer)
+        @param foldPrev previous fold level (integer)
+        @param token ???
+        @param annotationLinesAdded number of added/deleted annotation lines
+            (integer)
+        """
+        if (
+            mtype & (self.SC_MOD_INSERTTEXT | self.SC_MOD_DELETETEXT) and
+            linesAdded != 0 and
+            self.breaks
+        ):
+            bps = []    # list of breakpoints
+            for handle, (ln, cond, temp, enabled, ignorecount) in (
+                self.breaks.items()
+            ):
+                line = self.markerLine(handle) + 1
+                if ln != line:
+                    bps.append((ln, line))
+                    self.breaks[handle] = (line, cond, temp, enabled,
+                                           ignorecount)
+            self.inLinesChanged = True
+            for ln, line in sorted(bps, reverse=linesAdded > 0):
+                index1 = self.breakpointModel.getBreakPointIndex(
+                    self.fileName, ln)
+                index2 = self.breakpointModel.index(index1.row(), 1)
+                self.breakpointModel.setData(index2, line)
+            self.inLinesChanged = False
+        
+    def __restoreBreakpoints(self):
+        """
+        Private method to restore the breakpoints.
+        """
+        for handle in list(self.breaks.keys()):
+            self.markerDeleteHandle(handle)
+        self.__addBreakPoints(
+            QModelIndex(), 0, self.breakpointModel.rowCount() - 1)
+        self.__markerMap.update()
+        
+    def __deleteBreakPoints(self, parentIndex, start, end):
+        """
+        Private slot to delete breakpoints.
+        
+        @param parentIndex index of parent item (QModelIndex)
+        @param start start row (integer)
+        @param end end row (integer)
+        """
+        for row in range(start, end + 1):
+            index = self.breakpointModel.index(row, 0, parentIndex)
+            fn, lineno = self.breakpointModel.getBreakPointByIndex(index)[0:2]
+            if fn == self.fileName:
+                self.clearBreakpoint(lineno)
+        
+    def __changeBreakPoints(self, startIndex, endIndex):
+        """
+        Private slot to set changed breakpoints.
+        
+        @param startIndex start index of the breakpoints being changed
+            (QModelIndex)
+        @param endIndex end index of the breakpoints being changed
+            (QModelIndex)
+        """
+        if not self.inLinesChanged:
+            self.__addBreakPoints(QModelIndex(), startIndex.row(),
+                                  endIndex.row())
+        
+    def __breakPointDataAboutToBeChanged(self, startIndex, endIndex):
+        """
+        Private slot to handle the dataAboutToBeChanged signal of the
+        breakpoint model.
+        
+        @param startIndex start index of the rows to be changed (QModelIndex)
+        @param endIndex end index of the rows to be changed (QModelIndex)
+        """
+        self.__deleteBreakPoints(QModelIndex(), startIndex.row(),
+                                 endIndex.row())
+        
+    def __addBreakPoints(self, parentIndex, start, end):
+        """
+        Private slot to add breakpoints.
+        
+        @param parentIndex index of parent item (QModelIndex)
+        @param start start row (integer)
+        @param end end row (integer)
+        """
+        for row in range(start, end + 1):
+            index = self.breakpointModel.index(row, 0, parentIndex)
+            fn, line, cond, temp, enabled, ignorecount = (
+                self.breakpointModel.getBreakPointByIndex(index)[:6]
+            )
+            if fn == self.fileName:
+                self.newBreakpointWithProperties(
+                    line, (cond, temp, enabled, ignorecount))
+        
+    def clearBreakpoint(self, line):
+        """
+        Public method to clear a breakpoint.
+        
+        Note: This doesn't clear the breakpoint in the debugger,
+        it just deletes it from the editor internal list of breakpoints.
+        
+        @param line line number of the breakpoint (integer)
+        """
+        if self.inLinesChanged:
+            return
+        
+        for handle in self.breaks:
+            if self.markerLine(handle) == line - 1:
+                break
+        else:
+            # not found, simply ignore it
+            return
+        
+        del self.breaks[handle]
+        self.markerDeleteHandle(handle)
+        self.__markerMap.update()
+        
+    def newBreakpointWithProperties(self, line, properties):
+        """
+        Public method to set a new breakpoint and its properties.
+        
+        @param line line number of the breakpoint (integer)
+        @param properties properties for the breakpoint (tuple)
+                (condition, temporary flag, enabled flag, ignore count)
+        """
+        if not properties[2]:
+            marker = self.dbreakpoint
+        elif properties[0]:
+            marker = properties[1] and self.tcbreakpoint or self.cbreakpoint
+        else:
+            marker = properties[1] and self.tbreakpoint or self.breakpoint
+            
+        if self.markersAtLine(line - 1) & self.breakpointMask == 0:
+            handle = self.markerAdd(line - 1, marker)
+            self.breaks[handle] = (line,) + properties
+            self.breakpointToggled.emit(self)
+            self.__markerMap.update()
+        
+    def __toggleBreakpoint(self, line, temporary=False):
+        """
+        Private method to toggle a breakpoint.
+        
+        @param line line number of the breakpoint (integer)
+        @param temporary flag indicating a temporary breakpoint (boolean)
+        """
+        for handle in self.breaks:
+            if self.markerLine(handle) == line - 1:
+                # delete breakpoint or toggle it to the next state
+                index = self.breakpointModel.getBreakPointIndex(
+                    self.fileName, line)
+                if (
+                    Preferences.getDebugger("ThreeStateBreakPoints") and
+                    not self.breakpointModel.isBreakPointTemporaryByIndex(
+                        index)
+                ):
+                    self.breakpointModel.deleteBreakPointByIndex(index)
+                    self.__addBreakPoint(line, True)
+                else:
+                    self.breakpointModel.deleteBreakPointByIndex(index)
+                    self.breakpointToggled.emit(self)
+                break
+        else:
+            self.__addBreakPoint(line, temporary)
+
+    def __addBreakPoint(self, line, temporary):
+        """
+        Private method to add a new breakpoint.
+        
+        @param line line number of the breakpoint (integer)
+        @param temporary flag indicating a temporary breakpoint (boolean)
+        """
+        if self.fileName and self.isPyFile():
+            linestarts = PythonDisViewer.linestarts(self.text())
+            if line not in linestarts:
+                if Preferences.getDebugger("IntelligentBreakpoints"):
+                    # change line to the next one starting an instruction block
+                    index = bisect.bisect(linestarts, line)
+                    with contextlib.suppress(IndexError):
+                        line = linestarts[index]
+                        self.__toggleBreakpoint(line, temporary=temporary)
+                else:
+                    EricMessageBox.warning(
+                        self,
+                        self.tr("Add Breakpoint"),
+                        self.tr("No Python byte code will be created for the"
+                                " selected line. No break point will be set!")
+                    )
+                return
+            
+            self.breakpointModel.addBreakPoint(
+                self.fileName, line, ('', temporary, True, 0))
+            self.breakpointToggled.emit(self)
+        
+    def __toggleBreakpointEnabled(self, line):
+        """
+        Private method to toggle a breakpoints enabled status.
+        
+        @param line line number of the breakpoint (integer)
+        """
+        for handle in self.breaks:
+            if self.markerLine(handle) == line - 1:
+                index = self.breakpointModel.getBreakPointIndex(
+                    self.fileName, line)
+                self.breakpointModel.setBreakPointEnabledByIndex(
+                    index, not self.breaks[handle][3])
+                break
+        
+    def curLineHasBreakpoint(self):
+        """
+        Public method to check for the presence of a breakpoint at the current
+        line.
+        
+        @return flag indicating the presence of a breakpoint (boolean)
+        """
+        line, _ = self.getCursorPosition()
+        return self.markersAtLine(line) & self.breakpointMask != 0
+        
+    def getBreakpointLines(self):
+        """
+        Public method to get the lines containing a breakpoint.
+        
+        @return list of lines containing a breakpoint (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, self.breakpointMask)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasBreakpoints(self):
+        """
+        Public method to check for the presence of breakpoints.
+        
+        @return flag indicating the presence of breakpoints (boolean)
+        """
+        return len(self.breaks) > 0
+        
+    def __menuToggleTemporaryBreakpoint(self):
+        """
+        Private slot to handle the 'Toggle temporary breakpoint' context menu
+        action.
+        """
+        if self.line < 0:
+            self.line, index = self.getCursorPosition()
+        self.line += 1
+        self.__toggleBreakpoint(self.line, 1)
+        self.line = -1
+        
+    def menuToggleBreakpoint(self):
+        """
+        Public slot to handle the 'Toggle breakpoint' context menu action.
+        """
+        if self.line < 0:
+            self.line, index = self.getCursorPosition()
+        self.line += 1
+        self.__toggleBreakpoint(self.line)
+        self.line = -1
+        
+    def __menuToggleBreakpointEnabled(self):
+        """
+        Private slot to handle the 'Enable/Disable breakpoint' context menu
+        action.
+        """
+        if self.line < 0:
+            self.line, index = self.getCursorPosition()
+        self.line += 1
+        self.__toggleBreakpointEnabled(self.line)
+        self.line = -1
+        
+    def menuEditBreakpoint(self, line=None):
+        """
+        Public slot to handle the 'Edit breakpoint' context menu action.
+        
+        @param line linenumber of the breakpoint to edit
+        """
+        if line is not None:
+            self.line = line - 1
+        if self.line < 0:
+            self.line, index = self.getCursorPosition()
+        
+        for handle in self.breaks:
+            if self.markerLine(handle) == self.line:
+                ln, cond, temp, enabled, ignorecount = self.breaks[handle]
+                index = self.breakpointModel.getBreakPointIndex(self.fileName,
+                                                                ln)
+                if not index.isValid():
+                    return
+                
+                # get recently used breakpoint conditions
+                rs = Preferences.Prefs.rsettings.value(
+                    recentNameBreakpointConditions)
+                condHistory = (
+                    Preferences.toList(rs)[
+                        :Preferences.getDebugger("RecentNumber")]
+                    if rs is not None else
+                    []
+                )
+                
+                from Debugger.EditBreakpointDialog import EditBreakpointDialog
+                dlg = EditBreakpointDialog(
+                    (self.fileName, ln),
+                    (cond, temp, enabled, ignorecount),
+                    condHistory, self, modal=True)
+                if dlg.exec() == QDialog.DialogCode.Accepted:
+                    cond, temp, enabled, ignorecount = dlg.getData()
+                    self.breakpointModel.setBreakPointByIndex(
+                        index, self.fileName, ln,
+                        (cond, temp, enabled, ignorecount))
+                    
+                    if cond:
+                        # save the recently used breakpoint condition
+                        if cond in condHistory:
+                            condHistory.remove(cond)
+                        condHistory.insert(0, cond)
+                        Preferences.Prefs.rsettings.setValue(
+                            recentNameBreakpointConditions, condHistory)
+                        Preferences.Prefs.rsettings.sync()
+                    
+                    break
+        
+        self.line = -1
+        
+    def menuNextBreakpoint(self):
+        """
+        Public slot to handle the 'Next breakpoint' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == self.lines() - 1:
+            line = 0
+        else:
+            line += 1
+        bpline = self.markerFindNext(line, self.breakpointMask)
+        if bpline < 0:
+            # wrap around
+            bpline = self.markerFindNext(0, self.breakpointMask)
+        if bpline >= 0:
+            self.setCursorPosition(bpline, 0)
+            self.ensureLineVisible(bpline)
+        
+    def menuPreviousBreakpoint(self):
+        """
+        Public slot to handle the 'Previous breakpoint' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == 0:
+            line = self.lines() - 1
+        else:
+            line -= 1
+        bpline = self.markerFindPrevious(line, self.breakpointMask)
+        if bpline < 0:
+            # wrap around
+            bpline = self.markerFindPrevious(
+                self.lines() - 1, self.breakpointMask)
+        if bpline >= 0:
+            self.setCursorPosition(bpline, 0)
+            self.ensureLineVisible(bpline)
+        
+    def __menuClearBreakpoints(self):
+        """
+        Private slot to handle the 'Clear all breakpoints' context menu action.
+        """
+        self.__clearBreakpoints(self.fileName)
+        
+    def __clearBreakpoints(self, fileName):
+        """
+        Private slot to clear all breakpoints.
+        
+        @param fileName name of the file (string)
+        """
+        idxList = []
+        for (ln, _, _, _, _) in self.breaks.values():
+            index = self.breakpointModel.getBreakPointIndex(fileName, ln)
+            if index.isValid():
+                idxList.append(index)
+        if idxList:
+            self.breakpointModel.deleteBreakPoints(idxList)
+    
+    ###########################################################################
+    ## Bookmark handling methods below
+    ###########################################################################
+    
+    def toggleBookmark(self, line):
+        """
+        Public method to toggle a bookmark.
+        
+        @param line line number of the bookmark (integer)
+        """
+        for handle in self.bookmarks:
+            if self.markerLine(handle) == line - 1:
+                self.bookmarks.remove(handle)
+                self.markerDeleteHandle(handle)
+                break
+        else:
+            # set a new bookmark
+            handle = self.markerAdd(line - 1, self.bookmark)
+            self.bookmarks.append(handle)
+        self.bookmarkToggled.emit(self)
+        self.__markerMap.update()
+        
+    def getBookmarks(self):
+        """
+        Public method to retrieve the bookmarks.
+        
+        @return sorted list of all lines containing a bookmark
+            (list of integer)
+        """
+        bmlist = []
+        for handle in self.bookmarks:
+            bmlist.append(self.markerLine(handle) + 1)
+        
+        bmlist.sort()
+        return bmlist
+        
+    def getBookmarkLines(self):
+        """
+        Public method to get the lines containing a bookmark.
+        
+        @return list of lines containing a bookmark (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, 1 << self.bookmark)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasBookmarks(self):
+        """
+        Public method to check for the presence of bookmarks.
+        
+        @return flag indicating the presence of bookmarks (boolean)
+        """
+        return len(self.bookmarks) > 0
+    
+    def menuToggleBookmark(self):
+        """
+        Public slot to handle the 'Toggle bookmark' context menu action.
+        """
+        if self.line < 0:
+            self.line, index = self.getCursorPosition()
+        self.line += 1
+        self.toggleBookmark(self.line)
+        self.line = -1
+        
+    def nextBookmark(self):
+        """
+        Public slot to handle the 'Next bookmark' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == self.lines() - 1:
+            line = 0
+        else:
+            line += 1
+        bmline = self.markerFindNext(line, 1 << self.bookmark)
+        if bmline < 0:
+            # wrap around
+            bmline = self.markerFindNext(0, 1 << self.bookmark)
+        if bmline >= 0:
+            self.setCursorPosition(bmline, 0)
+            self.ensureLineVisible(bmline)
+        
+    def previousBookmark(self):
+        """
+        Public slot to handle the 'Previous bookmark' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == 0:
+            line = self.lines() - 1
+        else:
+            line -= 1
+        bmline = self.markerFindPrevious(line, 1 << self.bookmark)
+        if bmline < 0:
+            # wrap around
+            bmline = self.markerFindPrevious(
+                self.lines() - 1, 1 << self.bookmark)
+        if bmline >= 0:
+            self.setCursorPosition(bmline, 0)
+            self.ensureLineVisible(bmline)
+        
+    def clearBookmarks(self):
+        """
+        Public slot to handle the 'Clear all bookmarks' context menu action.
+        """
+        for handle in self.bookmarks:
+            self.markerDeleteHandle(handle)
+        self.bookmarks.clear()
+        self.bookmarkToggled.emit(self)
+        self.__markerMap.update()
+    
+    ###########################################################################
+    ## Printing methods below
+    ###########################################################################
+
+    def printFile(self):
+        """
+        Public slot to print the text.
+        """
+        from .Printer import Printer
+        printer = Printer(mode=QPrinter.PrinterMode.HighResolution)
+        sb = ericApp().getObject("UserInterface").statusBar()
+        printDialog = QPrintDialog(printer, self)
+        if self.hasSelectedText():
+            printDialog.setOption(
+                QAbstractPrintDialog.PrintDialogOption.PrintSelection,
+                True)
+        if printDialog.exec() == QDialog.DialogCode.Accepted:
+            sb.showMessage(self.tr('Printing...'))
+            QApplication.processEvents()
+            fn = self.getFileName()
+            if fn is not None:
+                printer.setDocName(os.path.basename(fn))
+            else:
+                printer.setDocName(self.noName)
+            if (
+                printDialog.printRange() ==
+                QAbstractPrintDialog.PrintRange.Selection
+            ):
+                # get the selection
+                fromLine, fromIndex, toLine, toIndex = self.getSelection()
+                if toIndex == 0:
+                    toLine -= 1
+                # QScintilla seems to print one line more than told
+                res = printer.printRange(self, fromLine, toLine - 1)
+            else:
+                res = printer.printRange(self)
+            if res:
+                sb.showMessage(self.tr('Printing completed'), 2000)
+            else:
+                sb.showMessage(self.tr('Error while printing'), 2000)
+            QApplication.processEvents()
+        else:
+            sb.showMessage(self.tr('Printing aborted'), 2000)
+            QApplication.processEvents()
+
+    def printPreviewFile(self):
+        """
+        Public slot to show a print preview of the text.
+        """
+        from PyQt6.QtPrintSupport import QPrintPreviewDialog
+        from .Printer import Printer
+        
+        printer = Printer(mode=QPrinter.PrinterMode.HighResolution)
+        fn = self.getFileName()
+        if fn is not None:
+            printer.setDocName(os.path.basename(fn))
+        else:
+            printer.setDocName(self.noName)
+        preview = QPrintPreviewDialog(printer, self)
+        preview.paintRequested.connect(self.__printPreview)
+        preview.exec()
+    
+    def __printPreview(self, printer):
+        """
+        Private slot to generate a print preview.
+        
+        @param printer reference to the printer object
+            (QScintilla.Printer.Printer)
+        """
+        printer.printRange(self)
+    
+    ###########################################################################
+    ## Task handling methods below
+    ###########################################################################
+    
+    def getTaskLines(self):
+        """
+        Public method to get the lines containing a task.
+        
+        @return list of lines containing a task (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, 1 << self.taskmarker)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasTaskMarkers(self):
+        """
+        Public method to determine, if this editor contains any task markers.
+        
+        @return flag indicating the presence of task markers (boolean)
+        """
+        return self.__hasTaskMarkers
+        
+    def nextTask(self):
+        """
+        Public slot to handle the 'Next task' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == self.lines() - 1:
+            line = 0
+        else:
+            line += 1
+        taskline = self.markerFindNext(line, 1 << self.taskmarker)
+        if taskline < 0:
+            # wrap around
+            taskline = self.markerFindNext(0, 1 << self.taskmarker)
+        if taskline >= 0:
+            self.setCursorPosition(taskline, 0)
+            self.ensureLineVisible(taskline)
+        
+    def previousTask(self):
+        """
+        Public slot to handle the 'Previous task' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == 0:
+            line = self.lines() - 1
+        else:
+            line -= 1
+        taskline = self.markerFindPrevious(line, 1 << self.taskmarker)
+        if taskline < 0:
+            # wrap around
+            taskline = self.markerFindPrevious(
+                self.lines() - 1, 1 << self.taskmarker)
+        if taskline >= 0:
+            self.setCursorPosition(taskline, 0)
+            self.ensureLineVisible(taskline)
+        
+    def extractTasks(self):
+        """
+        Public slot to extract all tasks.
+        """
+        from Tasks.Task import Task
+        markers = {
+            taskType: Preferences.getTasks(markersName).split()
+            for taskType, markersName in Task.TaskType2MarkersName.items()
+        }
+        txtList = self.text().split(self.getLineSeparator())
+        
+        # clear all task markers and tasks
+        self.markerDeleteAll(self.taskmarker)
+        self.taskViewer.clearFileTasks(self.fileName)
+        self.__hasTaskMarkers = False
+        
+        # now search tasks and record them
+        for lineIndex, line in enumerate(txtList):
+            shouldBreak = False
+            
+            if line.endswith("__NO-TASK__"):
+                # ignore potential task marker
+                continue
+            
+            for taskType, taskMarkers in markers.items():
+                for taskMarker in taskMarkers:
+                    index = line.find(taskMarker)
+                    if index > -1:
+                        task = line[index:]
+                        self.markerAdd(lineIndex, self.taskmarker)
+                        self.taskViewer.addFileTask(
+                            task, self.fileName, lineIndex + 1, taskType)
+                        self.__hasTaskMarkers = True
+                        shouldBreak = True
+                        break
+                if shouldBreak:
+                    break
+        self.taskMarkersUpdated.emit(self)
+        self.__markerMap.update()
+    
+    ###########################################################################
+    ## Change tracing methods below
+    ###########################################################################
+    
+    def __createChangeMarkerPixmap(self, key, size=16, width=4):
+        """
+        Private method to create a pixmap for the change markers.
+        
+        @param key key of the color to use (string)
+        @param size size of the pixmap (integer)
+        @param width width of the marker line (integer)
+        @return create pixmap (QPixmap)
+        """
+        pixmap = QPixmap(size, size)
+        pixmap.fill(Qt.GlobalColor.transparent)
+        painter = QPainter(pixmap)
+        painter.fillRect(size - 4, 0, 4, size,
+                         Preferences.getEditorColour(key))
+        painter.end()
+        return pixmap
+        
+    def __initOnlineChangeTrace(self):
+        """
+        Private slot to initialize the online change trace.
+        """
+        self.__hasChangeMarkers = False
+        self.__oldText = self.text()
+        self.__lastSavedText = self.text()
+        self.__onlineChangeTraceTimer = QTimer(self)
+        self.__onlineChangeTraceTimer.setSingleShot(True)
+        self.__onlineChangeTraceTimer.setInterval(
+            Preferences.getEditor("OnlineChangeTraceInterval"))
+        self.__onlineChangeTraceTimer.timeout.connect(
+            self.__onlineChangeTraceTimerTimeout)
+        self.textChanged.connect(self.__resetOnlineChangeTraceTimer)
+        
+    def __reinitOnlineChangeTrace(self):
+        """
+        Private slot to re-initialize the online change trace.
+        """
+        self.__oldText = self.text()
+        self.__lastSavedText = self.text()
+        self.__deleteAllChangeMarkers()
+        
+    def __resetOnlineChangeTraceTimer(self):
+        """
+        Private method to reset the online syntax check timer.
+        """
+        if Preferences.getEditor("OnlineChangeTrace"):
+            self.__onlineChangeTraceTimer.stop()
+            self.__onlineChangeTraceTimer.start()
+        
+    def __onlineChangeTraceTimerTimeout(self):
+        """
+        Private slot to mark added and changed lines.
+        """
+        self.__deleteAllChangeMarkers()
+        
+        # step 1: mark saved changes
+        oldL = self.__oldText.splitlines()
+        newL = self.__lastSavedText.splitlines()
+        matcher = difflib.SequenceMatcher(None, oldL, newL)
+        
+        for token, _, _, j1, j2 in matcher.get_opcodes():
+            if token in ["insert", "replace"]:
+                for lineNo in range(j1, j2):
+                    self.markerAdd(lineNo, self.__changeMarkerSaved)
+                    self.__hasChangeMarkers = True
+        
+        # step 2: mark unsaved changes
+        oldL = self.__lastSavedText.splitlines()
+        newL = self.text().splitlines()
+        matcher = difflib.SequenceMatcher(None, oldL, newL)
+        
+        for token, _, _, j1, j2 in matcher.get_opcodes():
+            if token in ["insert", "replace"]:
+                for lineNo in range(j1, j2):
+                    self.markerAdd(lineNo, self.__changeMarkerUnsaved)
+                    self.__hasChangeMarkers = True
+        
+        if self.__hasChangeMarkers:
+            self.changeMarkersUpdated.emit(self)
+            self.__markerMap.update()
+        
+    def __resetOnlineChangeTraceInfo(self):
+        """
+        Private slot to reset the online change trace info.
+        """
+        self.__lastSavedText = self.text()
+        self.__deleteAllChangeMarkers()
+        
+        # mark saved changes
+        oldL = self.__oldText.splitlines()
+        newL = self.__lastSavedText.splitlines()
+        matcher = difflib.SequenceMatcher(None, oldL, newL)
+        
+        for token, _, _, j1, j2 in matcher.get_opcodes():
+            if token in ["insert", "replace"]:
+                for lineNo in range(j1, j2):
+                    self.markerAdd(lineNo, self.__changeMarkerSaved)
+                    self.__hasChangeMarkers = True
+        
+        if self.__hasChangeMarkers:
+            self.changeMarkersUpdated.emit(self)
+            self.__markerMap.update()
+        
+    def __deleteAllChangeMarkers(self):
+        """
+        Private slot to delete all change markers.
+        """
+        self.markerDeleteAll(self.__changeMarkerUnsaved)
+        self.markerDeleteAll(self.__changeMarkerSaved)
+        self.__hasChangeMarkers = False
+        self.changeMarkersUpdated.emit(self)
+        self.__markerMap.update()
+        
+    def getChangeLines(self):
+        """
+        Public method to get the lines containing a change.
+        
+        @return list of lines containing a change (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, self.changeMarkersMask)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasChangeMarkers(self):
+        """
+        Public method to determine, if this editor contains any change markers.
+        
+        @return flag indicating the presence of change markers (boolean)
+        """
+        return self.__hasChangeMarkers
+        
+    def nextChange(self):
+        """
+        Public slot to handle the 'Next change' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == self.lines() - 1:
+            line = 0
+        else:
+            line += 1
+        changeline = self.markerFindNext(line, self.changeMarkersMask)
+        if changeline < 0:
+            # wrap around
+            changeline = self.markerFindNext(0, self.changeMarkersMask)
+        if changeline >= 0:
+            self.setCursorPosition(changeline, 0)
+            self.ensureLineVisible(changeline)
+        
+    def previousChange(self):
+        """
+        Public slot to handle the 'Previous change' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == 0:
+            line = self.lines() - 1
+        else:
+            line -= 1
+        changeline = self.markerFindPrevious(line, self.changeMarkersMask)
+        if changeline < 0:
+            # wrap around
+            changeline = self.markerFindPrevious(
+                self.lines() - 1, self.changeMarkersMask)
+        if changeline >= 0:
+            self.setCursorPosition(changeline, 0)
+            self.ensureLineVisible(changeline)
+    
+    ###########################################################################
+    ## Flags handling methods below
+    ###########################################################################
+    
+    def __processFlags(self):
+        """
+        Private method to extract flags and process them.
+        
+        @return list of change flags (list of string)
+        """
+        txt = self.text()
+        flags = Utilities.extractFlags(txt)
+        
+        changedFlags = []
+        
+        # Flag 1: FileType
+        if "FileType" in flags:
+            oldFiletype = self.filetype
+            if isinstance(flags["FileType"], str):
+                self.filetype = flags["FileType"]
+                self.filetypeByFlag = True
+                if oldFiletype != self.filetype:
+                    changedFlags.append("FileType")
+        else:
+            if self.filetype != "" and self.filetypeByFlag:
+                self.filetype = ""
+                self.filetypeByFlag = False
+                self.__bindName(txt.splitlines()[0])
+                changedFlags.append("FileType")
+        
+        return changedFlags
+    
+    ###########################################################################
+    ## File handling methods below
+    ###########################################################################
+    
+    def checkDirty(self):
+        """
+        Public method to check dirty status and open a message window.
+        
+        @return flag indicating successful reset of the dirty flag (boolean)
+        """
+        if self.isModified():
+            fn = self.fileName
+            if fn is None:
+                fn = self.noName
+            res = EricMessageBox.okToClearData(
+                self,
+                self.tr("File Modified"),
+                self.tr("<p>The file <b>{0}</b> has unsaved changes.</p>")
+                .format(fn),
+                self.saveFile)
+            if res:
+                self.vm.setEditorName(self, self.fileName)
+            return res
+        
+        return True
+        
+    def revertToUnmodified(self):
+        """
+        Public method to revert back to the last saved state.
+        """
+        undo_ = True
+        while self.isModified():
+            if undo_:
+                # try undo first
+                if self.isUndoAvailable():
+                    self.undo()
+                else:
+                    undo_ = False
+            else:
+                # try redo next
+                if self.isRedoAvailable():
+                    self.redo()
+                else:
+                    break
+                    # Couldn't find the unmodified state
+    
+    def readFile(self, fn, createIt=False, encoding=""):
+        """
+        Public slot to read the text from a file.
+        
+        @param fn filename to read from (string)
+        @param createIt flag indicating the creation of a new file, if the
+            given one doesn't exist (boolean)
+        @param encoding encoding to be used to read the file (string)
+            (Note: this parameter overrides encoding detection)
+        """
+        self.__loadEditorConfig(fileName=fn)
+        
+        try:
+            with EricOverrideCursor():
+                if createIt and not os.path.exists(fn):
+                    with open(fn, "w"):
+                        pass
+                if encoding == "":
+                    encoding = self.__getEditorConfig("DefaultEncoding",
+                                                      nodefault=True)
+                if encoding:
+                    txt, self.encoding = Utilities.readEncodedFileWithEncoding(
+                        fn, encoding)
+                else:
+                    txt, self.encoding = Utilities.readEncodedFile(fn)
+        except (UnicodeDecodeError, OSError) as why:
+            EricMessageBox.critical(
+                self.vm,
+                self.tr('Open File'),
+                self.tr('<p>The file <b>{0}</b> could not be opened.</p>'
+                        '<p>Reason: {1}</p>')
+                    .format(fn, str(why)))
+            raise
+        
+        with EricOverrideCursor():
+            modified = False
+            
+            self.setText(txt)
+            
+            # get eric specific flags
+            self.__processFlags()
+            
+            # perform automatic EOL conversion
+            if (
+                self.__getEditorConfig("EOLMode", nodefault=True) or
+                Preferences.getEditor("AutomaticEOLConversion")
+            ):
+                self.convertEols(self.eolMode())
+            else:
+                fileEol = self.detectEolString(txt)
+                self.setEolModeByEolString(fileEol)
+            
+            self.extractTasks()
+            
+            self.setModified(modified)
+            self.lastModified = pathlib.Path(fn).stat().st_mtime
+    
+    def __convertTabs(self):
+        """
+        Private slot to convert tabulators to spaces.
+        """
+        if (
+            (not self.__getEditorConfig("TabForIndentation")) and
+            Preferences.getEditor("ConvertTabsOnLoad") and
+            not (self.lexer_ and
+                 self.lexer_.alwaysKeepTabs())
+        ):
+            txt = self.text()
+            txtExpanded = txt.expandtabs(self.__getEditorConfig("TabWidth"))
+            if txtExpanded != txt:
+                self.beginUndoAction()
+                self.setText(txt)
+                self.endUndoAction()
+                
+                self.setModified(True)
+    
+    def __removeTrailingWhitespace(self):
+        """
+        Private method to remove trailing whitespace.
+        """
+        searchRE = r"[ \t]+$"    # whitespace at the end of a line
+        
+        ok = self.findFirstTarget(searchRE, True, False, False, 0, 0)
+        self.beginUndoAction()
+        while ok:
+            self.replaceTarget("")
+            ok = self.findNextTarget()
+        self.endUndoAction()
+        
+    def writeFile(self, fn, backup=True):
+        """
+        Public slot to write the text to a file.
+        
+        @param fn filename to write to (string)
+        @param backup flag indicating to save a backup (boolean)
+        @return flag indicating success (boolean)
+        """
+        config = self.__loadEditorConfigObject(fn)
+        
+        eol = self.__getEditorConfig("EOLMode", nodefault=True, config=config)
+        if eol is not None:
+            self.convertEols(eol)
+        
+        if self.__getEditorConfig("StripTrailingWhitespace", config=config):
+            self.__removeTrailingWhitespace()
+        
+        txt = self.text()
+        
+        if self.__getEditorConfig("InsertFinalNewline", config=config):
+            eol = self.getLineSeparator()
+            if eol:
+                if len(txt) >= len(eol):
+                    if txt[-len(eol):] != eol:
+                        txt += eol
+                else:
+                    txt += eol
+        
+        # create a backup file, if the option is set
+        createBackup = backup and Preferences.getEditor("CreateBackupFile")
+        if createBackup:
+            if os.path.islink(fn):
+                fn = os.path.realpath(fn)
+            bfn = '{0}~'.format(fn)
+            try:
+                permissions = os.stat(fn).st_mode
+                perms_valid = True
+            except OSError:
+                # if there was an error, ignore it
+                perms_valid = False
+            with contextlib.suppress(OSError):
+                os.remove(bfn)
+            with contextlib.suppress(OSError):
+                os.rename(fn, bfn)
+        
+        # now write text to the file fn
+        try:
+            editorConfigEncoding = self.__getEditorConfig(
+                "DefaultEncoding", nodefault=True, config=config)
+            self.encoding = Utilities.writeEncodedFile(
+                fn, txt, self.encoding, forcedEncoding=editorConfigEncoding)
+            if createBackup and perms_valid:
+                os.chmod(fn, permissions)
+            return True
+        except (OSError, Utilities.CodingError, UnicodeError) as why:
+            EricMessageBox.critical(
+                self,
+                self.tr('Save File'),
+                self.tr('<p>The file <b>{0}</b> could not be saved.<br/>'
+                        'Reason: {1}</p>')
+                .format(fn, str(why)))
+            return False
+        
+    def __getSaveFileName(self, path=None):
+        """
+        Private method to get the name of the file to be saved.
+        
+        @param path directory to save the file in (string)
+        @return file name (string)
+        """
+        # save to project, if a project is loaded
+        if self.project.isOpen():
+            if (
+                self.fileName and
+                self.project.startswithProjectPath(self.fileName)
+            ):
+                path = os.path.dirname(self.fileName)
+            elif not self.fileName:
+                path = self.project.getProjectPath()
+        
+        if not path and self.fileName:
+            path = os.path.dirname(self.fileName)
+        if not path:
+            path = (
+                Preferences.getMultiProject("Workspace") or
+                Utilities.getHomeDir()
+            )
+        
+        from . import Lexers
+        if self.fileName:
+            filterPattern = "(*{0})".format(
+                os.path.splitext(self.fileName)[1])
+            for fileFilter in Lexers.getSaveFileFiltersList(True):
+                if filterPattern in fileFilter:
+                    defaultFilter = fileFilter
+                    break
+            else:
+                defaultFilter = Preferences.getEditor("DefaultSaveFilter")
+        else:
+            defaultFilter = Preferences.getEditor("DefaultSaveFilter")
+        fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+            self,
+            self.tr("Save File"),
+            path,
+            Lexers.getSaveFileFiltersList(True, True),
+            defaultFilter,
+            EricFileDialog.DontConfirmOverwrite)
+        
+        if fn:
+            if fn.endswith("."):
+                fn = fn[:-1]
+            
+            fpath = pathlib.Path(fn)
+            if not fpath.suffix:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fpath = fpath.with_suffix(ex)
+            if fpath.exists():
+                res = EricMessageBox.yesNo(
+                    self,
+                    self.tr("Save File"),
+                    self.tr("<p>The file <b>{0}</b> already exists."
+                            " Overwrite it?</p>").format(fpath),
+                    icon=EricMessageBox.Warning)
+                if not res:
+                    return ""
+        
+            return str(fpath)
+        
+        return ""
+    
+    def saveFileCopy(self, path=None):
+        """
+        Public method to save a copy of the file.
+        
+        @param path directory to save the file in (string)
+        @return flag indicating success (boolean)
+        """
+        fn = self.__getSaveFileName(path)
+        if not fn:
+            return False
+        
+        res = self.writeFile(fn)
+        if (
+            res and
+            self.project.isOpen() and
+            self.project.startswithProjectPath(fn)
+        ):
+            # save to project, if a project is loaded
+            self.project.appendFile(fn)
+        
+        return res
+        
+    def saveFile(self, saveas=False, path=None):
+        """
+        Public method to save the text to a file.
+        
+        @param saveas flag indicating a 'save as' action (boolean)
+        @param path directory to save the file in (string)
+        @return flag indicating success (boolean)
+        """
+        if not saveas and not self.isModified():
+            return False      # do nothing if text wasn't changed
+            
+        newName = None
+        if saveas or self.fileName == "":
+            saveas = True
+            
+            fn = self.__getSaveFileName(path)
+            if not fn:
+                return False
+            
+            newName = fn
+            
+            # save to project, if a project is loaded
+            if (
+                self.project.isOpen() and
+                self.project.startswithProjectPath(fn)
+            ):
+                editorConfigEol = self.__getEditorConfig(
+                    "EOLMode", nodefault=True,
+                    config=self.__loadEditorConfigObject(fn))
+                if editorConfigEol is not None:
+                    self.setEolMode(editorConfigEol)
+                else:
+                    self.setEolModeByEolString(self.project.getEolString())
+                self.convertEols(self.eolMode())
+        else:
+            fn = self.fileName
+        
+        self.__loadEditorConfig(fn)
+        self.editorAboutToBeSaved.emit(self.fileName)
+        if self.writeFile(fn):
+            if saveas:
+                self.__clearBreakpoints(self.fileName)
+            self.__setFileName(fn)
+            self.setModified(False)
+            self.setReadOnly(False)
+            self.setWindowTitle(self.fileName)
+            # get eric specific flags
+            changedFlags = self.__processFlags()
+            if not self.__lexerReset and "FileType" in changedFlags:
+                self.setLanguage(self.fileName)
+            
+            if saveas:
+                self.isResourcesFile = self.fileName.endswith(".qrc")
+                self.__initContextMenu()
+                self.editorRenamed.emit(self.fileName)
+                
+                # save to project, if a project is loaded
+                if (
+                    self.project.isOpen() and
+                    self.project.startswithProjectPath(fn)
+                ):
+                    self.project.appendFile(fn)
+                    self.addedToProject()
+                
+                self.setLanguage(self.fileName)
+            
+            self.lastModified = pathlib.Path(fn).stat().st_mtime
+            if newName is not None:
+                self.vm.addToRecentList(newName)
+            self.editorSaved.emit(self.fileName)
+            self.checkSyntax()
+            self.extractTasks()
+            self.__resetOnlineChangeTraceInfo()
+            self.__checkEncoding()
+            return True
+        else:
+            self.lastModified = (
+                pathlib.Path(fn).stat().st_mtime
+                if pathlib.Path(fn).exists() else
+                0
+            )
+            return False
+        
+    def saveFileAs(self, path=None, toProject=False):
+        """
+        Public slot to save a file with a new name.
+        
+        @param path directory to save the file in (string)
+        @param toProject flag indicating a save to project operation
+            (boolean)
+        @return tuple of two values (boolean, string) giving a success
+            indicator and the name of the saved file
+        """
+        return self.saveFile(True, path)
+        
+    def handleRenamed(self, fn):
+        """
+        Public slot to handle the editorRenamed signal.
+        
+        @param fn filename to be set for the editor (string).
+        """
+        self.__clearBreakpoints(fn)
+        
+        self.__setFileName(fn)
+        self.setWindowTitle(self.fileName)
+        
+        self.__loadEditorConfig()
+        
+        if self.lexer_ is None:
+            self.setLanguage(self.fileName)
+        
+        self.lastModified = pathlib.Path(fn).stat().st_mtime
+        self.vm.setEditorName(self, self.fileName)
+        self.__updateReadOnly(True)
+        
+    def fileRenamed(self, fn):
+        """
+        Public slot to handle the editorRenamed signal.
+        
+        @param fn filename to be set for the editor (string).
+        """
+        self.handleRenamed(fn)
+        if not self.inFileRenamed:
+            self.inFileRenamed = True
+            self.editorRenamed.emit(self.fileName)
+            self.inFileRenamed = False
+    
+    ###########################################################################
+    ## Utility methods below
+    ###########################################################################
+
+    def ensureVisible(self, line, expand=False):
+        """
+        Public slot to ensure, that the specified line is visible.
+        
+        @param line line number to make visible
+        @type int
+        @param expand flag indicating to expand all folds
+        @type bool
+        """
+        self.ensureLineVisible(line - 1)
+        if expand:
+            self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line - 1,
+                               QsciScintilla.SC_FOLDACTION_EXPAND)
+        
+    def ensureVisibleTop(self, line, expand=False):
+        """
+        Public slot to ensure, that the specified line is visible at the top
+        of the editor.
+        
+        @param line line number to make visible
+        @type int
+        @param expand flag indicating to expand all folds
+        @type bool
+        """
+        self.ensureVisible(line)
+        self.setFirstVisibleLine(line - 1)
+        self.ensureCursorVisible()
+        if expand:
+            self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line - 1,
+                               QsciScintilla.SC_FOLDACTION_EXPAND)
+        
+    def __marginClicked(self, margin, line, modifiers):
+        """
+        Private slot to handle the marginClicked signal.
+        
+        @param margin id of the clicked margin (integer)
+        @param line line number of the click (integer)
+        @param modifiers keyboard modifiers (Qt.KeyboardModifiers)
+        """
+        if margin == self.__bmMargin:
+            self.toggleBookmark(line + 1)
+        elif margin == self.__bpMargin:
+            self.__toggleBreakpoint(line + 1)
+        elif margin == self.__indicMargin:
+            if self.markersAtLine(line) & (1 << self.syntaxerror):
+                self.__showSyntaxError(line)
+            elif self.markersAtLine(line) & (1 << self.warning):
+                self.__showWarning(line)
+        
+    def handleMonospacedEnable(self):
+        """
+        Public slot to handle the Use Monospaced Font context menu entry.
+        """
+        if self.menuActs["MonospacedFont"].isChecked():
+            if not self.lexer_:
+                self.setMonospaced(True)
+        else:
+            if self.lexer_:
+                self.lexer_.readSettings(
+                    Preferences.getSettings(), "Scintilla")
+                if self.lexer_.hasSubstyles():
+                    self.lexer_.readSubstyles(self)
+                self.lexer_.initProperties()
+            self.setMonospaced(False)
+            self.__setMarginsDisplay()
+        
+    def getWordBoundaries(self, line, index, useWordChars=True):
+        """
+        Public method to get the word boundaries at a position.
+        
+        @param line number of line to look at (int)
+        @param index position to look at (int)
+        @param useWordChars flag indicating to use the wordCharacters
+            method (boolean)
+        @return tuple with start and end indexes of the word at the position
+            (integer, integer)
+        """
+        wc = self.wordCharacters()
+        if wc is None or not useWordChars:
+            pattern = r"\b[\w_]+\b"
+        else:
+            wc = re.sub(r'\w', "", wc)
+            pattern = r"\b[\w{0}]+\b".format(re.escape(wc))
+        rx = (
+            re.compile(pattern)
+            if self.caseSensitive() else
+            re.compile(pattern, re.IGNORECASE)
+        )
+        
+        text = self.text(line)
+        for match in rx.finditer(text):
+            start, end = match.span()
+            if start <= index <= end:
+                return (start, end)
+        
+        return (index, index)
+    
+    def getWord(self, line, index, direction=0, useWordChars=True):
+        """
+        Public method to get the word at a position.
+        
+        @param line number of line to look at (int)
+        @param index position to look at (int)
+        @param direction direction to look in (0 = whole word, 1 = left,
+            2 = right)
+        @param useWordChars flag indicating to use the wordCharacters
+            method (boolean)
+        @return the word at that position (string)
+        """
+        start, end = self.getWordBoundaries(line, index, useWordChars)
+        if direction == 1:
+            end = index
+        elif direction == 2:
+            start = index
+        if end > start:
+            text = self.text(line)
+            word = text[start:end]
+        else:
+            word = ''
+        return word
+        
+    def getWordLeft(self, line, index):
+        """
+        Public method to get the word to the left of a position.
+        
+        @param line number of line to look at (int)
+        @param index position to look at (int)
+        @return the word to the left of that position (string)
+        """
+        return self.getWord(line, index, 1)
+        
+    def getWordRight(self, line, index):
+        """
+        Public method to get the word to the right of a position.
+        
+        @param line number of line to look at (int)
+        @param index position to look at (int)
+        @return the word to the right of that position (string)
+        """
+        return self.getWord(line, index, 2)
+        
+    def getCurrentWord(self):
+        """
+        Public method to get the word at the current position.
+        
+        @return the word at that current position (string)
+        """
+        line, index = self.getCursorPosition()
+        return self.getWord(line, index)
+        
+    def getCurrentWordBoundaries(self):
+        """
+        Public method to get the word boundaries at the current position.
+        
+        @return tuple with start and end indexes of the current word
+            (integer, integer)
+        """
+        line, index = self.getCursorPosition()
+        return self.getWordBoundaries(line, index)
+        
+    def selectWord(self, line, index):
+        """
+        Public method to select the word at a position.
+        
+        @param line number of line to look at (int)
+        @param index position to look at (int)
+        """
+        start, end = self.getWordBoundaries(line, index)
+        self.setSelection(line, start, line, end)
+        
+    def selectCurrentWord(self):
+        """
+        Public method to select the current word.
+        """
+        line, index = self.getCursorPosition()
+        self.selectWord(line, index)
+        
+    def __getCharacter(self, pos):
+        """
+        Private method to get the character to the left of the current position
+        in the current line.
+        
+        @param pos position to get character at (integer)
+        @return requested character or "", if there are no more (string) and
+            the next position (i.e. pos - 1)
+        """
+        if pos <= 0:
+            return "", pos
+        
+        pos = self.positionBefore(pos)
+        ch = self.charAt(pos)
+        
+        # Don't go past the end of the previous line
+        if ch in ('\n', '\r'):
+            return "", pos
+        
+        return ch, pos
+    
+    def getSearchText(self, selectionOnly=False):
+        """
+        Public method to determine the selection or the current word for the
+        next search operation.
+        
+        @param selectionOnly flag indicating that only selected text should be
+            returned (boolean)
+        @return selection or current word (string)
+        """
+        if self.hasSelectedText():
+            text = self.selectedText()
+            if '\r' in text or '\n' in text:
+                # the selection contains at least a newline, it is
+                # unlikely to be the expression to search for
+                return ''
+            
+            return text
+        
+        if not selectionOnly:
+            # no selected text, determine the word at the current position
+            return self.getCurrentWord()
+        
+        return ''
+        
+    def setSearchIndicator(self, startPos, indicLength):
+        """
+        Public method to set a search indicator for the given range.
+        
+        @param startPos start position of the indicator (integer)
+        @param indicLength length of the indicator (integer)
+        """
+        self.setIndicatorRange(self.searchIndicator, startPos, indicLength)
+        line = self.lineIndexFromPosition(startPos)[0]
+        if line not in self.__searchIndicatorLines:
+            self.__searchIndicatorLines.append(line)
+        
+    def clearSearchIndicators(self):
+        """
+        Public method to clear all search indicators.
+        """
+        self.clearAllIndicators(self.searchIndicator)
+        self.__markedText = ""
+        self.__searchIndicatorLines = []
+        self.__markerMap.update()
+        
+    def __markOccurrences(self):
+        """
+        Private method to mark all occurrences of the current word.
+        """
+        word = self.getCurrentWord()
+        if not word:
+            self.clearSearchIndicators()
+            return
+        
+        if self.__markedText == word:
+            return
+        
+        self.clearSearchIndicators()
+        ok = self.findFirstTarget(word, False, self.caseSensitive(), True,
+                                  0, 0)
+        while ok:
+            tgtPos, tgtLen = self.getFoundTarget()
+            self.setSearchIndicator(tgtPos, tgtLen)
+            ok = self.findNextTarget()
+        self.__markedText = word
+        self.__markerMap.update()
+    
+    def getSearchIndicatorLines(self):
+        """
+        Public method to get the lines containing a search indicator.
+        
+        @return list of lines containing a search indicator (list of integer)
+        """
+        return self.__searchIndicatorLines[:]
+    
+    def updateMarkerMap(self):
+        """
+        Public method to initiate an update of the marker map.
+        """
+        self.__markerMap.update()
+    
+    ###########################################################################
+    ## Highlighting marker handling methods below
+    ###########################################################################
+    
+    def setHighlight(self, startLine, startIndex, endLine, endIndex):
+        """
+        Public method to set a text highlight.
+        
+        @param startLine line of the highlight start
+        @type int
+        @param startIndex index of the highlight start
+        @type int
+        @param endLine line of the highlight end
+        @type int
+        @param endIndex index of the highlight end
+        @type int
+        """
+        self.setIndicator(self.highlightIndicator, startLine, startIndex,
+                          endLine, endIndex)
+    
+    def clearAllHighlights(self):
+        """
+        Public method to clear all highlights.
+        """
+        self.clearAllIndicators(self.highlightIndicator)
+    
+    def clearHighlight(self, startLine, startIndex, endLine, endIndex):
+        """
+        Public method to clear a text highlight.
+        
+        @param startLine line of the highlight start
+        @type int
+        @param startIndex index of the highlight start
+        @type int
+        @param endLine line of the highlight end
+        @type int
+        @param endIndex index of the highlight end
+        @type int
+        """
+        self.clearIndicator(self.highlightIndicator, startLine, startIndex,
+                            endLine, endIndex)
+    
+    ###########################################################################
+    ## Comment handling methods below
+    ###########################################################################
+    
+    def __isCommentedLine(self, line, commentStr):
+        """
+        Private method to check, if the given line is a comment line as
+        produced by the configured comment rules.
+        
+        @param line text of the line to check (string)
+        @param commentStr comment string to check against (string)
+        @return flag indicating a commented line (boolean)
+        """
+        if Preferences.getEditor("CommentColumn0"):
+            return line.startswith(commentStr)
+        else:
+            return line.strip().startswith(commentStr)
+    
+    def toggleCommentBlock(self):
+        """
+        Public slot to toggle the comment of a block.
+        
+        If the line of the cursor or the selection is not commented, it will
+        be commented. If it is commented, the comment block will be removed.
+        The later works independent of the current selection.
+        """
+        if self.lexer_ is None or not self.lexer_.canBlockComment():
+            return
+        
+        commentStr = self.lexer_.commentStr()
+        line, index = self.getCursorPosition()
+        
+        # check if line starts with our comment string (i.e. was commented
+        # by our comment...() slots
+        if (
+            self.hasSelectedText() and
+            self.__isCommentedLine(self.text(self.getSelection()[0]),
+                                   commentStr)
+        ):
+            self.uncommentLineOrSelection()
+        elif not self.__isCommentedLine(self.text(line), commentStr):
+            # it doesn't, so comment the line or selection
+            self.commentLineOrSelection()
+        else:
+            # determine the start of the comment block
+            begline = line
+            while (
+                begline > 0 and
+                self.__isCommentedLine(self.text(begline - 1), commentStr)
+            ):
+                begline -= 1
+            # determine the end of the comment block
+            endline = line
+            lines = self.lines()
+            while (
+                endline < lines and
+                self.__isCommentedLine(self.text(endline + 1), commentStr)
+            ):
+                endline += 1
+            
+            self.setSelection(begline, 0, endline, self.lineLength(endline))
+            self.uncommentLineOrSelection()
+            
+            # reset the cursor
+            self.setCursorPosition(line, index - len(commentStr))
+        
+    def commentLine(self):
+        """
+        Public slot to comment the current line.
+        """
+        if self.lexer_ is None or not self.lexer_.canBlockComment():
+            return
+        
+        line, index = self.getCursorPosition()
+        self.beginUndoAction()
+        if Preferences.getEditor("CommentColumn0"):
+            self.insertAt(self.lexer_.commentStr(), line, 0)
+        else:
+            lineText = self.text(line)
+            pos = len(lineText.replace(lineText.lstrip(" \t"), ""))
+            self.insertAt(self.lexer_.commentStr(), line, pos)
+        self.endUndoAction()
+        
+    def uncommentLine(self):
+        """
+        Public slot to uncomment the current line.
+        """
+        if self.lexer_ is None or not self.lexer_.canBlockComment():
+            return
+        
+        commentStr = self.lexer_.commentStr()
+        line, index = self.getCursorPosition()
+        
+        # check if line starts with our comment string (i.e. was commented
+        # by our comment...() slots
+        if not self.__isCommentedLine(self.text(line), commentStr):
+            return
+        
+        # now remove the comment string
+        self.beginUndoAction()
+        if Preferences.getEditor("CommentColumn0"):
+            self.setSelection(line, 0, line, len(commentStr))
+        else:
+            lineText = self.text(line)
+            pos = len(lineText.replace(lineText.lstrip(" \t"), ""))
+            self.setSelection(line, pos, line, pos + len(commentStr))
+        self.removeSelectedText()
+        self.endUndoAction()
+        
+    def commentSelection(self):
+        """
+        Public slot to comment the current selection.
+        """
+        if self.lexer_ is None or not self.lexer_.canBlockComment():
+            return
+        
+        if not self.hasSelectedText():
+            return
+        
+        commentStr = self.lexer_.commentStr()
+        
+        # get the selection boundaries
+        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
+        endLine = lineTo if indexTo else lineTo - 1
+        
+        self.beginUndoAction()
+        # iterate over the lines
+        for line in range(lineFrom, endLine + 1):
+            if Preferences.getEditor("CommentColumn0"):
+                self.insertAt(commentStr, line, 0)
+            else:
+                lineText = self.text(line)
+                pos = len(lineText.replace(lineText.lstrip(" \t"), ""))
+                self.insertAt(commentStr, line, pos)
+        
+        # change the selection accordingly
+        self.setSelection(lineFrom, 0, endLine + 1, 0)
+        self.endUndoAction()
+        
+    def uncommentSelection(self):
+        """
+        Public slot to uncomment the current selection.
+        """
+        if self.lexer_ is None or not self.lexer_.canBlockComment():
+            return
+        
+        if not self.hasSelectedText():
+            return
+        
+        commentStr = self.lexer_.commentStr()
+        
+        # get the selection boundaries
+        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
+        endLine = lineTo if indexTo else lineTo - 1
+        
+        self.beginUndoAction()
+        # iterate over the lines
+        for line in range(lineFrom, endLine + 1):
+            # check if line starts with our comment string (i.e. was commented
+            # by our comment...() slots
+            if not self.__isCommentedLine(self.text(line), commentStr):
+                continue
+            
+            if Preferences.getEditor("CommentColumn0"):
+                self.setSelection(line, 0, line, len(commentStr))
+            else:
+                lineText = self.text(line)
+                pos = len(lineText.replace(lineText.lstrip(" \t"), ""))
+                self.setSelection(line, pos, line, pos + len(commentStr))
+            self.removeSelectedText()
+            
+            # adjust selection start
+            if line == lineFrom:
+                indexFrom -= len(commentStr)
+                if indexFrom < 0:
+                    indexFrom = 0
+            
+            # adjust selection end
+            if line == lineTo:
+                indexTo -= len(commentStr)
+                if indexTo < 0:
+                    indexTo = 0
+            
+        # change the selection accordingly
+        self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
+        self.endUndoAction()
+        
+    def commentLineOrSelection(self):
+        """
+        Public slot to comment the current line or current selection.
+        """
+        if self.hasSelectedText():
+            self.commentSelection()
+        else:
+            self.commentLine()
+
+    def uncommentLineOrSelection(self):
+        """
+        Public slot to uncomment the current line or current selection.
+        """
+        if self.hasSelectedText():
+            self.uncommentSelection()
+        else:
+            self.uncommentLine()
+
+    def streamCommentLine(self):
+        """
+        Public slot to stream comment the current line.
+        """
+        if self.lexer_ is None or not self.lexer_.canStreamComment():
+            return
+        
+        commentStr = self.lexer_.streamCommentStr()
+        line, index = self.getCursorPosition()
+        
+        self.beginUndoAction()
+        self.insertAt(commentStr['end'], line, self.lineLength(line))
+        self.insertAt(commentStr['start'], line, 0)
+        self.endUndoAction()
+        
+    def streamCommentSelection(self):
+        """
+        Public slot to comment the current selection.
+        """
+        if self.lexer_ is None or not self.lexer_.canStreamComment():
+            return
+        
+        if not self.hasSelectedText():
+            return
+        
+        commentStr = self.lexer_.streamCommentStr()
+        
+        # get the selection boundaries
+        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
+        if indexTo == 0:
+            endLine = lineTo - 1
+            endIndex = self.lineLength(endLine)
+        else:
+            endLine = lineTo
+            endIndex = indexTo
+        
+        self.beginUndoAction()
+        self.insertAt(commentStr['end'], endLine, endIndex)
+        self.insertAt(commentStr['start'], lineFrom, indexFrom)
+        
+        # change the selection accordingly
+        if indexTo > 0:
+            indexTo += len(commentStr['end'])
+            if lineFrom == endLine:
+                indexTo += len(commentStr['start'])
+        self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
+        self.endUndoAction()
+        
+    def streamCommentLineOrSelection(self):
+        """
+        Public slot to stream comment the current line or current selection.
+        """
+        if self.hasSelectedText():
+            self.streamCommentSelection()
+        else:
+            self.streamCommentLine()
+        
+    def boxCommentLine(self):
+        """
+        Public slot to box comment the current line.
+        """
+        if self.lexer_ is None or not self.lexer_.canBoxComment():
+            return
+        
+        commentStr = self.lexer_.boxCommentStr()
+        line, index = self.getCursorPosition()
+        
+        eol = self.getLineSeparator()
+        self.beginUndoAction()
+        self.insertAt(eol, line, self.lineLength(line))
+        self.insertAt(commentStr['end'], line + 1, 0)
+        self.insertAt(commentStr['middle'], line, 0)
+        self.insertAt(eol, line, 0)
+        self.insertAt(commentStr['start'], line, 0)
+        self.endUndoAction()
+        
+    def boxCommentSelection(self):
+        """
+        Public slot to box comment the current selection.
+        """
+        if self.lexer_ is None or not self.lexer_.canBoxComment():
+            return
+        
+        if not self.hasSelectedText():
+            return
+            
+        commentStr = self.lexer_.boxCommentStr()
+        
+        # get the selection boundaries
+        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
+        endLine = lineTo if indexTo else lineTo - 1
+        
+        self.beginUndoAction()
+        # iterate over the lines
+        for line in range(lineFrom, endLine + 1):
+            self.insertAt(commentStr['middle'], line, 0)
+        
+        # now do the comments before and after the selection
+        eol = self.getLineSeparator()
+        self.insertAt(eol, endLine, self.lineLength(endLine))
+        self.insertAt(commentStr['end'], endLine + 1, 0)
+        self.insertAt(eol, lineFrom, 0)
+        self.insertAt(commentStr['start'], lineFrom, 0)
+        
+        # change the selection accordingly
+        self.setSelection(lineFrom, 0, endLine + 3, 0)
+        self.endUndoAction()
+        
+    def boxCommentLineOrSelection(self):
+        """
+        Public slot to box comment the current line or current selection.
+        """
+        if self.hasSelectedText():
+            self.boxCommentSelection()
+        else:
+            self.boxCommentLine()
+    
+    ###########################################################################
+    ## Indentation handling methods below
+    ###########################################################################
+
+    def __indentLine(self, indent=True):
+        """
+        Private method to indent or unindent the current line.
+        
+        @param indent flag indicating an indent operation (boolean)
+                <br />If the flag is true, an indent operation is performed.
+                Otherwise the current line is unindented.
+        """
+        line, index = self.getCursorPosition()
+        self.beginUndoAction()
+        if indent:
+            self.indent(line)
+        else:
+            self.unindent(line)
+        self.endUndoAction()
+        if indent:
+            self.setCursorPosition(line, index + self.indentationWidth())
+        else:
+            self.setCursorPosition(line, index - self.indentationWidth())
+        
+    def __indentSelection(self, indent=True):
+        """
+        Private method to indent or unindent the current selection.
+        
+        @param indent flag indicating an indent operation (boolean)
+                <br />If the flag is true, an indent operation is performed.
+                Otherwise the current line is unindented.
+        """
+        if not self.hasSelectedText():
+            return
+        
+        # get the selection
+        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
+        endLine = lineTo if indexTo else lineTo - 1
+        
+        self.beginUndoAction()
+        # iterate over the lines
+        for line in range(lineFrom, endLine + 1):
+            if indent:
+                self.indent(line)
+            else:
+                self.unindent(line)
+        self.endUndoAction()
+        if indent:
+            if indexTo == 0:
+                self.setSelection(
+                    lineFrom, indexFrom + self.indentationWidth(),
+                    lineTo, 0)
+            else:
+                self.setSelection(
+                    lineFrom, indexFrom + self.indentationWidth(),
+                    lineTo, indexTo + self.indentationWidth())
+        else:
+            indexStart = indexFrom - self.indentationWidth()
+            if indexStart < 0:
+                indexStart = 0
+            indexEnd = indexTo - self.indentationWidth()
+            if indexEnd < 0:
+                indexEnd = 0
+            self.setSelection(lineFrom, indexStart, lineTo, indexEnd)
+        
+    def indentLineOrSelection(self):
+        """
+        Public slot to indent the current line or current selection.
+        """
+        if self.hasSelectedText():
+            self.__indentSelection(True)
+        else:
+            self.__indentLine(True)
+        
+    def unindentLineOrSelection(self):
+        """
+        Public slot to unindent the current line or current selection.
+        """
+        if self.hasSelectedText():
+            self.__indentSelection(False)
+        else:
+            self.__indentLine(False)
+        
+    def smartIndentLineOrSelection(self):
+        """
+        Public slot to indent current line smartly.
+        """
+        if self.hasSelectedText():
+            if self.lexer_ and self.lexer_.hasSmartIndent():
+                self.lexer_.smartIndentSelection(self)
+            else:
+                self.__indentSelection(True)
+        else:
+            if self.lexer_ and self.lexer_.hasSmartIndent():
+                self.lexer_.smartIndentLine(self)
+            else:
+                self.__indentLine(True)
+        
+    def gotoLine(self, line, pos=1, firstVisible=False, expand=False):
+        """
+        Public slot to jump to the beginning of a line.
+        
+        @param line line number to go to
+        @type int
+        @param pos position in line to go to
+        @type int
+        @param firstVisible flag indicating to make the line the first
+            visible line
+        @type bool
+        @param expand flag indicating to expand all folds
+        @type bool
+        """
+        self.setCursorPosition(line - 1, pos - 1)
+        if firstVisible:
+            self.ensureVisibleTop(line, expand)
+        else:
+            self.ensureVisible(line, expand)
+    
+    def __textChanged(self):
+        """
+        Private slot to handle a change of the editor text.
+        
+        This slot defers the handling to the next time the event loop
+        is run in order to ensure, that cursor position has been updated
+        by the underlying Scintilla editor.
+        """
+        QTimer.singleShot(0, self.__saveLastEditPosition)
+    
+    def __saveLastEditPosition(self):
+        """
+        Private slot to record the last edit position.
+        """
+        self.__lastEditPosition = self.getCursorPosition()
+        self.lastEditPositionAvailable.emit()
+    
+    def isLastEditPositionAvailable(self):
+        """
+        Public method to check, if a last edit position is available.
+        
+        @return flag indicating availability (boolean)
+        """
+        return self.__lastEditPosition is not None
+    
+    def gotoLastEditPosition(self):
+        """
+        Public method to move the cursor to the last edit position.
+        """
+        self.setCursorPosition(*self.__lastEditPosition)
+        self.ensureVisible(self.__lastEditPosition[0])
+    
+    def gotoMethodClass(self, goUp=False):
+        """
+        Public method to go to the next Python method or class definition.
+        
+        @param goUp flag indicating the move direction (boolean)
+        """
+        if self.isPyFile() or self.isRubyFile():
+            lineNo = self.getCursorPosition()[0]
+            line = self.text(lineNo)
+            if line.strip().startswith(("class ", "def ", "module ")):
+                if goUp:
+                    lineNo -= 1
+                else:
+                    lineNo += 1
+            while True:
+                if goUp and lineNo < 0:
+                    self.setCursorPosition(0, 0)
+                    self.ensureVisible(0)
+                    return
+                elif not goUp and lineNo == self.lines():
+                    lineNo = self.lines() - 1
+                    self.setCursorPosition(lineNo, self.lineLength(lineNo))
+                    self.ensureVisible(lineNo)
+                    return
+                
+                line = self.text(lineNo)
+                if line.strip().startswith(("class ", "def ", "module ")):
+                    # try 'def ' first because it occurs more often
+                    first = line.find("def ")
+                    if first > -1:
+                        first += 4
+                    else:
+                        first = line.find("class ")
+                        if first > -1:
+                            first += 6
+                        else:
+                            first = line.find("module ") + 7
+                    match = re.search("[:(]", line)
+                    if match:
+                        end = match.start()
+                    else:
+                        end = self.lineLength(lineNo) - 1
+                    self.setSelection(lineNo, first, lineNo, end)
+                    self.ensureVisible(lineNo)
+                    return
+                
+                if goUp:
+                    lineNo -= 1
+                else:
+                    lineNo += 1
+    
+    ###########################################################################
+    ## Setup methods below
+    ###########################################################################
+
+    def readSettings(self):
+        """
+        Public slot to read the settings into our lexer.
+        """
+        # read the lexer settings and reinit the properties
+        if self.lexer_ is not None:
+            self.lexer_.readSettings(Preferences.getSettings(), "Scintilla")
+            if self.lexer_.hasSubstyles():
+                self.lexer_.readSubstyles(self)
+            self.lexer_.initProperties()
+            
+            self.lexer_.setDefaultColor(self.lexer_.color(0))
+            self.lexer_.setDefaultPaper(self.lexer_.paper(0))
+        
+        self.__bindLexer(self.fileName)
+        self.recolor()
+        
+        # read the typing completer settings
+        if self.completer is not None:
+            self.completer.readSettings()
+        
+        # set the line marker colours or pixmap
+        if Preferences.getEditor("LineMarkersBackground"):
+            self.markerDefine(QsciScintilla.MarkerSymbol.Background,
+                              self.currentline)
+            self.markerDefine(QsciScintilla.MarkerSymbol.Background,
+                              self.errorline)
+            self.__setLineMarkerColours()
+        else:
+            self.markerDefine(
+                UI.PixmapCache.getPixmap("currentLineMarker"),
+                self.currentline)
+            self.markerDefine(
+                UI.PixmapCache.getPixmap("errorLineMarker"),
+                self.errorline)
+        
+        # set the text display
+        self.__setTextDisplay()
+        
+        # set margin 0 and 2 configuration
+        self.__setMarginsDisplay()
+        
+        # set the auto-completion function
+        self.__acCache.setSize(
+            Preferences.getEditor("AutoCompletionCacheSize"))
+        self.__acCache.setMaximumCacheTime(
+            Preferences.getEditor("AutoCompletionCacheTime"))
+        self.__acCacheEnabled = Preferences.getEditor(
+            "AutoCompletionCacheEnabled")
+        acTimeout = Preferences.getEditor("AutoCompletionTimeout")
+        if acTimeout != self.__acTimer.interval:
+            self.__acTimer.setInterval(acTimeout)
+        self.__setAutoCompletion()
+        
+        # set the calltips function
+        self.__setCallTips()
+        
+        # set the autosave flags
+        self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
+        
+        if Preferences.getEditor("MiniContextMenu") != self.miniMenu:
+            # regenerate context menu
+            self.__initContextMenu()
+        else:
+            # set checked context menu items
+            self.menuActs["AutoCompletionEnable"].setChecked(
+                self.autoCompletionThreshold() != -1)
+            self.menuActs["MonospacedFont"].setChecked(
+                self.useMonospaced)
+            self.menuActs["AutosaveEnable"].setChecked(
+                self.autosaveEnabled and not self.autosaveManuallyDisabled)
+        
+        # regenerate the margins context menu(s)
+        self.__initContextMenuMargins()
+        
+        if Preferences.getEditor("MarkOccurrencesEnabled"):
+            self.__markOccurrencesTimer.setInterval(
+                Preferences.getEditor("MarkOccurrencesTimeout"))
+        else:
+            self.__markOccurrencesTimer.stop()
+            self.clearSearchIndicators()
+        
+        if Preferences.getEditor("OnlineSyntaxCheck"):
+            self.__onlineSyntaxCheckTimer.setInterval(
+                Preferences.getEditor("OnlineSyntaxCheckInterval") * 1000)
+        else:
+            self.__onlineSyntaxCheckTimer.stop()
+        
+        if Preferences.getEditor("OnlineChangeTrace"):
+            self.__onlineChangeTraceTimer.setInterval(
+                Preferences.getEditor("OnlineChangeTraceInterval"))
+        else:
+            self.__onlineChangeTraceTimer.stop()
+            self.__deleteAllChangeMarkers()
+        self.markerDefine(self.__createChangeMarkerPixmap(
+            "OnlineChangeTraceMarkerUnsaved"), self.__changeMarkerUnsaved)
+        self.markerDefine(self.__createChangeMarkerPixmap(
+            "OnlineChangeTraceMarkerSaved"), self.__changeMarkerSaved)
+        
+        # refresh the annotations display
+        self.__refreshAnnotations()
+        
+        self.__markerMap.setMapPosition(
+            Preferences.getEditor("ShowMarkerMapOnRight"))
+        self.__markerMap.initColors()
+        
+        self.setLanguage(self.fileName, propagate=False)
+        
+        self.settingsRead.emit()
+    
+    def __setLineMarkerColours(self):
+        """
+        Private method to set the line marker colours.
+        """
+        self.setMarkerForegroundColor(
+            Preferences.getEditorColour("CurrentMarker"), self.currentline)
+        self.setMarkerBackgroundColor(
+            Preferences.getEditorColour("CurrentMarker"), self.currentline)
+        self.setMarkerForegroundColor(
+            Preferences.getEditorColour("ErrorMarker"), self.errorline)
+        self.setMarkerBackgroundColor(
+            Preferences.getEditorColour("ErrorMarker"), self.errorline)
+        
+    def __setMarginsDisplay(self):
+        """
+        Private method to configure margins 0 and 2.
+        """
+        # set the settings for all margins
+        self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont"))
+        self.setMarginsForegroundColor(
+            Preferences.getEditorColour("MarginsForeground"))
+        self.setMarginsBackgroundColor(
+            Preferences.getEditorColour("MarginsBackground"))
+        
+        # reset standard margins settings
+        for margin in range(5):
+            self.setMarginLineNumbers(margin, False)
+            self.setMarginMarkerMask(margin, 0)
+            self.setMarginWidth(margin, 0)
+            self.setMarginSensitivity(margin, False)
+        
+        # set marker margin(s) settings
+        self.__bmMargin = 0
+        self.__linenoMargin = 1
+        self.__bpMargin = 2
+        self.__foldMargin = 3
+        self.__indicMargin = 4
+        
+        marginBmMask = (1 << self.bookmark)
+        self.setMarginWidth(self.__bmMargin, 16)
+        self.setMarginSensitivity(self.__bmMargin, True)
+        self.setMarginMarkerMask(self.__bmMargin, marginBmMask)
+        
+        marginBpMask = (
+            (1 << self.breakpoint) |
+            (1 << self.cbreakpoint) |
+            (1 << self.tbreakpoint) |
+            (1 << self.tcbreakpoint) |
+            (1 << self.dbreakpoint)
+        )
+        self.setMarginWidth(self.__bpMargin, 16)
+        self.setMarginSensitivity(self.__bpMargin, True)
+        self.setMarginMarkerMask(self.__bpMargin, marginBpMask)
+        
+        marginIndicMask = (
+            (1 << self.syntaxerror) |
+            (1 << self.notcovered) |
+            (1 << self.taskmarker) |
+            (1 << self.warning) |
+            (1 << self.__changeMarkerUnsaved) |
+            (1 << self.__changeMarkerSaved) |
+            (1 << self.currentline) |
+            (1 << self.errorline)
+        )
+        self.setMarginWidth(self.__indicMargin, 16)
+        self.setMarginSensitivity(self.__indicMargin, True)
+        self.setMarginMarkerMask(self.__indicMargin, marginIndicMask)
+        
+        # set linenumber margin settings
+        linenoMargin = Preferences.getEditor("LinenoMargin")
+        self.setMarginLineNumbers(self.__linenoMargin, linenoMargin)
+        if linenoMargin:
+            self.__resizeLinenoMargin()
+        else:
+            self.setMarginWidth(self.__linenoMargin, 0)
+        
+        # set folding margin settings
+        if Preferences.getEditor("FoldingMargin"):
+            self.setMarginWidth(self.__foldMargin, 16)
+            folding = Preferences.getEditor("FoldingStyle")
+            self.setFolding(folding, self.__foldMargin)
+            self.setFoldMarginColors(
+                Preferences.getEditorColour("FoldmarginBackground"),
+                Preferences.getEditorColour("FoldmarginBackground"))
+            self.setFoldMarkersColors(
+                Preferences.getEditorColour("FoldMarkersForeground"),
+                Preferences.getEditorColour("FoldMarkersBackground"))
+        else:
+            self.setMarginWidth(self.__foldMargin, 0)
+            self.setFolding(QsciScintilla.FoldStyle.NoFoldStyle.value,
+                            self.__foldMargin)
+    
+    def __resizeLinenoMargin(self):
+        """
+        Private slot to resize the line numbers margin.
+        """
+        linenoMargin = Preferences.getEditor("LinenoMargin")
+        if linenoMargin:
+            self.setMarginWidth(
+                self.__linenoMargin, '8' * (len(str(self.lines())) + 1))
+    
+    def __setTabAndIndent(self):
+        """
+        Private method to set indentation size and style and tab width.
+        """
+        self.setTabWidth(self.__getEditorConfig("TabWidth"))
+        self.setIndentationWidth(self.__getEditorConfig("IndentWidth"))
+        if self.lexer_ and self.lexer_.alwaysKeepTabs():
+            self.setIndentationsUseTabs(True)
+        else:
+            self.setIndentationsUseTabs(
+                self.__getEditorConfig("TabForIndentation"))
+    
+    def __setTextDisplay(self):
+        """
+        Private method to configure the text display.
+        """
+        self.__setTabAndIndent()
+        
+        self.setTabIndents(Preferences.getEditor("TabIndents"))
+        self.setBackspaceUnindents(Preferences.getEditor("TabIndents"))
+        self.setIndentationGuides(Preferences.getEditor("IndentationGuides"))
+        self.setIndentationGuidesBackgroundColor(
+            Preferences.getEditorColour("IndentationGuidesBackground"))
+        self.setIndentationGuidesForegroundColor(
+            Preferences.getEditorColour("IndentationGuidesForeground"))
+        if Preferences.getEditor("ShowWhitespace"):
+            self.setWhitespaceVisibility(
+                QsciScintilla.WhitespaceVisibility.WsVisible)
+            with contextlib.suppress(AttributeError):
+                self.setWhitespaceForegroundColor(
+                    Preferences.getEditorColour("WhitespaceForeground"))
+                self.setWhitespaceBackgroundColor(
+                    Preferences.getEditorColour("WhitespaceBackground"))
+                self.setWhitespaceSize(
+                    Preferences.getEditor("WhitespaceSize"))
+        else:
+            self.setWhitespaceVisibility(
+                QsciScintilla.WhitespaceVisibility.WsInvisible)
+        self.setEolVisibility(Preferences.getEditor("ShowEOL"))
+        self.setAutoIndent(Preferences.getEditor("AutoIndentation"))
+        if Preferences.getEditor("BraceHighlighting"):
+            self.setBraceMatching(QsciScintilla.BraceMatch.SloppyBraceMatch)
+        else:
+            self.setBraceMatching(QsciScintilla.BraceMatch.NoBraceMatch)
+        self.setMatchedBraceForegroundColor(
+            Preferences.getEditorColour("MatchingBrace"))
+        self.setMatchedBraceBackgroundColor(
+            Preferences.getEditorColour("MatchingBraceBack"))
+        self.setUnmatchedBraceForegroundColor(
+            Preferences.getEditorColour("NonmatchingBrace"))
+        self.setUnmatchedBraceBackgroundColor(
+            Preferences.getEditorColour("NonmatchingBraceBack"))
+        if Preferences.getEditor("CustomSelectionColours"):
+            self.setSelectionBackgroundColor(
+                Preferences.getEditorColour("SelectionBackground"))
+        else:
+            self.setSelectionBackgroundColor(
+                QApplication.palette().color(QPalette.ColorRole.Highlight))
+        if Preferences.getEditor("ColourizeSelText"):
+            self.resetSelectionForegroundColor()
+        elif Preferences.getEditor("CustomSelectionColours"):
+            self.setSelectionForegroundColor(
+                Preferences.getEditorColour("SelectionForeground"))
+        else:
+            self.setSelectionForegroundColor(
+                QApplication.palette().color(
+                    QPalette.ColorRole.HighlightedText))
+        self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol"))
+        self.setCaretForegroundColor(
+            Preferences.getEditorColour("CaretForeground"))
+        self.setCaretLineBackgroundColor(
+            Preferences.getEditorColour("CaretLineBackground"))
+        self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible"))
+        self.setCaretLineAlwaysVisible(
+            Preferences.getEditor("CaretLineAlwaysVisible"))
+        self.caretWidth = Preferences.getEditor("CaretWidth")
+        self.setCaretWidth(self.caretWidth)
+        self.caretLineFrameWidth = Preferences.getEditor("CaretLineFrameWidth")
+        self.setCaretLineFrameWidth(self.caretLineFrameWidth)
+        self.useMonospaced = Preferences.getEditor("UseMonospacedFont")
+        self.setMonospaced(self.useMonospaced)
+        edgeMode = Preferences.getEditor("EdgeMode")
+        edge = QsciScintilla.EdgeMode(edgeMode)
+        self.setEdgeMode(edge)
+        if edgeMode:
+            self.setEdgeColumn(Preferences.getEditor("EdgeColumn"))
+            self.setEdgeColor(Preferences.getEditorColour("Edge"))
+        
+        wrapVisualFlag = Preferences.getEditor("WrapVisualFlag")
+        self.setWrapMode(Preferences.getEditor("WrapLongLinesMode"))
+        self.setWrapVisualFlags(wrapVisualFlag, wrapVisualFlag)
+        self.setWrapIndentMode(Preferences.getEditor("WrapIndentMode"))
+        self.setWrapStartIndent(Preferences.getEditor("WrapStartIndent"))
+        
+        self.zoomTo(Preferences.getEditor("ZoomFactor"))
+        
+        self.searchIndicator = QsciScintilla.INDIC_CONTAINER
+        self.indicatorDefine(
+            self.searchIndicator, QsciScintilla.INDIC_BOX,
+            Preferences.getEditorColour("SearchMarkers"))
+        if (
+            not Preferences.getEditor("SearchMarkersEnabled") and
+            not Preferences.getEditor("QuickSearchMarkersEnabled") and
+            not Preferences.getEditor("MarkOccurrencesEnabled")
+        ):
+            self.clearAllIndicators(self.searchIndicator)
+        
+        self.spellingIndicator = QsciScintilla.INDIC_CONTAINER + 1
+        self.indicatorDefine(
+            self.spellingIndicator, QsciScintilla.INDIC_SQUIGGLE,
+            Preferences.getEditorColour("SpellingMarkers"))
+        self.__setSpelling()
+        
+        self.highlightIndicator = QsciScintilla.INDIC_CONTAINER + 2
+        self.indicatorDefine(
+            self.highlightIndicator, QsciScintilla.INDIC_FULLBOX,
+            Preferences.getEditorColour("HighlightMarker"))
+        
+        self.setCursorFlashTime(QApplication.cursorFlashTime())
+        
+        with contextlib.suppress(AttributeError):
+            if Preferences.getEditor("AnnotationsEnabled"):
+                self.setAnnotationDisplay(
+                    QsciScintilla.AnnotationDisplay.AnnotationBoxed)
+            else:
+                self.setAnnotationDisplay(
+                    QsciScintilla.AnnotationDisplay.AnnotationHidden)
+        self.__setAnnotationStyles()
+        
+        if Preferences.getEditor("OverrideEditAreaColours"):
+            self.setColor(Preferences.getEditorColour("EditAreaForeground"))
+            self.setPaper(Preferences.getEditorColour("EditAreaBackground"))
+        
+        self.setVirtualSpaceOptions(
+            Preferences.getEditor("VirtualSpaceOptions"))
+        
+        if Preferences.getEditor("MouseHoverHelp"):
+            self.SendScintilla(QsciScintilla.SCI_SETMOUSEDWELLTIME,
+                               Preferences.getEditor("MouseHoverTimeout"))
+        else:
+            self.SendScintilla(QsciScintilla.SCI_SETMOUSEDWELLTIME,
+                               QsciScintilla.SC_TIME_FOREVER)
+        
+        # to avoid errors due to line endings by pasting
+        self.SendScintilla(QsciScintilla.SCI_SETPASTECONVERTENDINGS, True)
+        
+        self.__markerMap.setEnabled(True)
+    
+    def __setEolMode(self):
+        """
+        Private method to configure the eol mode of the editor.
+        """
+        if (
+            self.fileName and
+            self.project.isOpen() and
+            self.project.isProjectFile(self.fileName)
+        ):
+            eolMode = self.__getEditorConfig("EOLMode", nodefault=True)
+            if eolMode is None:
+                eolStr = self.project.getEolString()
+                self.setEolModeByEolString(eolStr)
+            else:
+                self.setEolMode(eolMode)
+        else:
+            eolMode = self.__getEditorConfig("EOLMode")
+            self.setEolMode(eolMode)
+        self.__eolChanged()
+        
+    def __setAutoCompletion(self):
+        """
+        Private method to configure the autocompletion function.
+        """
+        if self.lexer_:
+            self.setAutoCompletionFillupsEnabled(
+                Preferences.getEditor("AutoCompletionFillups"))
+        self.setAutoCompletionCaseSensitivity(
+            Preferences.getEditor("AutoCompletionCaseSensitivity"))
+        self.setAutoCompletionReplaceWord(
+            Preferences.getEditor("AutoCompletionReplaceWord"))
+        self.setAutoCompletionThreshold(0)
+        if Preferences.getEditor("AutoCompletionShowSingle"):
+            self.setAutoCompletionUseSingle(
+                QsciScintilla.AutoCompletionUseSingle.AcusAlways)
+        else:
+            self.setAutoCompletionUseSingle(
+                QsciScintilla.AutoCompletionUseSingle.AcusNever)
+        autoCompletionSource = Preferences.getEditor("AutoCompletionSource")
+        if (
+            autoCompletionSource ==
+            QsciScintilla.AutoCompletionSource.AcsDocument
+        ):
+            self.setAutoCompletionSource(
+                QsciScintilla.AutoCompletionSource.AcsDocument)
+        elif (
+            autoCompletionSource == QsciScintilla.AutoCompletionSource.AcsAPIs
+        ):
+            self.setAutoCompletionSource(
+                QsciScintilla.AutoCompletionSource.AcsAPIs)
+        else:
+            self.setAutoCompletionSource(
+                QsciScintilla.AutoCompletionSource.AcsAll)
+        
+        self.setAutoCompletionWidgetSize(
+            Preferences.getEditor("AutoCompletionMaxChars"),
+            Preferences.getEditor("AutoCompletionMaxLines")
+        )
+        
+    def __setCallTips(self):
+        """
+        Private method to configure the calltips function.
+        """
+        self.setCallTipsBackgroundColor(
+            Preferences.getEditorColour("CallTipsBackground"))
+        self.setCallTipsForegroundColor(
+            Preferences.getEditorColour("CallTipsForeground"))
+        self.setCallTipsHighlightColor(
+            Preferences.getEditorColour("CallTipsHighlight"))
+        self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
+        calltipsStyle = Preferences.getEditor("CallTipsStyle")
+        with contextlib.suppress(AttributeError):
+            self.setCallTipsPosition(
+                Preferences.getEditor("CallTipsPosition"))
+        
+        if Preferences.getEditor("CallTipsEnabled"):
+            if calltipsStyle == QsciScintilla.CallTipsStyle.CallTipsNoContext:
+                self.setCallTipsStyle(
+                    QsciScintilla.CallTipsStyle.CallTipsNoContext)
+            elif (
+                calltipsStyle ==
+                QsciScintilla.CallTipsStyle.CallTipsNoAutoCompletionContext
+            ):
+                self.setCallTipsStyle(
+                    QsciScintilla.CallTipsStyle
+                    .CallTipsNoAutoCompletionContext)
+            else:
+                self.setCallTipsStyle(
+                    QsciScintilla.CallTipsStyle.CallTipsContext)
+        else:
+            self.setCallTipsStyle(QsciScintilla.CallTipsStyle.CallTipsNone)
+
+    ###########################################################################
+    ## Autocompletion handling methods below
+    ###########################################################################
+
+    def canAutoCompleteFromAPIs(self):
+        """
+        Public method to check for API availablity.
+        
+        @return flag indicating autocompletion from APIs is available (boolean)
+        """
+        return self.acAPI
+        
+    def autoCompleteQScintilla(self):
+        """
+        Public method to perform an autocompletion using QScintilla methods.
+        """
+        self.__acText = ' '  # Prevent long running ACs to add results
+        self.__acWatchdog.stop()
+        if self.__acCompletions:
+            return
+        
+        acs = Preferences.getEditor("AutoCompletionSource")
+        if acs == QsciScintilla.AutoCompletionSource.AcsDocument:
+            self.autoCompleteFromDocument()
+        elif acs == QsciScintilla.AutoCompletionSource.AcsAPIs:
+            self.autoCompleteFromAPIs()
+        elif acs == QsciScintilla.AutoCompletionSource.AcsAll:
+            self.autoCompleteFromAll()
+        else:
+            EricMessageBox.information(
+                self,
+                self.tr("Autocompletion"),
+                self.tr(
+                    """Autocompletion is not available because"""
+                    """ there is no autocompletion source set."""))
+        
+    def setAutoCompletionEnabled(self, enable):
+        """
+        Public method to enable/disable autocompletion.
+        
+        @param enable flag indicating the desired autocompletion status
+            (boolean)
+        """
+        if enable:
+            autoCompletionSource = Preferences.getEditor(
+                "AutoCompletionSource")
+            if (
+                autoCompletionSource ==
+                QsciScintilla.AutoCompletionSource.AcsDocument
+            ):
+                self.setAutoCompletionSource(
+                    QsciScintilla.AutoCompletionSource.AcsDocument)
+            elif (
+                autoCompletionSource ==
+                QsciScintilla.AutoCompletionSource.AcsAPIs
+            ):
+                self.setAutoCompletionSource(
+                    QsciScintilla.AutoCompletionSource.AcsAPIs)
+            else:
+                self.setAutoCompletionSource(
+                    QsciScintilla.AutoCompletionSource.AcsAll)
+    
+    def __toggleAutoCompletionEnable(self):
+        """
+        Private slot to handle the Enable Autocompletion context menu entry.
+        """
+        if self.menuActs["AutoCompletionEnable"].isChecked():
+            self.setAutoCompletionEnabled(True)
+        else:
+            self.setAutoCompletionEnabled(False)
+    
+    #################################################################
+    ## Support for autocompletion hook methods
+    #################################################################
+    
+    def __charAdded(self, charNumber):
+        """
+        Private slot called to handle the user entering a character.
+        
+        @param charNumber value of the character entered (integer)
+        """
+        char = chr(charNumber)
+        # update code documentation viewer
+        if (
+            char == "(" and
+            Preferences.getDocuViewer("ShowInfoOnOpenParenthesis")
+        ):
+            self.vm.showEditorInfo(self)
+        
+        self.__delayedDocstringMenuPopup(self.getCursorPosition())
+        
+        if self.isListActive():
+            if self.__isStartChar(char):
+                self.cancelList()
+                self.autoComplete(auto=True, context=True)
+                return
+            elif char == '(':
+                self.cancelList()
+            else:
+                self.__acTimer.stop()
+        
+        if (
+            self.callTipsStyle() !=
+            QsciScintilla.CallTipsStyle.CallTipsNone and
+            self.lexer_ is not None and chr(charNumber) in '()'
+        ):
+            self.callTip()
+        
+        if not self.isCallTipActive():
+            char = chr(charNumber)
+            if self.__isStartChar(char):
+                self.autoComplete(auto=True, context=True)
+                return
+            
+            line, col = self.getCursorPosition()
+            txt = self.getWordLeft(line, col)
+            if len(txt) >= Preferences.getEditor("AutoCompletionThreshold"):
+                self.autoComplete(auto=True, context=False)
+                return
+    
+    def __isStartChar(self, ch):
+        """
+        Private method to check, if a character is an autocompletion start
+        character.
+        
+        @param ch character to be checked (one character string)
+        @return flag indicating the result (boolean)
+        """
+        if self.lexer_ is None:
+            return False
+        
+        wseps = self.lexer_.autoCompletionWordSeparators()
+        return any(wsep.endswith(ch) for wsep in wseps)
+    
+    def __autocompletionCancelled(self):
+        """
+        Private slot to handle the cancellation of an auto-completion list.
+        """
+        self.__acWatchdog.stop()
+        
+        self.__acText = ""
+    
+    #################################################################
+    ## auto-completion hook interfaces
+    #################################################################
+    
+    def addCompletionListHook(self, key, func, asynchroneous=False):
+        """
+        Public method to set an auto-completion list provider.
+        
+        @param key name of the provider
+        @type str
+        @param func function providing completion list. func
+            should be a function taking a reference to the editor and
+            a boolean indicating to complete a context. It should return
+            the possible completions as a list of strings.
+        @type function(editor, bool) -> list of str in case async is False
+            and function(editor, bool, str) returning nothing in case async
+            is True
+        @param asynchroneous flag indicating an asynchroneous function
+        @type bool
+        """
+        if (
+            key in self.__completionListHookFunctions or
+            key in self.__completionListAsyncHookFunctions
+        ):
+            # it was already registered
+            EricMessageBox.warning(
+                self,
+                self.tr("Auto-Completion Provider"),
+                self.tr("""The completion list provider '{0}' was already"""
+                        """ registered. Ignoring duplicate request.""")
+                .format(key))
+            return
+        
+        if asynchroneous:
+            self.__completionListAsyncHookFunctions[key] = func
+        else:
+            self.__completionListHookFunctions[key] = func
+    
+    def removeCompletionListHook(self, key):
+        """
+        Public method to remove a previously registered completion list
+        provider.
+        
+        @param key name of the provider
+        @type str
+        """
+        if key in self.__completionListHookFunctions:
+            del self.__completionListHookFunctions[key]
+        elif key in self.__completionListAsyncHookFunctions:
+            del self.__completionListAsyncHookFunctions[key]
+    
+    def getCompletionListHook(self, key):
+        """
+        Public method to get the registered completion list provider.
+        
+        @param key name of the provider
+        @type str
+        @return function providing completion list
+        @rtype function or None
+        """
+        return (self.__completionListHookFunctions.get(key) or
+                self.__completionListAsyncHookFunctions.get(key))
+    
+    def autoComplete(self, auto=False, context=True):
+        """
+        Public method to start auto-completion.
+        
+        @param auto flag indicating a call from the __charAdded method
+            (boolean)
+        @param context flag indicating to complete a context (boolean)
+        """
+        if auto and not Preferences.getEditor("AutoCompletionEnabled"):
+            # auto-completion is disabled
+            return
+        
+        if self.isListActive():
+            self.cancelList()
+        
+        if (
+            self.__completionListHookFunctions or
+            self.__completionListAsyncHookFunctions
+        ):
+            # Avoid delayed auto-completion after cursor repositioning
+            self.__acText = self.__getAcText()
+            if auto and Preferences.getEditor("AutoCompletionTimeout"):
+                self.__acTimer.stop()
+                self.__acContext = context
+                self.__acTimer.start()
+            else:
+                self.__autoComplete(auto, context)
+        elif (
+            not auto or
+            (self.autoCompletionSource() !=
+             QsciScintilla.AutoCompletionSource.AcsNone)
+        ):
+            self.autoCompleteQScintilla()
+    
+    def __getAcText(self):
+        """
+        Private method to get the text from cursor position for autocompleting.
+        
+        @return text left of cursor position
+        @rtype str
+        """
+        line, col = self.getCursorPosition()
+        text = self.text(line)
+        try:
+            acText = (
+                self.getWordLeft(line, col - 1) + text[col - 1]
+                if self.__isStartChar(text[col - 1]) else
+                self.getWordLeft(line, col)
+            )
+        except IndexError:
+            acText = ""
+        
+        return acText
+    
+    def __autoComplete(self, auto=True, context=None):
+        """
+        Private method to start auto-completion via plug-ins.
+        
+        @param auto flag indicating a call from the __charAdded method
+            (boolean)
+        @param context flag indicating to complete a context
+        @type bool or None
+        """
+        self.__acCompletions.clear()
+        self.__acCompletionsFinished = 0
+        
+        # Suppress empty completions
+        if auto and self.__acText == '':
+            return
+        
+        completions = (
+            self.__acCache.get(self.__acText)
+            if self.__acCacheEnabled else
+            None
+        )
+        if completions is not None:
+            # show list with cached entries
+            if self.isListActive():
+                self.cancelList()
+            
+            self.__showCompletionsList(completions)
+        else:
+            if context is None:
+                context = self.__acContext
+            
+            for key in self.__completionListAsyncHookFunctions:
+                self.__completionListAsyncHookFunctions[key](
+                    self, context, self.__acText)
+            
+            for key in self.__completionListHookFunctions:
+                completions = self.__completionListHookFunctions[key](
+                    self, context)
+                self.completionsListReady(completions, self.__acText)
+            
+            if Preferences.getEditor("AutoCompletionScintillaOnFail"):
+                self.__acWatchdog.start()
+    
+    def completionsListReady(self, completions, acText):
+        """
+        Public method to show the completions determined by a completions
+        provider.
+        
+        @param completions list of possible completions
+        @type list of str or set of str
+        @param acText text to be completed
+        @type str
+        """
+        currentWord = self.__getAcText() or ' '
+        # process the list only, if not already obsolete ...
+        if acText != self.__acText or not self.__acText.endswith(currentWord):
+            # Suppress auto-completion done by QScintilla as fallback
+            self.__acWatchdog.stop()
+            return
+        
+        self.__acCompletions.update(set(completions))
+        
+        self.__acCompletionsFinished += 1
+        # Got all results from auto completer?
+        if self.__acCompletionsFinished >= (
+            len(self.__completionListAsyncHookFunctions) +
+            len(self.__completionListHookFunctions)
+        ):
+            self.__acWatchdog.stop()
+            
+            # Autocomplete with QScintilla if no results present
+            if (
+                Preferences.getEditor("AutoCompletionScintillaOnFail") and
+                not self.__acCompletions
+            ):
+                self.autoCompleteQScintilla()
+                return
+        
+        # ... or completions are not empty
+        if not bool(completions):
+            return
+        
+        if self.isListActive():
+            self.cancelList()
+        
+        if self.__acCompletions:
+            if self.__acCacheEnabled:
+                self.__acCache.add(acText, set(self.__acCompletions))
+            self.__showCompletionsList(self.__acCompletions)
+    
+    def __showCompletionsList(self, completions):
+        """
+        Private method to show the completions list.
+        
+        @param completions completions to be shown
+        @type list of str or set of str
+        """
+        acCompletions = (
+            sorted(completions,
+                   key=self.__replaceLeadingUnderscores)
+            if Preferences.getEditor("AutoCompletionReversedList") else
+            sorted(completions)
+        )
+        self.showUserList(EditorAutoCompletionListID, acCompletions)
+    
+    def __replaceLeadingUnderscores(self, txt):
+        """
+        Private method to replace the first two underlines for invers sorting.
+        
+        @param txt completion text
+        @type str
+        @return modified completion text
+        @rtype str
+        """
+        if txt.startswith('_'):
+            return txt[:2].replace('_', '~') + txt[2:]
+        else:
+            return txt
+    
+    def __clearCompletionsCache(self):
+        """
+        Private method to clear the auto-completions cache.
+        """
+        self.__acCache.clear()
+    
+    def __completionListSelected(self, listId, txt):
+        """
+        Private slot to handle the selection from the completion list.
+        
+        @param listId the ID of the user list (should be 1 or 2) (integer)
+        @param txt the selected text (string)
+        """
+        # custom completions via plug-ins
+        if listId == EditorAutoCompletionListID:
+            lst = txt.split()
+            if len(lst) > 1:
+                txt = lst[0]
+            
+            self.beginUndoAction()
+            if Preferences.getEditor("AutoCompletionReplaceWord"):
+                self.selectCurrentWord()
+                self.removeSelectedText()
+                line, col = self.getCursorPosition()
+            else:
+                line, col = self.getCursorPosition()
+                wLeft = self.getWordLeft(line, col)
+                if not txt.startswith(wLeft):
+                    self.selectCurrentWord()
+                    self.removeSelectedText()
+                    line, col = self.getCursorPosition()
+                elif wLeft:
+                    txt = txt[len(wLeft):]
+                
+                if txt and txt[0] in "'\"":
+                    # New in jedi 0.16: AC of dict keys
+                    txt = txt[1:]
+            self.insert(txt)
+            self.endUndoAction()
+            self.setCursorPosition(line, col + len(txt))
+        
+        # template completions
+        elif listId == TemplateCompletionListID:
+            self.__applyTemplate(txt, self.getLanguage())
+        
+        # 'goto reference' completions
+        elif listId == ReferencesListID:
+            with contextlib.suppress(ValueError, IndexError):
+                index = self.__referencesList.index(txt)
+                filename, line, column = self.__referencesPositionsList[index]
+                self.vm.openSourceFile(
+                    filename, lineno=line, pos=column, addNext=True)
+    
+    def canProvideDynamicAutoCompletion(self):
+        """
+        Public method to test the dynamic auto-completion availability.
+        
+        @return flag indicating the availability of dynamic auto-completion
+            (boolean)
+        """
+        return (self.acAPI or
+                bool(self.__completionListHookFunctions) or
+                bool(self.__completionListAsyncHookFunctions))
+    
+    #################################################################
+    ## call-tip hook interfaces
+    #################################################################
+    
+    def addCallTipHook(self, key, func):
+        """
+        Public method to set a calltip provider.
+        
+        @param key name of the provider
+        @type str
+        @param func function providing calltips. func
+            should be a function taking a reference to the editor,
+            a position into the text and the amount of commas to the
+            left of the cursor. It should return the possible
+            calltips as a list of strings.
+        @type function(editor, int, int) -> list of str
+        """
+        if key in self.__ctHookFunctions:
+            # it was already registered
+            EricMessageBox.warning(
+                self,
+                self.tr("Call-Tips Provider"),
+                self.tr("""The call-tips provider '{0}' was already"""
+                        """ registered. Ignoring duplicate request.""")
+                .format(key))
+            return
+        
+        self.__ctHookFunctions[key] = func
+    
+    def removeCallTipHook(self, key):
+        """
+        Public method to remove a previously registered calltip provider.
+        
+        @param key name of the provider
+        @type str
+        """
+        if key in self.__ctHookFunctions:
+            del self.__ctHookFunctions[key]
+    
+    def getCallTipHook(self, key):
+        """
+        Public method to get the registered calltip provider.
+        
+        @param key name of the provider
+        @type str
+        @return function providing calltips
+        @rtype function or None
+        """
+        if key in self.__ctHookFunctions:
+            return self.__ctHookFunctions[key]
+        else:
+            return None
+    
+    def canProvideCallTipps(self):
+        """
+        Public method to test the calltips availability.
+        
+        @return flag indicating the availability of calltips (boolean)
+        """
+        return (self.acAPI or
+                bool(self.__ctHookFunctions))
+    
+    def callTip(self):
+        """
+        Public method to show calltips.
+        """
+        if bool(self.__ctHookFunctions):
+            self.__callTip()
+        else:
+            super().callTip()
+    
+    def __callTip(self):
+        """
+        Private method to show call tips provided by a plugin.
+        """
+        pos = self.currentPosition()
+        
+        # move backward to the start of the current calltip working out
+        # which argument to highlight
+        commas = 0
+        found = False
+        ch, pos = self.__getCharacter(pos)
+        while ch:
+            if ch == ',':
+                commas += 1
+            elif ch == ')':
+                depth = 1
+                
+                # ignore everything back to the start of the corresponding
+                # parenthesis
+                ch, pos = self.__getCharacter(pos)
+                while ch:
+                    if ch == ')':
+                        depth += 1
+                    elif ch == '(':
+                        depth -= 1
+                        if depth == 0:
+                            break
+                    ch, pos = self.__getCharacter(pos)
+            elif ch == '(':
+                found = True
+                break
+            
+            ch, pos = self.__getCharacter(pos)
+        
+        self.cancelCallTips()
+        
+        if not found:
+            return
+        
+        callTips = []
+        if self.__ctHookFunctions:
+            for key in self.__ctHookFunctions:
+                callTips.extend(self.__ctHookFunctions[key](self, pos, commas))
+            callTips = list(set(callTips))
+            callTips.sort()
+        else:
+            # try QScintilla calltips
+            super().callTip()
+            return
+        if len(callTips) == 0:
+            if Preferences.getEditor("CallTipsScintillaOnFail"):
+                # try QScintilla calltips
+                super().callTip()
+            return
+        
+        ctshift = 0
+        for ct in callTips:
+            shift = ct.index("(")
+            if ctshift < shift:
+                ctshift = shift
+        
+        cv = self.callTipsVisible()
+        ct = (
+            # this is just a safe guard
+            self._encodeString("\n".join(callTips[:cv]))
+            if cv > 0 else
+            # until here and unindent below
+            self._encodeString("\n".join(callTips))
+        )
+        
+        self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW,
+                           self.__adjustedCallTipPosition(ctshift, pos), ct)
+        if b'\n' in ct:
+            return
+        
+        # Highlight the current argument
+        if commas == 0:
+            astart = ct.find(b'(')
+        else:
+            astart = ct.find(b',')
+            commas -= 1
+            while astart != -1 and commas > 0:
+                astart = ct.find(b',', astart + 1)
+                commas -= 1
+        
+        if astart == -1:
+            return
+        
+        depth = 0
+        for aend in range(astart + 1, len(ct)):
+            ch = ct[aend:aend + 1]
+            
+            if ch == b',' and depth == 0:
+                break
+            elif ch == b'(':
+                depth += 1
+            elif ch == b')':
+                if depth == 0:
+                    break
+                
+                depth -= 1
+        
+        if astart != aend:
+            self.SendScintilla(QsciScintilla.SCI_CALLTIPSETHLT,
+                               astart + 1, aend)
+    
+    def __adjustedCallTipPosition(self, ctshift, pos):
+        """
+        Private method to calculate an adjusted position for showing calltips.
+        
+        @param ctshift amount the calltip shall be shifted (integer)
+        @param pos position into the text (integer)
+        @return new position for the calltip (integer)
+        """
+        ct = pos
+        if ctshift:
+            ctmin = self.SendScintilla(
+                QsciScintilla.SCI_POSITIONFROMLINE,
+                self.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, ct))
+            if ct - ctshift < ctmin:
+                ct = ctmin
+            else:
+                ct -= ctshift
+        return ct
+    
+    #################################################################
+    ## Methods needed by the code documentation viewer
+    #################################################################
+    
+    def __showCodeInfo(self):
+        """
+        Private slot to handle the context menu action to show code info.
+        """
+        self.vm.showEditorInfo(self)
+    
+    #################################################################
+    ## Methods needed by the context menu
+    #################################################################
+    
+    def __marginNumber(self, xPos):
+        """
+        Private method to calculate the margin number based on a x position.
+        
+        @param xPos x position (integer)
+        @return margin number (integer, -1 for no margin)
+        """
+        width = 0
+        for margin in range(5):
+            width += self.marginWidth(margin)
+            if xPos <= width:
+                return margin
+        return -1
+        
+    def contextMenuEvent(self, evt):
+        """
+        Protected method implementing the context menu event.
+        
+        @param evt the context menu event (QContextMenuEvent)
+        """
+        evt.accept()
+        if self.__marginNumber(evt.x()) == -1:
+            self.spellingMenuPos = self.positionFromPoint(evt.pos())
+            if (
+                self.spellingMenuPos >= 0 and
+                self.spell is not None and
+                self.hasIndicator(self.spellingIndicator,
+                                  self.spellingMenuPos)
+            ):
+                self.spellingMenu.popup(evt.globalPos())
+            else:
+                self.menu.popup(evt.globalPos())
+        else:
+            self.line = self.lineAt(evt.pos())
+            if self.__marginNumber(evt.x()) in [self.__bmMargin,
+                                                self.__linenoMargin]:
+                self.bmMarginMenu.popup(evt.globalPos())
+            elif self.__marginNumber(evt.x()) == self.__bpMargin:
+                self.bpMarginMenu.popup(evt.globalPos())
+            elif self.__marginNumber(evt.x()) == self.__indicMargin:
+                self.indicMarginMenu.popup(evt.globalPos())
+            elif self.__marginNumber(evt.x()) == self.__foldMargin:
+                self.foldMarginMenu.popup(evt.globalPos())
+        
+    def __showContextMenu(self):
+        """
+        Private slot handling the aboutToShow signal of the context menu.
+        """
+        self.menuActs["Reopen"].setEnabled(
+            not self.isModified() and bool(self.fileName))
+        self.menuActs["Save"].setEnabled(self.isModified())
+        self.menuActs["Undo"].setEnabled(self.isUndoAvailable())
+        self.menuActs["Redo"].setEnabled(self.isRedoAvailable())
+        self.menuActs["Revert"].setEnabled(self.isModified())
+        self.menuActs["Cut"].setEnabled(self.hasSelectedText())
+        self.menuActs["Copy"].setEnabled(self.hasSelectedText())
+        if self.menuActs["ExecuteSelection"] is not None:
+            self.menuActs["ExecuteSelection"].setEnabled(
+                self.hasSelectedText())
+        self.menuActs["Paste"].setEnabled(self.canPaste())
+        if not self.isResourcesFile:
+            if self.fileName and self.isPyFile():
+                self.menuActs["Show"].setEnabled(True)
+            else:
+                self.menuActs["Show"].setEnabled(False)
+            if (
+                self.fileName and
+                (self.isPyFile() or self.isRubyFile())
+            ):
+                self.menuActs["Diagrams"].setEnabled(True)
+            else:
+                self.menuActs["Diagrams"].setEnabled(False)
+        if not self.miniMenu:
+            if self.lexer_ is not None:
+                self.menuActs["Comment"].setEnabled(
+                    self.lexer_.canBlockComment())
+                self.menuActs["Uncomment"].setEnabled(
+                    self.lexer_.canBlockComment())
+            else:
+                self.menuActs["Comment"].setEnabled(False)
+                self.menuActs["Uncomment"].setEnabled(False)
+            
+            cline = self.getCursorPosition()[0]
+            line = self.text(cline)
+            self.menuActs["Docstring"].setEnabled(
+                self.getDocstringGenerator().isFunctionStart(line))
+        
+        self.menuActs["TypingAidsEnabled"].setEnabled(
+            self.completer is not None)
+        self.menuActs["TypingAidsEnabled"].setChecked(
+            self.completer is not None and self.completer.isEnabled())
+        
+        if not self.isResourcesFile:
+            self.menuActs["calltip"].setEnabled(self.canProvideCallTipps())
+            self.menuActs["codeInfo"].setEnabled(
+                self.vm.isEditorInfoSupported(self.getLanguage()))
+        
+        self.menuActs["MonospacedFont"].setEnabled(self.lexer_ is None)
+        
+        splitOrientation = self.vm.getSplitOrientation()
+        if splitOrientation == Qt.Orientation.Horizontal:
+            self.menuActs["NewSplit"].setIcon(
+                UI.PixmapCache.getIcon("splitHorizontal"))
+        else:
+            self.menuActs["NewSplit"].setIcon(
+                UI.PixmapCache.getIcon("splitVertical"))
+        
+        self.menuActs["Tools"].setEnabled(not self.toolsMenu.isEmpty())
+        
+        self.showMenu.emit("Main", self.menu, self)
+        
+    def __showContextMenuAutocompletion(self):
+        """
+        Private slot called before the autocompletion menu is shown.
+        """
+        self.menuActs["acDynamic"].setEnabled(
+            self.canProvideDynamicAutoCompletion())
+        self.menuActs["acClearCache"].setEnabled(
+            self.canProvideDynamicAutoCompletion())
+        self.menuActs["acAPI"].setEnabled(self.acAPI)
+        self.menuActs["acAPIDocument"].setEnabled(self.acAPI)
+        
+        self.showMenu.emit("Autocompletion", self.autocompletionMenu, self)
+        
+    def __showContextMenuShow(self):
+        """
+        Private slot called before the show menu is shown.
+        """
+        prEnable = False
+        coEnable = False
+        
+        # first check if the file belongs to a project
+        if (
+            self.project.isOpen() and
+            self.project.isProjectSource(self.fileName)
+        ):
+            fn = self.project.getMainScript(True)
+            if fn is not None:
+                prEnable = (
+                    self.project.isPy3Project() and
+                    bool(Utilities.getProfileFileNames(fn))
+                )
+                coEnable = (
+                    self.project.isPy3Project() and
+                    bool(Utilities.getCoverageFileNames(fn))
+                )
+        
+        # now check ourselves
+        fn = self.getFileName()
+        if fn is not None:
+            prEnable |= (
+                self.project.isPy3Project() and
+                bool(Utilities.getProfileFileName(fn))
+            )
+            coEnable |= (
+                self.project.isPy3Project() and
+                bool(Utilities.getCoverageFileName(fn))
+            )
+        
+        coEnable |= bool(self.__coverageFile)
+        
+        # now check for syntax errors
+        if self.hasSyntaxErrors():
+            coEnable = False
+        
+        self.profileMenuAct.setEnabled(prEnable)
+        self.coverageMenuAct.setEnabled(coEnable)
+        self.coverageShowAnnotationMenuAct.setEnabled(
+            coEnable and len(self.notcoveredMarkers) == 0)
+        self.coverageHideAnnotationMenuAct.setEnabled(
+            len(self.notcoveredMarkers) > 0)
+        
+        self.showMenu.emit("Show", self.menuShow, self)
+        
+    def __showContextMenuGraphics(self):
+        """
+        Private slot handling the aboutToShow signal of the diagrams context
+        menu.
+        """
+        if (
+            self.project.isOpen() and
+            self.project.isProjectSource(self.fileName)
+        ):
+            self.applicationDiagramMenuAct.setEnabled(True)
+        else:
+            self.applicationDiagramMenuAct.setEnabled(False)
+        
+        self.showMenu.emit("Graphics", self.graphicsMenu, self)
+        
+    def __showContextMenuMargin(self, menu):
+        """
+        Private slot handling the aboutToShow signal of the margins context
+        menu.
+        
+        @param menu reference to the menu to be shown
+        @type QMenu
+        """
+        if menu is self.bpMarginMenu:
+            supportsDebugger = bool(self.fileName and self.isPyFile())
+            hasBreakpoints = bool(self.breaks)
+            hasBreakpoint = bool(
+                self.markersAtLine(self.line) & self.breakpointMask)
+            
+            self.marginMenuActs["Breakpoint"].setEnabled(supportsDebugger)
+            self.marginMenuActs["TempBreakpoint"].setEnabled(supportsDebugger)
+            self.marginMenuActs["NextBreakpoint"].setEnabled(
+                supportsDebugger and hasBreakpoints)
+            self.marginMenuActs["PreviousBreakpoint"].setEnabled(
+                supportsDebugger and hasBreakpoints)
+            self.marginMenuActs["ClearBreakpoint"].setEnabled(
+                supportsDebugger and hasBreakpoints)
+            self.marginMenuActs["EditBreakpoint"].setEnabled(
+                supportsDebugger and hasBreakpoint)
+            self.marginMenuActs["EnableBreakpoint"].setEnabled(
+                supportsDebugger and hasBreakpoint)
+            if supportsDebugger:
+                if self.markersAtLine(self.line) & (1 << self.dbreakpoint):
+                    self.marginMenuActs["EnableBreakpoint"].setText(
+                        self.tr('Enable breakpoint'))
+                else:
+                    self.marginMenuActs["EnableBreakpoint"].setText(
+                        self.tr('Disable breakpoint'))
+        
+        if menu is self.bmMarginMenu:
+            hasBookmarks = bool(self.bookmarks)
+            
+            self.marginMenuActs["NextBookmark"].setEnabled(hasBookmarks)
+            self.marginMenuActs["PreviousBookmark"].setEnabled(hasBookmarks)
+            self.marginMenuActs["ClearBookmark"].setEnabled(hasBookmarks)
+        
+        if menu is self.foldMarginMenu:
+            isFoldHeader = bool(self.SendScintilla(
+                QsciScintilla.SCI_GETFOLDLEVEL, self.line) &
+                QsciScintilla.SC_FOLDLEVELHEADERFLAG)
+            
+            self.marginMenuActs["ExpandChildren"].setEnabled(isFoldHeader)
+            self.marginMenuActs["CollapseChildren"].setEnabled(isFoldHeader)
+        
+        if menu is self.indicMarginMenu:
+            hasSyntaxErrors = bool(self.syntaxerrors)
+            hasWarnings = bool(self.warnings)
+            hasNotCoveredMarkers = bool(self.notcoveredMarkers)
+            
+            self.marginMenuActs["GotoSyntaxError"].setEnabled(hasSyntaxErrors)
+            self.marginMenuActs["ClearSyntaxError"].setEnabled(hasSyntaxErrors)
+            if (
+                hasSyntaxErrors and
+                self.markersAtLine(self.line) & (1 << self.syntaxerror)
+            ):
+                self.marginMenuActs["ShowSyntaxError"].setEnabled(True)
+            else:
+                self.marginMenuActs["ShowSyntaxError"].setEnabled(False)
+            
+            self.marginMenuActs["NextWarningMarker"].setEnabled(hasWarnings)
+            self.marginMenuActs["PreviousWarningMarker"].setEnabled(
+                hasWarnings)
+            self.marginMenuActs["ClearWarnings"].setEnabled(hasWarnings)
+            if (
+                hasWarnings and
+                self.markersAtLine(self.line) & (1 << self.warning)
+            ):
+                self.marginMenuActs["ShowWarning"].setEnabled(True)
+            else:
+                self.marginMenuActs["ShowWarning"].setEnabled(False)
+            
+            self.marginMenuActs["NextCoverageMarker"].setEnabled(
+                hasNotCoveredMarkers)
+            self.marginMenuActs["PreviousCoverageMarker"].setEnabled(
+                hasNotCoveredMarkers)
+            
+            self.marginMenuActs["PreviousTaskMarker"].setEnabled(
+                self.__hasTaskMarkers)
+            self.marginMenuActs["NextTaskMarker"].setEnabled(
+                self.__hasTaskMarkers)
+            
+            self.marginMenuActs["PreviousChangeMarker"].setEnabled(
+                self.__hasChangeMarkers)
+            self.marginMenuActs["NextChangeMarker"].setEnabled(
+                self.__hasChangeMarkers)
+            self.marginMenuActs["ClearChangeMarkers"].setEnabled(
+                self.__hasChangeMarkers)
+        
+        self.showMenu.emit("Margin", menu, self)
+        
+    def __showContextMenuChecks(self):
+        """
+        Private slot handling the aboutToShow signal of the checks context
+        menu.
+        """
+        self.showMenu.emit("Checks", self.checksMenu, self)
+        
+    def __showContextMenuTools(self):
+        """
+        Private slot handling the aboutToShow signal of the tools context
+        menu.
+        """
+        self.showMenu.emit("Tools", self.toolsMenu, self)
+        
+    def __reopenWithEncodingMenuTriggered(self, act):
+        """
+        Private method to handle the rereading of the file with a selected
+        encoding.
+        
+        @param act reference to the action that was triggered (QAction)
+        """
+        encoding = act.data()
+        self.readFile(self.fileName, encoding=encoding)
+        self.__convertTabs()
+        self.__checkEncoding()
+        
+    def __contextSave(self):
+        """
+        Private slot handling the save context menu entry.
+        """
+        ok = self.saveFile()
+        if ok:
+            self.vm.setEditorName(self, self.fileName)
+        
+    def __contextSaveAs(self):
+        """
+        Private slot handling the save as context menu entry.
+        """
+        ok = self.saveFileAs()
+        if ok:
+            self.vm.setEditorName(self, self.fileName)
+        
+    def __contextSaveCopy(self):
+        """
+        Private slot handling the save copy context menu entry.
+        """
+        self.saveFileCopy()
+        
+    def __contextClose(self):
+        """
+        Private slot handling the close context menu entry.
+        """
+        self.vm.closeEditor(self)
+    
+    def __newView(self):
+        """
+        Private slot to create a new view to an open document.
+        """
+        self.vm.newEditorView(self.fileName, self, self.filetype)
+        
+    def __newViewNewSplit(self):
+        """
+        Private slot to create a new view to an open document.
+        """
+        self.vm.addSplit()
+        self.vm.newEditorView(self.fileName, self, self.filetype)
+        
+    def __selectAll(self):
+        """
+        Private slot handling the select all context menu action.
+        """
+        self.selectAll(True)
+            
+    def __deselectAll(self):
+        """
+        Private slot handling the deselect all context menu action.
+        """
+        self.selectAll(False)
+        
+    def joinLines(self):
+        """
+        Public slot to join the current line with the next one.
+        """
+        curLine = self.getCursorPosition()[0]
+        if curLine == self.lines() - 1:
+            return
+        
+        line0Text = self.text(curLine)
+        line1Text = self.text(curLine + 1)
+        if line1Text in ["", "\r", "\n", "\r\n"]:
+            return
+        
+        if (
+            line0Text.rstrip("\r\n\\ \t").endswith(("'", '"')) and
+            line1Text.lstrip().startswith(("'", '"'))
+        ):
+            # merging multi line strings
+            startChars = "\r\n\\ \t'\""
+            endChars = " \t'\""
+        else:
+            startChars = "\r\n\\ \t"
+            endChars = " \t"
+        
+        # determine start index
+        startIndex = len(line0Text)
+        while startIndex > 0 and line0Text[startIndex - 1] in startChars:
+            startIndex -= 1
+        if startIndex == 0:
+            return
+        
+        # determine end index
+        endIndex = 0
+        while line1Text[endIndex] in endChars:
+            endIndex += 1
+        
+        self.setSelection(curLine, startIndex, curLine + 1, endIndex)
+        self.beginUndoAction()
+        self.removeSelectedText()
+        self.insertAt(" ", curLine, startIndex)
+        self.endUndoAction()
+        
+    def shortenEmptyLines(self):
+        """
+        Public slot to compress lines consisting solely of whitespace
+        characters.
+        """
+        searchRE = r"^[ \t]+$"
+        
+        ok = self.findFirstTarget(searchRE, True, False, False, 0, 0)
+        self.beginUndoAction()
+        while ok:
+            self.replaceTarget("")
+            ok = self.findNextTarget()
+        self.endUndoAction()
+        
+    def __autosaveEnable(self):
+        """
+        Private slot handling the autosave enable context menu action.
+        """
+        if self.menuActs["AutosaveEnable"].isChecked():
+            self.autosaveManuallyDisabled = False
+        else:
+            self.autosaveManuallyDisabled = True
+        
+    def shouldAutosave(self):
+        """
+        Public slot to check the autosave flags.
+        
+        @return flag indicating this editor should be saved (boolean)
+        """
+        return (
+            bool(self.fileName) and
+            not self.autosaveManuallyDisabled and
+            not self.isReadOnly()
+        )
+
+    def checkSyntax(self):
+        """
+        Public method to perform an automatic syntax check of the file.
+        """
+        fileType = self.filetype
+        if fileType == "MicroPython":
+            # adjustment for MicroPython
+            fileType = "Python3"
+        
+        if (
+            self.syntaxCheckService is None or
+            fileType not in self.syntaxCheckService.getLanguages()
+        ):
+            return
+        
+        if Preferences.getEditor("AutoCheckSyntax"):
+            if Preferences.getEditor("OnlineSyntaxCheck"):
+                self.__onlineSyntaxCheckTimer.stop()
+            
+            self.syntaxCheckService.syntaxCheck(
+                fileType, self.fileName or "(Unnamed)", self.text())
+
+    def __processSyntaxCheckError(self, fn, msg):
+        """
+        Private slot to report an error message of a syntax check.
+        
+        @param fn filename of the file
+        @type str
+        @param msg error message
+        @type str
+        """
+        if fn != self.fileName and (
+                bool(self.fileName) or fn != "(Unnamed)"):
+            return
+        
+        self.clearSyntaxError()
+        self.clearFlakesWarnings()
+        
+        self.toggleWarning(0, 0, True, msg)
+        
+        self.updateVerticalScrollBar()
+    
+    def __processSyntaxCheckResult(self, fn, problems):
+        """
+        Private slot to report the resulting messages of a syntax check.
+        
+        @param fn filename of the checked file (str)
+        @param problems dictionary with the keys 'error' and 'warnings' which
+            hold a list containing details about the error/ warnings
+            (file name, line number, column, codestring (only at syntax
+            errors), the message) (dict)
+        """
+        # Check if it's the requested file, otherwise ignore signal
+        if fn != self.fileName and (
+                bool(self.fileName) or fn != "(Unnamed)"):
+            return
+        
+        self.clearSyntaxError()
+        self.clearFlakesWarnings()
+        
+        error = problems.get('error')
+        if error:
+            _fn, lineno, col, code, msg = error
+            self.toggleSyntaxError(lineno, col, True, msg)
+        
+        warnings = problems.get('warnings', [])
+        for _fn, lineno, col, _code, msg in warnings:
+            self.toggleWarning(lineno, col, True, msg)
+        
+        self.updateVerticalScrollBar()
+ 
+    def __initOnlineSyntaxCheck(self):
+        """
+        Private slot to initialize the online syntax check.
+        """
+        self.__onlineSyntaxCheckTimer = QTimer(self)
+        self.__onlineSyntaxCheckTimer.setSingleShot(True)
+        self.__onlineSyntaxCheckTimer.setInterval(
+            Preferences.getEditor("OnlineSyntaxCheckInterval") * 1000)
+        self.__onlineSyntaxCheckTimer.timeout.connect(self.checkSyntax)
+        self.textChanged.connect(self.__resetOnlineSyntaxCheckTimer)
+        
+    def __resetOnlineSyntaxCheckTimer(self):
+        """
+        Private method to reset the online syntax check timer.
+        """
+        if Preferences.getEditor("OnlineSyntaxCheck"):
+            self.__onlineSyntaxCheckTimer.stop()
+            self.__onlineSyntaxCheckTimer.start()
+        
+    def __showCodeMetrics(self):
+        """
+        Private method to handle the code metrics context menu action.
+        """
+        if not self.checkDirty():
+            return
+        
+        from DataViews.CodeMetricsDialog import CodeMetricsDialog
+        self.codemetrics = CodeMetricsDialog()
+        self.codemetrics.show()
+        self.codemetrics.start(self.fileName)
+        
+    def __getCodeCoverageFile(self):
+        """
+        Private method to get the file name of the file containing coverage
+        info.
+        
+        @return file name of the coverage file
+        @rtype str
+        """
+        files = set()
+        
+        if bool(self.__coverageFile):
+            # return the path of a previously used coverage file
+            return self.__coverageFile
+        
+        # first check if the file belongs to a project and there is
+        # a project coverage file
+        if (
+            self.project.isOpen() and
+            self.project.isProjectSource(self.fileName)
+        ):
+            pfn = self.project.getMainScript(True)
+            if pfn is not None:
+                files |= set(Utilities.getCoverageFileNames(pfn))
+        
+        # now check, if there are coverage files belonging to ourselves
+        fn = self.getFileName()
+        if fn is not None:
+            files |= set(Utilities.getCoverageFileNames(fn))
+        
+        files = list(files)
+        if files:
+            if len(files) > 1:
+                cfn, ok = QInputDialog.getItem(
+                    self,
+                    self.tr("Code Coverage"),
+                    self.tr("Please select a coverage file"),
+                    files,
+                    0, False)
+                if not ok:
+                    return ""
+            else:
+                cfn = files[0]
+        else:
+            cfn = None
+        
+        return cfn
+        
+    def __showCodeCoverage(self):
+        """
+        Private method to handle the code coverage context menu action.
+        """
+        fn = self.__getCodeCoverageFile()
+        self.__coverageFile = fn
+        if fn:
+            from DataViews.PyCoverageDialog import PyCoverageDialog
+            self.codecoverage = PyCoverageDialog()
+            self.codecoverage.show()
+            self.codecoverage.start(fn, self.fileName)
+        
+    def refreshCoverageAnnotations(self):
+        """
+        Public method to refresh the code coverage annotations.
+        """
+        if self.showingNotcoveredMarkers:
+            self.codeCoverageShowAnnotations(silent=True)
+        
+    def codeCoverageShowAnnotations(self, silent=False, coverageFile=None):
+        """
+        Public method to handle the show code coverage annotations context
+        menu action.
+        
+        @param silent flag indicating to not show any dialog (defaults to
+            False)
+        @type bool (optional)
+        @param coverageFile path of the file containing the code coverage data
+            (defaults to None)
+        @type str (optional)
+        """
+        self.__codeCoverageHideAnnotations()
+        
+        fn = (
+            coverageFile
+            if bool(coverageFile) else
+            self.__getCodeCoverageFile()
+        )
+        self.__coverageFile = fn
+        
+        if fn:
+            from coverage import Coverage
+            cover = Coverage(data_file=fn)
+            cover.load()
+            missing = cover.analysis2(self.fileName)[3]
+            if missing:
+                for line in missing:
+                    handle = self.markerAdd(line - 1, self.notcovered)
+                    self.notcoveredMarkers.append(handle)
+                self.coverageMarkersShown.emit(True)
+                self.__markerMap.update()
+            else:
+                if not silent:
+                    EricMessageBox.information(
+                        self,
+                        self.tr("Show Code Coverage Annotations"),
+                        self.tr("""All lines have been covered."""))
+            self.showingNotcoveredMarkers = True
+        else:
+            if not silent:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("Show Code Coverage Annotations"),
+                    self.tr("""There is no coverage file available."""))
+        
+    def __codeCoverageHideAnnotations(self):
+        """
+        Private method to handle the hide code coverage annotations context
+        menu action.
+        """
+        for handle in self.notcoveredMarkers:
+            self.markerDeleteHandle(handle)
+        self.notcoveredMarkers.clear()
+        self.coverageMarkersShown.emit(False)
+        self.showingNotcoveredMarkers = False
+        self.__markerMap.update()
+        
+    def getCoverageLines(self):
+        """
+        Public method to get the lines containing a coverage marker.
+        
+        @return list of lines containing a coverage marker (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, 1 << self.notcovered)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasCoverageMarkers(self):
+        """
+        Public method to test, if there are coverage markers.
+        
+        @return flag indicating the presence of coverage markers (boolean)
+        """
+        return len(self.notcoveredMarkers) > 0
+        
+    def nextUncovered(self):
+        """
+        Public slot to handle the 'Next uncovered' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == self.lines() - 1:
+            line = 0
+        else:
+            line += 1
+        ucline = self.markerFindNext(line, 1 << self.notcovered)
+        if ucline < 0:
+            # wrap around
+            ucline = self.markerFindNext(0, 1 << self.notcovered)
+        if ucline >= 0:
+            self.setCursorPosition(ucline, 0)
+            self.ensureLineVisible(ucline)
+        
+    def previousUncovered(self):
+        """
+        Public slot to handle the 'Previous uncovered' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == 0:
+            line = self.lines() - 1
+        else:
+            line -= 1
+        ucline = self.markerFindPrevious(line, 1 << self.notcovered)
+        if ucline < 0:
+            # wrap around
+            ucline = self.markerFindPrevious(
+                self.lines() - 1, 1 << self.notcovered)
+        if ucline >= 0:
+            self.setCursorPosition(ucline, 0)
+            self.ensureLineVisible(ucline)
+        
+    def __showProfileData(self):
+        """
+        Private method to handle the show profile data context menu action.
+        """
+        files = set()
+        
+        # first check if the file belongs to a project and there is
+        # a project profile file
+        if (
+            self.project.isOpen() and
+            self.project.isProjectSource(self.fileName)
+        ):
+            fn = self.project.getMainScript(True)
+            if fn is not None:
+                files |= set(Utilities.getProfileFileNames(fn))
+        
+        # now check, if there are profile files belonging to ourselves
+        fn = self.getFileName()
+        if fn is not None:
+            files |= set(Utilities.getProfileFileNames(fn))
+        
+        files = list(files)
+        if files:
+            if len(files) > 1:
+                fn, ok = QInputDialog.getItem(
+                    self,
+                    self.tr("Profile Data"),
+                    self.tr("Please select a profile file"),
+                    files,
+                    0, False)
+                if not ok:
+                    return
+            else:
+                fn = files[0]
+        else:
+            return
+        
+        from DataViews.PyProfileDialog import PyProfileDialog
+        self.profiledata = PyProfileDialog()
+        self.profiledata.show()
+        self.profiledata.start(fn, self.fileName)
+        
+    def __lmBbookmarks(self):
+        """
+        Private method to handle the 'LMB toggles bookmark' context menu
+        action.
+        """
+        self.marginMenuActs["LMBbookmarks"].setChecked(True)
+        self.marginMenuActs["LMBbreakpoints"].setChecked(False)
+        
+    def __lmBbreakpoints(self):
+        """
+        Private method to handle the 'LMB toggles breakpoint' context menu
+        action.
+        """
+        self.marginMenuActs["LMBbookmarks"].setChecked(True)
+        self.marginMenuActs["LMBbreakpoints"].setChecked(False)
+    
+    ###########################################################################
+    ## Syntax error handling methods below
+    ###########################################################################
+
+    def toggleSyntaxError(self, line, index, error, msg="", show=False):
+        """
+        Public method to toggle a syntax error indicator.
+        
+        @param line line number of the syntax error (integer)
+        @param index index number of the syntax error (integer)
+        @param error flag indicating if the error marker should be
+            set or deleted (boolean)
+        @param msg error message (string)
+        @param show flag indicating to set the cursor to the error position
+            (boolean)
+        """
+        if line == 0:
+            line = 1
+            # hack to show a syntax error marker, if line is reported to be 0
+        if error:
+            # set a new syntax error marker
+            markers = self.markersAtLine(line - 1)
+            index += self.indentation(line - 1)
+            if not (markers & (1 << self.syntaxerror)):
+                handle = self.markerAdd(line - 1, self.syntaxerror)
+                self.syntaxerrors[handle] = [(msg, index)]
+                self.syntaxerrorToggled.emit(self)
+            else:
+                for handle in list(self.syntaxerrors.keys()):
+                    if (
+                        self.markerLine(handle) == line - 1 and
+                        (msg, index) not in self.syntaxerrors[handle]
+                    ):
+                        self.syntaxerrors[handle].append((msg, index))
+            if show:
+                self.setCursorPosition(line - 1, index)
+                self.ensureLineVisible(line - 1)
+        else:
+            for handle in list(self.syntaxerrors.keys()):
+                if self.markerLine(handle) == line - 1:
+                    del self.syntaxerrors[handle]
+                    self.markerDeleteHandle(handle)
+                    self.syntaxerrorToggled.emit(self)
+        
+        self.__setAnnotation(line - 1)
+        self.__markerMap.update()
+        
+    def getSyntaxErrors(self):
+        """
+        Public method to retrieve the syntax error markers.
+        
+        @return sorted list of all lines containing a syntax error
+            (list of integer)
+        """
+        selist = []
+        for handle in list(self.syntaxerrors.keys()):
+            selist.append(self.markerLine(handle) + 1)
+        
+        selist.sort()
+        return selist
+        
+    def getSyntaxErrorLines(self):
+        """
+        Public method to get the lines containing a syntax error.
+        
+        @return list of lines containing a syntax error (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, 1 << self.syntaxerror)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasSyntaxErrors(self):
+        """
+        Public method to check for the presence of syntax errors.
+        
+        @return flag indicating the presence of syntax errors (boolean)
+        """
+        return len(self.syntaxerrors) > 0
+    
+    def gotoSyntaxError(self):
+        """
+        Public slot to handle the 'Goto syntax error' context menu action.
+        """
+        seline = self.markerFindNext(0, 1 << self.syntaxerror)
+        if seline >= 0:
+            index = 0
+            for handle in self.syntaxerrors:
+                if self.markerLine(handle) == seline:
+                    index = self.syntaxerrors[handle][0][1]
+            self.setCursorPosition(seline, index)
+        self.ensureLineVisible(seline)
+        
+    def clearSyntaxError(self):
+        """
+        Public slot to handle the 'Clear all syntax error' context menu action.
+        """
+        for handle in list(self.syntaxerrors.keys()):
+            line = self.markerLine(handle) + 1
+            self.toggleSyntaxError(line, 0, False)
+        
+        self.syntaxerrors.clear()
+        self.syntaxerrorToggled.emit(self)
+    
+    def __showSyntaxError(self, line=-1):
+        """
+        Private slot to handle the 'Show syntax error message'
+        context menu action.
+        
+        @param line line number to show the syntax error for (integer)
+        """
+        if line == -1:
+            line = self.line
+        
+        for handle in list(self.syntaxerrors.keys()):
+            if self.markerLine(handle) == line:
+                errors = [e[0] for e in self.syntaxerrors[handle]]
+                EricMessageBox.critical(
+                    self,
+                    self.tr("Syntax Error"),
+                    "\n".join(errors))
+                break
+        else:
+            EricMessageBox.critical(
+                self,
+                self.tr("Syntax Error"),
+                self.tr("No syntax error message available."))
+    
+    ###########################################################################
+    ## VCS conflict marker handling methods below
+    ###########################################################################
+
+    def getVcsConflictMarkerLines(self):
+        """
+        Public method to determine the lines containing a VCS conflict marker.
+        
+        @return list of line numbers containg a VCS conflict marker
+        @rtype list of int
+        """
+        conflictMarkerLines = []
+        
+        regExp = re.compile("|".join(Editor.VcsConflictMarkerLineRegExpList),
+                            re.MULTILINE)
+        matches = [m for m in regExp.finditer(self.text())]
+        for match in matches:
+            line, _ = self.lineIndexFromPosition(match.start())
+            conflictMarkerLines.append(line)
+        
+        return conflictMarkerLines
+    
+    ###########################################################################
+    ## Warning handling methods below
+    ###########################################################################
+    
+    def toggleWarning(
+            self, line, col, warning, msg="", warningType=WarningCode):
+        """
+        Public method to toggle a warning indicator.
+        
+        Note: This method is used to set pyflakes and code style warnings.
+        
+        @param line line number of the warning
+        @param col column of the warning
+        @param warning flag indicating if the warning marker should be
+            set or deleted (boolean)
+        @param msg warning message (string)
+        @param warningType type of warning message (integer)
+        """
+        if line == 0:
+            line = 1
+            # hack to show a warning marker, if line is reported to be 0
+        if warning:
+            # set/amend a new warning marker
+            warn = (msg, warningType)
+            markers = self.markersAtLine(line - 1)
+            if not (markers & (1 << self.warning)):
+                handle = self.markerAdd(line - 1, self.warning)
+                self.warnings[handle] = [warn]
+                self.syntaxerrorToggled.emit(self)
+            else:
+                for handle in list(self.warnings.keys()):
+                    if (
+                        self.markerLine(handle) == line - 1 and
+                        warn not in self.warnings[handle]
+                    ):
+                        self.warnings[handle].append(warn)
+        else:
+            for handle in list(self.warnings.keys()):
+                if self.markerLine(handle) == line - 1:
+                    del self.warnings[handle]
+                    self.markerDeleteHandle(handle)
+                    self.syntaxerrorToggled.emit(self)
+        
+        self.__setAnnotation(line - 1)
+        self.__markerMap.update()
+    
+    def getWarnings(self):
+        """
+        Public method to retrieve the warning markers.
+        
+        @return sorted list of all lines containing a warning
+            (list of integer)
+        """
+        fwlist = []
+        for handle in list(self.warnings.keys()):
+            fwlist.append(self.markerLine(handle) + 1)
+        
+        fwlist.sort()
+        return fwlist
+    
+    def getWarningLines(self):
+        """
+        Public method to get the lines containing a warning.
+        
+        @return list of lines containing a warning (list of integer)
+        """
+        lines = []
+        line = -1
+        while True:
+            line = self.markerFindNext(line + 1, 1 << self.warning)
+            if line < 0:
+                break
+            else:
+                lines.append(line)
+        return lines
+        
+    def hasWarnings(self):
+        """
+        Public method to check for the presence of warnings.
+        
+        @return flag indicating the presence of warnings (boolean)
+        """
+        return len(self.warnings) > 0
+    
+    def nextWarning(self):
+        """
+        Public slot to handle the 'Next warning' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == self.lines() - 1:
+            line = 0
+        else:
+            line += 1
+        fwline = self.markerFindNext(line, 1 << self.warning)
+        if fwline < 0:
+            # wrap around
+            fwline = self.markerFindNext(0, 1 << self.warning)
+        if fwline >= 0:
+            self.setCursorPosition(fwline, 0)
+            self.ensureLineVisible(fwline)
+    
+    def previousWarning(self):
+        """
+        Public slot to handle the 'Previous warning' context menu action.
+        """
+        line, index = self.getCursorPosition()
+        if line == 0:
+            line = self.lines() - 1
+        else:
+            line -= 1
+        fwline = self.markerFindPrevious(line, 1 << self.warning)
+        if fwline < 0:
+            # wrap around
+            fwline = self.markerFindPrevious(
+                self.lines() - 1, 1 << self.warning)
+        if fwline >= 0:
+            self.setCursorPosition(fwline, 0)
+            self.ensureLineVisible(fwline)
+    
+    def clearFlakesWarnings(self):
+        """
+        Public slot to clear all pyflakes warnings.
+        """
+        self.__clearTypedWarning(Editor.WarningCode)
+    
+    def clearStyleWarnings(self):
+        """
+        Public slot to clear all style warnings.
+        """
+        self.__clearTypedWarning(Editor.WarningStyle)
+    
+    def __clearTypedWarning(self, warningKind):
+        """
+        Private method to clear warnings of a specific kind.
+        
+        @param warningKind kind of warning to clear (Editor.WarningCode,
+            Editor.WarningStyle)
+        """
+        for handle in list(self.warnings.keys()):
+            warnings = []
+            for msg, warningType in self.warnings[handle]:
+                if warningType == warningKind:
+                    continue
+                
+                warnings.append((msg, warningType))
+            
+            if warnings:
+                self.warnings[handle] = warnings
+                self.__setAnnotation(self.markerLine(handle))
+            else:
+                del self.warnings[handle]
+                self.__setAnnotation(self.markerLine(handle))
+                self.markerDeleteHandle(handle)
+        self.syntaxerrorToggled.emit(self)
+        self.__markerMap.update()
+    
+    def clearWarnings(self):
+        """
+        Public slot to clear all warnings.
+        """
+        for handle in self.warnings:
+            self.warnings[handle] = []
+            self.__setAnnotation(self.markerLine(handle))
+            self.markerDeleteHandle(handle)
+        self.warnings.clear()
+        self.syntaxerrorToggled.emit(self)
+        self.__markerMap.update()
+    
+    def __showWarning(self, line=-1):
+        """
+        Private slot to handle the 'Show warning' context menu action.
+        
+        @param line line number to show the warning for (integer)
+        """
+        if line == -1:
+            line = self.line
+        
+        for handle in list(self.warnings.keys()):
+            if self.markerLine(handle) == line:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("Warning"),
+                    '\n'.join([w[0] for w in self.warnings[handle]]))
+                break
+        else:
+            EricMessageBox.warning(
+                self,
+                self.tr("Warning"),
+                self.tr("No warning messages available."))
+    
+    ###########################################################################
+    ## Annotation handling methods below
+    ###########################################################################
+    
+    def __setAnnotationStyles(self):
+        """
+        Private slot to define the style used by inline annotations.
+        """
+        if hasattr(QsciScintilla, "annotate"):
+            self.annotationWarningStyle = (
+                QsciScintilla.STYLE_LASTPREDEFINED + 1
+            )
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETFORE,
+                self.annotationWarningStyle,
+                Preferences.getEditorColour("AnnotationsWarningForeground"))
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETBACK,
+                self.annotationWarningStyle,
+                Preferences.getEditorColour("AnnotationsWarningBackground"))
+            
+            self.annotationErrorStyle = self.annotationWarningStyle + 1
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETFORE,
+                self.annotationErrorStyle,
+                Preferences.getEditorColour("AnnotationsErrorForeground"))
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETBACK,
+                self.annotationErrorStyle,
+                Preferences.getEditorColour("AnnotationsErrorBackground"))
+            
+            self.annotationStyleStyle = self.annotationErrorStyle + 1
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETFORE,
+                self.annotationStyleStyle,
+                Preferences.getEditorColour("AnnotationsStyleForeground"))
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETBACK,
+                self.annotationStyleStyle,
+                Preferences.getEditorColour("AnnotationsStyleBackground"))
+        
+    def __setAnnotation(self, line):
+        """
+        Private method to set the annotations for the given line.
+        
+        @param line number of the line that needs annotation (integer)
+        """
+        if hasattr(QsciScintilla, "annotate"):
+            warningAnnotations = []
+            errorAnnotations = []
+            styleAnnotations = []
+            
+            # step 1: do warnings
+            for handle in self.warnings:
+                if self.markerLine(handle) == line:
+                    for msg, warningType in self.warnings[handle]:
+                        if warningType == self.WarningStyle:
+                            styleAnnotations.append(
+                                self.tr("Style: {0}").format(msg))
+                        else:
+                            warningAnnotations.append(
+                                self.tr("Warning: {0}").format(msg))
+            
+            # step 2: do syntax errors
+            for handle in self.syntaxerrors:
+                if self.markerLine(handle) == line:
+                    for msg, _ in self.syntaxerrors[handle]:
+                        errorAnnotations.append(
+                            self.tr("Error: {0}").format(msg))
+            
+            annotations = []
+            if styleAnnotations:
+                annotationStyleTxt = "\n".join(styleAnnotations)
+                if warningAnnotations or errorAnnotations:
+                    annotationStyleTxt += "\n"
+                annotations.append(QsciStyledText(
+                    annotationStyleTxt, self.annotationStyleStyle))
+            
+            if warningAnnotations:
+                annotationWarningTxt = "\n".join(warningAnnotations)
+                if errorAnnotations:
+                    annotationWarningTxt += "\n"
+                annotations.append(QsciStyledText(
+                    annotationWarningTxt, self.annotationWarningStyle))
+            
+            if errorAnnotations:
+                annotationErrorTxt = "\n".join(errorAnnotations)
+                annotations.append(QsciStyledText(
+                    annotationErrorTxt, self.annotationErrorStyle))
+            
+            if annotations:
+                self.annotate(line, annotations)
+            else:
+                self.clearAnnotations(line)
+    
+    def __refreshAnnotations(self):
+        """
+        Private method to refresh the annotations.
+        """
+        if hasattr(QsciScintilla, "annotate"):
+            self.clearAnnotations()
+            for handle in (
+                list(self.warnings.keys()) +
+                list(self.syntaxerrors.keys())
+            ):
+                line = self.markerLine(handle)
+                self.__setAnnotation(line)
+    
+    #################################################################
+    ## Fold handling methods
+    #################################################################
+    
+    def toggleCurrentFold(self):
+        """
+        Public slot to toggle the fold containing the current line.
+        """
+        line, index = self.getCursorPosition()
+        self.foldLine(line)
+    
+    def expandFoldWithChildren(self, line=-1):
+        """
+        Public slot to expand the current fold including its children.
+        
+        @param line number of line to be expanded
+        @type int
+        """
+        if line == -1:
+            line, index = self.getCursorPosition()
+        
+        self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line,
+                           QsciScintilla.SC_FOLDACTION_EXPAND)
+    
+    def collapseFoldWithChildren(self, line=-1):
+        """
+        Public slot to collapse the current fold including its children.
+        
+        @param line number of line to be expanded
+        @type int
+        """
+        if line == -1:
+            line, index = self.getCursorPosition()
+        
+        self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line,
+                           QsciScintilla.SC_FOLDACTION_CONTRACT)
+    
+    def __contextMenuExpandFoldWithChildren(self):
+        """
+        Private slot to handle the context menu expand with children action.
+        """
+        self.expandFoldWithChildren(self.line)
+    
+    def __contextMenuCollapseFoldWithChildren(self):
+        """
+        Private slot to handle the context menu collapse with children action.
+        """
+        self.collapseFoldWithChildren(self.line)
+    
+    #################################################################
+    ## Macro handling methods
+    #################################################################
+    
+    def __getMacroName(self):
+        """
+        Private method to select a macro name from the list of macros.
+        
+        @return Tuple of macro name and a flag, indicating, if the user
+            pressed ok or canceled the operation. (string, boolean)
+        """
+        qs = []
+        for s in list(self.macros.keys()):
+            qs.append(s)
+        qs.sort()
+        return QInputDialog.getItem(
+            self,
+            self.tr("Macro Name"),
+            self.tr("Select a macro name:"),
+            qs,
+            0, False)
+        
+    def macroRun(self):
+        """
+        Public method to execute a macro.
+        """
+        name, ok = self.__getMacroName()
+        if ok and name:
+            self.macros[name].play()
+        
+    def macroDelete(self):
+        """
+        Public method to delete a macro.
+        """
+        name, ok = self.__getMacroName()
+        if ok and name:
+            del self.macros[name]
+        
+    def macroLoad(self):
+        """
+        Public method to load a macro from a file.
+        """
+        configDir = Utilities.getConfigDir()
+        fname = EricFileDialog.getOpenFileName(
+            self,
+            self.tr("Load macro file"),
+            configDir,
+            self.tr("Macro files (*.macro)"))
+        
+        if not fname:
+            return  # user aborted
+        
+        try:
+            with open(fname, "r", encoding="utf-8") as f:
+                lines = f.readlines()
+        except OSError:
+            EricMessageBox.critical(
+                self,
+                self.tr("Error loading macro"),
+                self.tr(
+                    "<p>The macro file <b>{0}</b> could not be read.</p>")
+                .format(fname))
+            return
+        
+        if len(lines) != 2:
+            EricMessageBox.critical(
+                self,
+                self.tr("Error loading macro"),
+                self.tr("<p>The macro file <b>{0}</b> is corrupt.</p>")
+                .format(fname))
+            return
+        
+        macro = QsciMacro(lines[1], self)
+        self.macros[lines[0].strip()] = macro
+        
+    def macroSave(self):
+        """
+        Public method to save a macro to a file.
+        """
+        configDir = Utilities.getConfigDir()
+        
+        name, ok = self.__getMacroName()
+        if not ok or not name:
+            return  # user abort
+        
+        fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+            self,
+            self.tr("Save macro file"),
+            configDir,
+            self.tr("Macro files (*.macro)"),
+            "",
+            EricFileDialog.DontConfirmOverwrite)
+        
+        if not fname:
+            return  # user aborted
+        
+        fpath = pathlib.Path(fname)
+        if not fpath.suffix:
+            ex = selectedFilter.split("(*")[1].split(")")[0]
+            if ex:
+                fpath = fpath.with_suffix(ex)
+        if fpath.exists():
+            res = EricMessageBox.yesNo(
+                self,
+                self.tr("Save macro"),
+                self.tr("<p>The macro file <b>{0}</b> already exists."
+                        " Overwrite it?</p>").format(fpath),
+                icon=EricMessageBox.Warning)
+            if not res:
+                return
+        
+        try:
+            with fpath.open("w", encoding="utf-8") as f:
+                f.write("{0}{1}".format(name, "\n"))
+                f.write(self.macros[name].save())
+        except OSError:
+            EricMessageBox.critical(
+                self,
+                self.tr("Error saving macro"),
+                self.tr(
+                    "<p>The macro file <b>{0}</b> could not be written.</p>")
+                .format(fpath))
+            return
+        
+    def macroRecordingStart(self):
+        """
+        Public method to start macro recording.
+        """
+        if self.recording:
+            res = EricMessageBox.yesNo(
+                self,
+                self.tr("Start Macro Recording"),
+                self.tr("Macro recording is already active. Start new?"),
+                icon=EricMessageBox.Warning,
+                yesDefault=True)
+            if res:
+                self.macroRecordingStop()
+            else:
+                return
+        else:
+            self.recording = True
+        
+        self.curMacro = QsciMacro(self)
+        self.curMacro.startRecording()
+        
+    def macroRecordingStop(self):
+        """
+        Public method to stop macro recording.
+        """
+        if not self.recording:
+            return      # we are not recording
+        
+        self.curMacro.endRecording()
+        self.recording = False
+        
+        name, ok = QInputDialog.getText(
+            self,
+            self.tr("Macro Recording"),
+            self.tr("Enter name of the macro:"),
+            QLineEdit.EchoMode.Normal)
+        
+        if ok and name:
+            self.macros[name] = self.curMacro
+        
+        self.curMacro = None
+        
+    #################################################################
+    ## Overwritten methods
+    #################################################################
+    
+    def undo(self):
+        """
+        Public method to undo the last recorded change.
+        """
+        super().undo()
+        self.undoAvailable.emit(self.isUndoAvailable())
+        self.redoAvailable.emit(self.isRedoAvailable())
+        
+    def redo(self):
+        """
+        Public method to redo the last recorded change.
+        """
+        super().redo()
+        self.undoAvailable.emit(self.isUndoAvailable())
+        self.redoAvailable.emit(self.isRedoAvailable())
+        
+    def close(self, alsoDelete=False):
+        """
+        Public method called when the window gets closed.
+        
+        This overwritten method redirects the action to our
+        ViewManager.closeEditor, which in turn calls our closeIt
+        method.
+        
+        @param alsoDelete ignored
+        @return flag indicating a successful close of the editor (boolean)
+        """
+        return self.vm.closeEditor(self)
+        
+    def closeIt(self):
+        """
+        Public method called by the viewmanager to finally get rid of us.
+        """
+        if Preferences.getEditor("ClearBreaksOnClose") and not self.__clones:
+            self.__menuClearBreakpoints()
+        
+        for clone in self.__clones[:]:
+            self.removeClone(clone)
+            clone.removeClone(self)
+        
+        self.breakpointModel.rowsAboutToBeRemoved.disconnect(
+            self.__deleteBreakPoints)
+        self.breakpointModel.dataAboutToBeChanged.disconnect(
+            self.__breakPointDataAboutToBeChanged)
+        self.breakpointModel.dataChanged.disconnect(
+            self.__changeBreakPoints)
+        self.breakpointModel.rowsInserted.disconnect(
+            self.__addBreakPoints)
+        
+        if self.syntaxCheckService is not None:
+            self.syntaxCheckService.syntaxChecked.disconnect(
+                self.__processSyntaxCheckResult)
+            self.syntaxCheckService.error.disconnect(
+                self.__processSyntaxCheckError)
+        
+        if self.spell:
+            self.spell.stopIncrementalCheck()
+        
+        with contextlib.suppress(TypeError):
+            self.project.projectPropertiesChanged.disconnect(
+                self.__projectPropertiesChanged)
+        
+        if self.fileName:
+            self.taskViewer.clearFileTasks(self.fileName, True)
+        
+        super().close()
+        
+    def keyPressEvent(self, ev):
+        """
+        Protected method to handle the user input a key at a time.
+        
+        @param ev key event
+        @type QKeyEvent
+        """
+        def encloseSelectedText(encString):
+            """
+            Local function to enclose the current selection with some
+            characters.
+            
+            @param encString string to use to enclose the selection
+                (one or two characters)
+            @type str
+            """
+            startChar = encString[0]
+            endChar = encString[1] if len(encString) == 2 else startChar
+            
+            sline, sindex, eline, eindex = self.getSelection()
+            replaceText = startChar + self.selectedText() + endChar
+            self.beginUndoAction()
+            self.replaceSelectedText(replaceText)
+            self.endUndoAction()
+            self.setSelection(sline, sindex + 1, eline, eindex + 1)
+        
+        txt = ev.text()
+        
+        # See it is text to insert.
+        if len(txt) and txt >= " ":
+            if (
+                self.hasSelectedText() and
+                txt in Editor.EncloseChars
+            ):
+                encloseSelectedText(Editor.EncloseChars[txt])
+                ev.accept()
+                return
+            
+            super().keyPressEvent(ev)
+        else:
+            ev.ignore()
+        
+    def focusInEvent(self, event):
+        """
+        Protected method called when the editor receives focus.
+        
+        This method checks for modifications of the current file and
+        rereads it upon request. The cursor is placed at the current position
+        assuming, that it is in the vicinity of the old position after the
+        reread.
+        
+        @param event the event object
+        @type QFocusEvent
+        """
+        self.recolor()
+        self.vm.editActGrp.setEnabled(True)
+        self.vm.editorActGrp.setEnabled(True)
+        self.vm.copyActGrp.setEnabled(True)
+        self.vm.viewActGrp.setEnabled(True)
+        self.vm.searchActGrp.setEnabled(True)
+        with contextlib.suppress(AttributeError):
+            self.setCaretWidth(self.caretWidth)
+        self.__updateReadOnly(False)
+        if (
+            self.vm.editorsCheckFocusInEnabled() and
+            not self.inReopenPrompt and self.fileName and
+            pathlib.Path(self.fileName).exists() and
+            pathlib.Path(self.fileName).stat().st_mtime != self.lastModified
+        ):
+            self.inReopenPrompt = True
+            if Preferences.getEditor("AutoReopen") and not self.isModified():
+                self.refresh()
+            else:
+                msg = self.tr(
+                    """<p>The file <b>{0}</b> has been changed while it"""
+                    """ was opened in eric. Reread it?</p>"""
+                ).format(self.fileName)
+                yesDefault = True
+                if self.isModified():
+                    msg += self.tr(
+                        """<br><b>Warning:</b> You will lose"""
+                        """ your changes upon reopening it.""")
+                    yesDefault = False
+                res = EricMessageBox.yesNo(
+                    self,
+                    self.tr("File changed"), msg,
+                    icon=EricMessageBox.Warning,
+                    yesDefault=yesDefault)
+                if res:
+                    self.refresh()
+                else:
+                    # do not prompt for this change again...
+                    self.lastModified = (
+                        pathlib.Path(self.fileName).stat().st_mtime
+                    )
+            self.inReopenPrompt = False
+        
+        self.setCursorFlashTime(QApplication.cursorFlashTime())
+        
+        super().focusInEvent(event)
+        
+    def focusOutEvent(self, event):
+        """
+        Protected method called when the editor loses focus.
+        
+        @param event the event object
+        @type QFocusEvent
+        """
+        self.vm.editorActGrp.setEnabled(False)
+        self.setCaretWidth(0)
+        
+        self.cancelCallTips()
+        
+        super().focusOutEvent(event)
+        
+    def changeEvent(self, evt):
+        """
+        Protected method called to process an event.
+        
+        This implements special handling for the events showMaximized,
+        showMinimized and showNormal. The windows caption is shortened
+        for the minimized mode and reset to the full filename for the
+        other modes. This is to make the editor windows work nicer
+        with the QWorkspace.
+        
+        @param evt the event, that was generated
+        @type QEvent
+        """
+        if (
+            evt.type() == QEvent.Type.WindowStateChange and
+            bool(self.fileName)
+        ):
+            cap = (
+                os.path.basename(self.fileName)
+                if self.windowState() == Qt.WindowState.WindowMinimized else
+                self.fileName
+            )
+            if self.isReadOnly():
+                cap = self.tr("{0} (ro)").format(cap)
+            self.setWindowTitle(cap)
+        
+        super().changeEvent(evt)
+        
+    def mousePressEvent(self, event):
+        """
+        Protected method to handle the mouse press event.
+        
+        @param event the mouse press event
+        @type QMouseEvent
+        """
+        if event.button() == Qt.MouseButton.XButton1:
+            self.undo()
+            event.accept()
+        elif event.button() == Qt.MouseButton.XButton2:
+            self.redo()
+            event.accept()
+        elif (
+            event.button() == Qt.MouseButton.LeftButton and
+            bool(event.modifiers() & (
+                Qt.KeyboardModifier.MetaModifier |
+                Qt.KeyboardModifier.AltModifier
+            ))
+        ):
+            line, index = self.lineIndexFromPoint(event.position().toPoint())
+            self.addCursor(line, index)
+            event.accept()
+        else:
+            self.vm.eventFilter(self, event)
+            super().mousePressEvent(event)
+    
+    def mouseDoubleClickEvent(self, evt):
+        """
+        Protected method to handle mouse double click events.
+        
+        @param evt reference to the mouse event
+        @type QMouseEvent
+        """
+        super().mouseDoubleClickEvent(evt)
+        
+        self.mouseDoubleClick.emit(evt.position().toPoint(), evt.buttons())
+        
+    def wheelEvent(self, evt):
+        """
+        Protected method to handle wheel events.
+        
+        @param evt reference to the wheel event
+        @type QWheelEvent
+        """
+        delta = evt.angleDelta().y()
+        if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
+            if delta < 0:
+                self.zoomOut()
+            elif delta > 0:
+                self.zoomIn()
+            evt.accept()
+            return
+        
+        if evt.modifiers() & Qt.KeyboardModifier.ShiftModifier:
+            if delta < 0:
+                self.gotoMethodClass(False)
+            elif delta > 0:
+                self.gotoMethodClass(True)
+            evt.accept()
+            return
+        
+        super().wheelEvent(evt)
+    
+    def event(self, evt):
+        """
+        Public method handling events.
+        
+        @param evt reference to the event
+        @type QEvent
+        @return flag indicating, if the event was handled
+        @rtype bool
+        """
+        if evt.type() == QEvent.Type.Gesture:
+            self.gestureEvent(evt)
+            return True
+        
+        return super().event(evt)
+    
+    def gestureEvent(self, evt):
+        """
+        Protected method handling gesture events.
+        
+        @param evt reference to the gesture event
+        @type QGestureEvent
+        """
+        pinch = evt.gesture(Qt.GestureType.PinchGesture)
+        if pinch:
+            if pinch.state() == Qt.GestureState.GestureStarted:
+                zoom = (self.getZoom() + 10) / 10.0
+                pinch.setTotalScaleFactor(zoom)
+            elif pinch.state() == Qt.GestureState.GestureUpdated:
+                zoom = int(pinch.totalScaleFactor() * 10) - 10
+                if zoom <= -9:
+                    zoom = -9
+                    pinch.setTotalScaleFactor(0.1)
+                elif zoom >= 20:
+                    zoom = 20
+                    pinch.setTotalScaleFactor(3.0)
+                self.zoomTo(zoom)
+            evt.accept()
+    
+    def resizeEvent(self, evt):
+        """
+        Protected method handling resize events.
+        
+        @param evt reference to the resize event
+        @type QResizeEvent
+        """
+        super().resizeEvent(evt)
+        self.__markerMap.calculateGeometry()
+    
+    def viewportEvent(self, evt):
+        """
+        Protected method handling event of the viewport.
+        
+        @param evt reference to the event
+        @type QEvent
+        @return flag indiating that the event was handled
+        @rtype bool
+        """
+        with contextlib.suppress(AttributeError):
+            self.__markerMap.calculateGeometry()
+        return super().viewportEvent(evt)
+    
+    def __updateReadOnly(self, bForce=True):
+        """
+        Private method to update the readOnly information for this editor.
+        
+        If bForce is True, then updates everything regardless if
+        the attributes have actually changed, such as during
+        initialization time.  A signal is emitted after the
+        caption change.
+
+        @param bForce True to force change, False to only update and emit
+                signal if there was an attribute change.
+        """
+        if self.fileName == "":
+            return
+        
+        readOnly = (
+            not os.access(self.fileName, os.W_OK) or
+            self.isReadOnly()
+        )
+        if not bForce and (readOnly == self.isReadOnly()):
+            return
+        
+        cap = self.fileName
+        if readOnly:
+            cap = self.tr("{0} (ro)".format(cap))
+        self.setReadOnly(readOnly)
+        self.setWindowTitle(cap)
+        self.captionChanged.emit(cap, self)
+        
+    def refresh(self):
+        """
+        Public slot to refresh the editor contents.
+        """
+        # save cursor position
+        cline, cindex = self.getCursorPosition()
+        
+        # save bookmarks and breakpoints and clear them
+        bmlist = self.getBookmarks()
+        self.clearBookmarks()
+        
+        # clear syntax error markers
+        self.clearSyntaxError()
+        
+        # clear flakes warning markers
+        self.clearWarnings()
+        
+        # clear breakpoint markers
+        for handle in list(self.breaks.keys()):
+            self.markerDeleteHandle(handle)
+        self.breaks.clear()
+        
+        if not os.path.exists(self.fileName):
+            # close the file, if it was deleted in the background
+            self.close()
+            return
+        
+        # reread the file
+        try:
+            self.readFile(self.fileName)
+        except OSError:
+            # do not prompt for this change again...
+            self.lastModified = QDateTime.currentDateTime()
+        self.setModified(False)
+        self.__convertTabs()
+        
+        # re-initialize the online change tracer
+        self.__reinitOnlineChangeTrace()
+        
+        # reset cursor position
+        self.setCursorPosition(cline, cindex)
+        self.ensureCursorVisible()
+        
+        # reset bookmarks and breakpoints to their old position
+        if bmlist:
+            for bm in bmlist:
+                self.toggleBookmark(bm)
+        self.__restoreBreakpoints()
+        
+        self.editorSaved.emit(self.fileName)
+        self.checkSyntax()
+        
+        self.__markerMap.update()
+        
+        self.refreshed.emit()
+        
+    def setMonospaced(self, on):
+        """
+        Public method to set/reset a monospaced font.
+        
+        @param on flag to indicate usage of a monospace font (boolean)
+        """
+        if on:
+            if not self.lexer_:
+                f = Preferences.getEditorOtherFonts("MonospacedFont")
+                self.monospacedStyles(f)
+        else:
+            if not self.lexer_:
+                self.clearStyles()
+                self.__setMarginsDisplay()
+            self.setFont(Preferences.getEditorOtherFonts("DefaultFont"))
+        
+        self.useMonospaced = on
+    
+    def clearStyles(self):
+        """
+        Public method to set the styles according the selected Qt style
+        or the selected editor colours.
+        """
+        super().clearStyles()
+        if Preferences.getEditor("OverrideEditAreaColours"):
+            self.setColor(Preferences.getEditorColour("EditAreaForeground"))
+            self.setPaper(Preferences.getEditorColour("EditAreaBackground"))
+    
+    #################################################################
+    ## Drag and Drop Support
+    #################################################################
+    
+    def dragEnterEvent(self, event):
+        """
+        Protected method to handle the drag enter event.
+        
+        @param event the drag enter event (QDragEnterEvent)
+        """
+        self.inDragDrop = event.mimeData().hasUrls()
+        if self.inDragDrop:
+            event.acceptProposedAction()
+        else:
+            super().dragEnterEvent(event)
+        
+    def dragMoveEvent(self, event):
+        """
+        Protected method to handle the drag move event.
+        
+        @param event the drag move event (QDragMoveEvent)
+        """
+        if self.inDragDrop:
+            event.accept()
+        else:
+            super().dragMoveEvent(event)
+        
+    def dragLeaveEvent(self, event):
+        """
+        Protected method to handle the drag leave event.
+        
+        @param event the drag leave event (QDragLeaveEvent)
+        """
+        if self.inDragDrop:
+            self.inDragDrop = False
+            event.accept()
+        else:
+            super().dragLeaveEvent(event)
+        
+    def dropEvent(self, event):
+        """
+        Protected method to handle the drop event.
+        
+        @param event the drop event (QDropEvent)
+        """
+        if event.mimeData().hasUrls():
+            for url in event.mimeData().urls():
+                fname = url.toLocalFile()
+                if fname:
+                    if not pathlib.Path(fname).is_dir():
+                        self.vm.openSourceFile(fname)
+                    else:
+                        EricMessageBox.information(
+                            self,
+                            self.tr("Drop Error"),
+                            self.tr("""<p><b>{0}</b> is not a file.</p>""")
+                            .format(fname))
+            event.acceptProposedAction()
+        else:
+            super().dropEvent(event)
+        
+        self.inDragDrop = False
+    
+    #################################################################
+    ## Support for Qt resources files
+    #################################################################
+    
+    def __initContextMenuResources(self):
+        """
+        Private method used to setup the Resources context sub menu.
+        
+        @return reference to the generated menu (QMenu)
+        """
+        menu = QMenu(self.tr('Resources'))
+        
+        menu.addAction(
+            self.tr('Add file...'), self.__addFileResource)
+        menu.addAction(
+            self.tr('Add files...'), self.__addFileResources)
+        menu.addAction(
+            self.tr('Add aliased file...'),
+            self.__addFileAliasResource)
+        menu.addAction(
+            self.tr('Add localized resource...'),
+            self.__addLocalizedResource)
+        menu.addSeparator()
+        menu.addAction(
+            self.tr('Add resource frame'), self.__addResourceFrame)
+        
+        menu.aboutToShow.connect(self.__showContextMenuResources)
+        
+        return menu
+        
+    def __showContextMenuResources(self):
+        """
+        Private slot handling the aboutToShow signal of the resources context
+        menu.
+        """
+        self.showMenu.emit("Resources", self.resourcesMenu, self)
+        
+    def __addFileResource(self):
+        """
+        Private method to handle the Add file context menu action.
+        """
+        dirStr = os.path.dirname(self.fileName)
+        file = EricFileDialog.getOpenFileName(
+            self,
+            self.tr("Add file resource"),
+            dirStr,
+            "")
+        if file:
+            relFile = QDir(dirStr).relativeFilePath(file)
+            line, index = self.getCursorPosition()
+            self.insert("  <file>{0}</file>\n".format(relFile))
+            self.setCursorPosition(line + 1, index)
+        
+    def __addFileResources(self):
+        """
+        Private method to handle the Add files context menu action.
+        """
+        dirStr = os.path.dirname(self.fileName)
+        files = EricFileDialog.getOpenFileNames(
+            self,
+            self.tr("Add file resources"),
+            dirStr,
+            "")
+        if files:
+            myDir = QDir(dirStr)
+            filesText = ""
+            for file in files:
+                relFile = myDir.relativeFilePath(file)
+                filesText += "  <file>{0}</file>\n".format(relFile)
+            line, index = self.getCursorPosition()
+            self.insert(filesText)
+            self.setCursorPosition(line + len(files), index)
+        
+    def __addFileAliasResource(self):
+        """
+        Private method to handle the Add aliased file context menu action.
+        """
+        dirStr = os.path.dirname(self.fileName)
+        file = EricFileDialog.getOpenFileName(
+            self,
+            self.tr("Add aliased file resource"),
+            dirStr,
+            "")
+        if file:
+            relFile = QDir(dirStr).relativeFilePath(file)
+            alias, ok = QInputDialog.getText(
+                self,
+                self.tr("Add aliased file resource"),
+                self.tr("Alias for file <b>{0}</b>:").format(relFile),
+                QLineEdit.EchoMode.Normal,
+                relFile)
+            if ok and alias:
+                line, index = self.getCursorPosition()
+                self.insert('  <file alias="{1}">{0}</file>\n'
+                            .format(relFile, alias))
+                self.setCursorPosition(line + 1, index)
+        
+    def __addLocalizedResource(self):
+        """
+        Private method to handle the Add localized resource context menu
+        action.
+        """
+        from Project.AddLanguageDialog import AddLanguageDialog
+        dlg = AddLanguageDialog(self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            lang = dlg.getSelectedLanguage()
+            line, index = self.getCursorPosition()
+            self.insert('<qresource lang="{0}">\n</qresource>\n'.format(lang))
+            self.setCursorPosition(line + 2, index)
+        
+    def __addResourceFrame(self):
+        """
+        Private method to handle the Add resource frame context menu action.
+        """
+        line, index = self.getCursorPosition()
+        self.insert('<!DOCTYPE RCC>\n'
+                    '<RCC version="1.0">\n'
+                    '<qresource>\n'
+                    '</qresource>\n'
+                    '</RCC>\n')
+        self.setCursorPosition(line + 5, index)
+    
+    #################################################################
+    ## Support for diagrams below
+    #################################################################
+    
+    def __showClassDiagram(self):
+        """
+        Private method to handle the Class Diagram context menu action.
+        """
+        from Graphics.UMLDialog import UMLDialog, UMLDialogType
+        if not self.checkDirty():
+            return
+        
+        self.classDiagram = UMLDialog(
+            UMLDialogType.CLASS_DIAGRAM, self.project, self.fileName,
+            self, noAttrs=False)
+        self.classDiagram.show()
+        
+    def __showPackageDiagram(self):
+        """
+        Private method to handle the Package Diagram context menu action.
+        """
+        from Graphics.UMLDialog import UMLDialog, UMLDialogType
+        if not self.checkDirty():
+            return
+        
+        package = (
+            os.path.isdir(self.fileName) and
+            self.fileName or os.path.dirname(self.fileName)
+        )
+        res = EricMessageBox.yesNo(
+            self,
+            self.tr("Package Diagram"),
+            self.tr("""Include class attributes?"""),
+            yesDefault=True)
+        self.packageDiagram = UMLDialog(
+            UMLDialogType.PACKAGE_DIAGRAM, self.project, package,
+            self, noAttrs=not res)
+        self.packageDiagram.show()
+        
+    def __showImportsDiagram(self):
+        """
+        Private method to handle the Imports Diagram context menu action.
+        """
+        from Graphics.UMLDialog import UMLDialog, UMLDialogType
+        if not self.checkDirty():
+            return
+        
+        package = os.path.dirname(self.fileName)
+        res = EricMessageBox.yesNo(
+            self,
+            self.tr("Imports Diagram"),
+            self.tr("""Include imports from external modules?"""))
+        self.importsDiagram = UMLDialog(
+            UMLDialogType.IMPORTS_DIAGRAM, self.project, package,
+            self, showExternalImports=res)
+        self.importsDiagram.show()
+        
+    def __showApplicationDiagram(self):
+        """
+        Private method to handle the Imports Diagram context menu action.
+        """
+        from Graphics.UMLDialog import UMLDialog, UMLDialogType
+        res = EricMessageBox.yesNo(
+            self,
+            self.tr("Application Diagram"),
+            self.tr("""Include module names?"""),
+            yesDefault=True)
+        self.applicationDiagram = UMLDialog(
+            UMLDialogType.APPLICATION_DIAGRAM, self.project,
+            self, noModules=not res)
+        self.applicationDiagram.show()
+    
+    def __loadDiagram(self):
+        """
+        Private slot to load a diagram from file.
+        """
+        from Graphics.UMLDialog import UMLDialog, UMLDialogType
+        self.loadedDiagram = UMLDialog(
+            UMLDialogType.NO_DIAGRAM, self.project, parent=self)
+        if self.loadedDiagram.load():
+            self.loadedDiagram.show(fromFile=True)
+        else:
+            self.loadedDiagram = None
+    
+    #######################################################################
+    ## Typing aids related methods below
+    #######################################################################
+    
+    def __toggleTypingAids(self):
+        """
+        Private slot to toggle the typing aids.
+        """
+        if self.menuActs["TypingAidsEnabled"].isChecked():
+            self.completer.setEnabled(True)
+        else:
+            self.completer.setEnabled(False)
+    
+    #######################################################################
+    ## Auto-completing templates
+    #######################################################################
+    
+    def editorCommand(self, cmd):
+        """
+        Public method to perform a simple editor command.
+        
+        @param cmd the scintilla command to be performed
+        """
+        if cmd == QsciScintilla.SCI_TAB:
+            try:
+                templateViewer = ericApp().getObject("TemplateViewer")
+            except KeyError:
+                # template viewer is not active
+                templateViewer = None
+            
+            if templateViewer is not None:
+                line, index = self.getCursorPosition()
+                tmplName = self.getWordLeft(line, index)
+                if tmplName:
+                    if templateViewer.hasTemplate(tmplName,
+                                                  self.getLanguage()):
+                        self.__applyTemplate(tmplName, self.getLanguage())
+                        return
+                    else:
+                        templateNames = templateViewer.getTemplateNames(
+                            tmplName, self.getLanguage())
+                        if len(templateNames) == 1:
+                            self.__applyTemplate(templateNames[0],
+                                                 self.getLanguage())
+                            return
+                        elif len(templateNames) > 1:
+                            self.showUserList(
+                                TemplateCompletionListID,
+                                ["{0}?{1:d}".format(t, self.TemplateImageID)
+                                 for t in templateNames])
+                            return
+        
+        elif cmd == QsciScintilla.SCI_DELETEBACK:
+            line, index = self.getCursorPosition()
+            text = self.text(line)[index - 1:index + 1]
+            matchingPairs = ['()', '[]', '{}', '<>', "''", '""']
+            # __IGNORE_WARNING_M613__
+            if text in matchingPairs:
+                self.delete()
+        
+        super().editorCommand(cmd)
+    
+    def __applyTemplate(self, templateName, language):
+        """
+        Private method to apply a template by name.
+        
+        @param templateName name of the template to apply (string)
+        @param language name of the language (group) to get the template
+            from (string)
+        """
+        try:
+            templateViewer = ericApp().getObject("TemplateViewer")
+        except KeyError:
+            # template viewer is not active
+            return
+        
+        if templateViewer.hasTemplate(templateName, self.getLanguage()):
+            self.extendSelectionWordLeft()
+            templateViewer.applyNamedTemplate(templateName,
+                                              self.getLanguage())
+    
+    #######################################################################
+    ## Project related methods
+    #######################################################################
+    
+    def __projectPropertiesChanged(self):
+        """
+        Private slot to handle changes of the project properties.
+        """
+        if self.spell:
+            pwl, pel = self.project.getProjectDictionaries()
+            self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
+                                       pwl=pwl, pel=pel)
+        
+        editorConfigEol = self.__getEditorConfig("EOLMode", nodefault=True)
+        if editorConfigEol is not None:
+            self.setEolMode(editorConfigEol)
+        else:
+            self.setEolModeByEolString(self.project.getEolString())
+        self.convertEols(self.eolMode())
+    
+    def addedToProject(self):
+        """
+        Public method to signal, that this editor has been added to a project.
+        """
+        if self.spell:
+            pwl, pel = self.project.getProjectDictionaries()
+            self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
+                                       pwl=pwl, pel=pel)
+        
+        self.project.projectPropertiesChanged.connect(
+            self.__projectPropertiesChanged)
+    
+    def projectOpened(self):
+        """
+        Public slot to handle the opening of a project.
+        """
+        if (
+            self.fileName and
+            self.project.isProjectSource(self.fileName)
+        ):
+            self.project.projectPropertiesChanged.connect(
+                self.__projectPropertiesChanged)
+            self.setSpellingForProject()
+    
+    def projectClosed(self):
+        """
+        Public slot to handle the closing of a project.
+        """
+        with contextlib.suppress(TypeError):
+            self.project.projectPropertiesChanged.disconnect(
+                self.__projectPropertiesChanged)
+    
+    #######################################################################
+    ## Spell checking related methods
+    #######################################################################
+    
+    def getSpellingLanguage(self):
+        """
+        Public method to get the current spelling language.
+        
+        @return current spelling language
+        @rtype str
+        """
+        if self.spell:
+            return self.spell.getLanguage()
+        
+        return ""
+    
+    def __setSpellingLanguage(self, language, pwl="", pel=""):
+        """
+        Private slot to set the spell checking language.
+        
+        @param language spell checking language to be set (string)
+        @param pwl name of the personal/project word list (string)
+        @param pel name of the personal/project exclude list (string)
+        """
+        if self.spell and self.spell.getLanguage() != language:
+            self.spell.setLanguage(language, pwl=pwl, pel=pel)
+            self.spell.checkDocumentIncrementally()
+    
+    def __setSpelling(self):
+        """
+        Private method to initialize the spell checking functionality.
+        """
+        if Preferences.getEditor("SpellCheckingEnabled"):
+            self.__spellCheckStringsOnly = Preferences.getEditor(
+                "SpellCheckStringsOnly")
+            if self.spell is None:
+                self.spell = SpellChecker(self, self.spellingIndicator,
+                                          checkRegion=self.isSpellCheckRegion)
+            self.setSpellingForProject()
+            self.spell.setMinimumWordSize(
+                Preferences.getEditor("SpellCheckingMinWordSize"))
+            
+            self.setAutoSpellChecking()
+        else:
+            self.spell = None
+            self.clearAllIndicators(self.spellingIndicator)
+
+    def setSpellingForProject(self):
+        """
+        Public method to set the spell checking options for files belonging
+        to the current project.
+        """
+        if (
+            self.fileName and
+            self.project.isOpen() and
+            self.project.isProjectSource(self.fileName)
+        ):
+            pwl, pel = self.project.getProjectDictionaries()
+            self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
+                                       pwl=pwl, pel=pel)
+    
+    def setAutoSpellChecking(self):
+        """
+        Public method to set the automatic spell checking.
+        """
+        if Preferences.getEditor("AutoSpellCheckingEnabled"):
+            with contextlib.suppress(TypeError):
+                self.SCN_CHARADDED.connect(
+                    self.__spellCharAdded, Qt.ConnectionType.UniqueConnection)
+            self.spell.checkDocumentIncrementally()
+        else:
+            with contextlib.suppress(TypeError):
+                self.SCN_CHARADDED.disconnect(self.__spellCharAdded)
+            self.clearAllIndicators(self.spellingIndicator)
+    
+    def isSpellCheckRegion(self, pos):
+        """
+        Public method to check, if the given position is within a region, that
+        should be spell checked.
+        
+        For files with a configured full text file extension all regions will
+        be regarded as to be checked. Depending on configuration, all unknown
+        files (i.e. those without a file extension) will be checked fully as
+        well.
+        
+        @param pos position to be checked
+        @type int
+        @return flag indicating pos is in a spell check region
+        @rtype bool
+        """
+        if self.__spellCheckStringsOnly:
+            if (
+                (self.__fileNameExtension in
+                 Preferences.getEditor("FullSpellCheckExtensions")) or
+                (not self.__fileNameExtension and
+                 Preferences.getEditor("FullSpellCheckUnknown"))
+            ):
+                return True
+            else:
+                style = self.styleAt(pos)
+                if self.lexer_ is not None:
+                    return (
+                        self.lexer_.isCommentStyle(style) or
+                        self.lexer_.isStringStyle(style)
+                    )
+        
+        return True
+    
+    @pyqtSlot(int)
+    def __spellCharAdded(self, charNumber):
+        """
+        Private slot called to handle the user entering a character.
+        
+        @param charNumber value of the character entered (integer)
+        """
+        if self.spell:
+            if not chr(charNumber).isalnum():
+                self.spell.checkWord(
+                    self.positionBefore(self.currentPosition()), True)
+            elif self.hasIndicator(
+                    self.spellingIndicator, self.currentPosition()):
+                self.spell.checkWord(self.currentPosition())
+    
+    def checkSpelling(self):
+        """
+        Public slot to perform an interactive spell check of the document.
+        """
+        if self.spell:
+            cline, cindex = self.getCursorPosition()
+            from .SpellCheckingDialog import SpellCheckingDialog
+            dlg = SpellCheckingDialog(self.spell, 0, self.length(), self)
+            dlg.exec()
+            self.setCursorPosition(cline, cindex)
+            if Preferences.getEditor("AutoSpellCheckingEnabled"):
+                self.spell.checkDocumentIncrementally()
+    
+    def __checkSpellingSelection(self):
+        """
+        Private slot to spell check the current selection.
+        """
+        from .SpellCheckingDialog import SpellCheckingDialog
+        sline, sindex, eline, eindex = self.getSelection()
+        startPos = self.positionFromLineIndex(sline, sindex)
+        endPos = self.positionFromLineIndex(eline, eindex)
+        dlg = SpellCheckingDialog(self.spell, startPos, endPos, self)
+        dlg.exec()
+    
+    def __checkSpellingWord(self):
+        """
+        Private slot to check the word below the spelling context menu.
+        """
+        from .SpellCheckingDialog import SpellCheckingDialog
+        line, index = self.lineIndexFromPosition(self.spellingMenuPos)
+        wordStart, wordEnd = self.getWordBoundaries(line, index)
+        wordStartPos = self.positionFromLineIndex(line, wordStart)
+        wordEndPos = self.positionFromLineIndex(line, wordEnd)
+        dlg = SpellCheckingDialog(self.spell, wordStartPos, wordEndPos, self)
+        dlg.exec()
+    
+    def __showContextMenuSpelling(self):
+        """
+        Private slot to set up the spelling menu before it is shown.
+        """
+        self.spellingMenu.clear()
+        self.spellingSuggActs = []
+        line, index = self.lineIndexFromPosition(self.spellingMenuPos)
+        word = self.getWord(line, index)
+        suggestions = self.spell.getSuggestions(word)
+        for suggestion in suggestions[:5]:
+            self.spellingSuggActs.append(
+                self.spellingMenu.addAction(suggestion))
+        if suggestions:
+            self.spellingMenu.addSeparator()
+        self.spellingMenu.addAction(
+            UI.PixmapCache.getIcon("spellchecking"),
+            self.tr("Check spelling..."), self.__checkSpellingWord)
+        self.spellingMenu.addAction(
+            self.tr("Add to dictionary"), self.__addToSpellingDictionary)
+        self.spellingMenu.addAction(
+            self.tr("Ignore All"), self.__ignoreSpellingAlways)
+        
+        self.showMenu.emit("Spelling", self.spellingMenu, self)
+    
+    def __contextMenuSpellingTriggered(self, action):
+        """
+        Private slot to handle the selection of a suggestion of the spelling
+        context menu.
+        
+        @param action reference to the action that was selected (QAction)
+        """
+        if action in self.spellingSuggActs:
+            replacement = action.text()
+            line, index = self.lineIndexFromPosition(self.spellingMenuPos)
+            wordStart, wordEnd = self.getWordBoundaries(line, index)
+            self.setSelection(line, wordStart, line, wordEnd)
+            self.beginUndoAction()
+            self.removeSelectedText()
+            self.insert(replacement)
+            self.endUndoAction()
+    
+    def __addToSpellingDictionary(self):
+        """
+        Private slot to add the word below the spelling context menu to the
+        dictionary.
+        """
+        line, index = self.lineIndexFromPosition(self.spellingMenuPos)
+        word = self.getWord(line, index)
+        self.spell.add(word)
+        
+        wordStart, wordEnd = self.getWordBoundaries(line, index)
+        self.clearIndicator(self.spellingIndicator, line, wordStart,
+                            line, wordEnd)
+        if Preferences.getEditor("AutoSpellCheckingEnabled"):
+            self.spell.checkDocumentIncrementally()
+    
+    def __removeFromSpellingDictionary(self):
+        """
+        Private slot to remove the word below the context menu to the
+        dictionary.
+        """
+        line, index = self.lineIndexFromPosition(self.spellingMenuPos)
+        word = self.getWord(line, index)
+        self.spell.remove(word)
+        
+        if Preferences.getEditor("AutoSpellCheckingEnabled"):
+            self.spell.checkDocumentIncrementally()
+    
+    def __ignoreSpellingAlways(self):
+        """
+        Private to always ignore the word below the spelling context menu.
+        """
+        line, index = self.lineIndexFromPosition(self.spellingMenuPos)
+        word = self.getWord(line, index)
+        self.spell.ignoreAlways(word)
+        if Preferences.getEditor("AutoSpellCheckingEnabled"):
+            self.spell.checkDocumentIncrementally()
+    
+    #######################################################################
+    ## Cooperation related methods
+    #######################################################################
+    
+    def getSharingStatus(self):
+        """
+        Public method to get some share status info.
+        
+        @return tuple indicating, if the editor is sharable, the sharing
+            status, if it is inside a locally initiated shared edit session
+            and if it is inside a remotely initiated shared edit session
+            (boolean, boolean, boolean, boolean)
+        """
+        return (
+            (bool(self.fileName) and
+             self.project.isOpen() and
+             self.project.isProjectFile(self.fileName)),
+            self.__isShared,
+            self.__inSharedEdit,
+            self.__inRemoteSharedEdit
+        )
+    
+    def shareConnected(self, connected):
+        """
+        Public slot to handle a change of the connected state.
+        
+        @param connected flag indicating the connected state (boolean)
+        """
+        if not connected:
+            self.__inRemoteSharedEdit = False
+            self.setReadOnly(False)
+            self.__updateReadOnly()
+            self.cancelSharedEdit(send=False)
+            self.__isSyncing = False
+            self.__receivedWhileSyncing = []
+    
+    def shareEditor(self, share):
+        """
+        Public slot to set the shared status of the editor.
+        
+        @param share flag indicating the share status (boolean)
+        """
+        self.__isShared = share
+        if not share:
+            self.shareConnected(False)
+    
+    def startSharedEdit(self):
+        """
+        Public slot to start a shared edit session for the editor.
+        """
+        self.__inSharedEdit = True
+        self.__savedText = self.text()
+        hashStr = str(
+            QCryptographicHash.hash(
+                Utilities.encode(self.__savedText, self.encoding)[0],
+                QCryptographicHash.Algorithm.Sha1).toHex(),
+            encoding="utf-8")
+        self.__send(Editor.StartEditToken, hashStr)
+    
+    def sendSharedEdit(self):
+        """
+        Public slot to end a shared edit session for the editor and
+        send the changes.
+        """
+        commands = self.__calculateChanges(self.__savedText, self.text())
+        self.__send(Editor.EndEditToken, commands)
+        self.__inSharedEdit = False
+        self.__savedText = ""
+    
+    def cancelSharedEdit(self, send=True):
+        """
+        Public slot to cancel a shared edit session for the editor.
+        
+        @param send flag indicating to send the CancelEdit command (boolean)
+        """
+        self.__inSharedEdit = False
+        self.__savedText = ""
+        if send:
+            self.__send(Editor.CancelEditToken)
+    
+    def __send(self, token, args=None):
+        """
+        Private method to send an editor command to remote editors.
+        
+        @param token command token (string)
+        @param args arguments for the command (string)
+        """
+        if self.vm.isConnected():
+            msg = ""
+            if token in (Editor.StartEditToken,
+                         Editor.EndEditToken,
+                         Editor.RequestSyncToken,
+                         Editor.SyncToken):
+                msg = "{0}{1}{2}".format(
+                    token,
+                    Editor.Separator,
+                    args
+                )
+            elif token == Editor.CancelEditToken:
+                msg = "{0}{1}c".format(
+                    token,
+                    Editor.Separator
+                )
+            
+            self.vm.send(self.fileName, msg)
+    
+    def receive(self, command):
+        """
+        Public slot to handle received editor commands.
+        
+        @param command command string (string)
+        """
+        if self.__isShared:
+            if (
+                self.__isSyncing and
+                not command.startswith(Editor.SyncToken + Editor.Separator)
+            ):
+                self.__receivedWhileSyncing.append(command)
+            else:
+                self.__dispatchCommand(command)
+    
+    def __dispatchCommand(self, command):
+        """
+        Private method to dispatch received commands.
+        
+        @param command command to be processed (string)
+        """
+        token, argsString = command.split(Editor.Separator, 1)
+        if token == Editor.StartEditToken:
+            self.__processStartEditCommand(argsString)
+        elif token == Editor.CancelEditToken:
+            self.shareConnected(False)
+        elif token == Editor.EndEditToken:
+            self.__processEndEditCommand(argsString)
+        elif token == Editor.RequestSyncToken:
+            self.__processRequestSyncCommand(argsString)
+        elif token == Editor.SyncToken:
+            self.__processSyncCommand(argsString)
+    
+    def __processStartEditCommand(self, argsString):
+        """
+        Private slot to process a remote StartEdit command.
+        
+        @param argsString string containing the command parameters (string)
+        """
+        if not self.__inSharedEdit and not self.__inRemoteSharedEdit:
+            self.__inRemoteSharedEdit = True
+            self.setReadOnly(True)
+            self.__updateReadOnly()
+            hashStr = str(
+                QCryptographicHash.hash(
+                    Utilities.encode(self.text(), self.encoding)[0],
+                    QCryptographicHash.Algorithm.Sha1).toHex(),
+                encoding="utf-8")
+            if hashStr != argsString:
+                # text is different to the remote site, request to sync it
+                self.__isSyncing = True
+                self.__send(Editor.RequestSyncToken, argsString)
+    
+    def __calculateChanges(self, old, new):
+        """
+        Private method to determine change commands to convert old text into
+        new text.
+        
+        @param old old text (string)
+        @param new new text (string)
+        @return commands to change old into new (string)
+        """
+        oldL = old.splitlines()
+        newL = new.splitlines()
+        matcher = difflib.SequenceMatcher(None, oldL, newL)
+        
+        formatStr = "@@{0} {1} {2} {3}"
+        commands = []
+        for token, i1, i2, j1, j2 in matcher.get_opcodes():
+            if token == "insert":               # secok
+                commands.append(formatStr.format("i", j1, j2 - j1, -1))
+                commands.extend(newL[j1:j2])
+            elif token == "delete":             # secok
+                commands.append(formatStr.format("d", j1, i2 - i1, -1))
+            elif token == "replace":            # secok
+                commands.append(formatStr.format("r", j1, i2 - i1, j2 - j1))
+                commands.extend(newL[j1:j2])
+        
+        return "\n".join(commands) + "\n"
+    
+    def __processEndEditCommand(self, argsString):
+        """
+        Private slot to process a remote EndEdit command.
+        
+        @param argsString string containing the command parameters (string)
+        """
+        commands = argsString.splitlines()
+        sep = self.getLineSeparator()
+        cur = self.getCursorPosition()
+        
+        self.setReadOnly(False)
+        self.beginUndoAction()
+        while commands:
+            commandLine = commands.pop(0)
+            if not commandLine.startswith("@@"):
+                continue
+            
+            args = commandLine.split()
+            command = args.pop(0)
+            pos, l1, l2 = [int(arg) for arg in args]
+            if command == "@@i":
+                txt = sep.join(commands[0:l1]) + sep
+                self.insertAt(txt, pos, 0)
+                del commands[0:l1]
+            elif command == "@@d":
+                self.setSelection(pos, 0, pos + l1, 0)
+                self.removeSelectedText()
+            elif command == "@@r":
+                self.setSelection(pos, 0, pos + l1, 0)
+                self.removeSelectedText()
+                txt = sep.join(commands[0:l2]) + sep
+                self.insertAt(txt, pos, 0)
+                del commands[0:l2]
+        self.endUndoAction()
+        self.__updateReadOnly()
+        self.__inRemoteSharedEdit = False
+        
+        self.setCursorPosition(*cur)
+    
+    def __processRequestSyncCommand(self, argsString):
+        """
+        Private slot to process a remote RequestSync command.
+        
+        @param argsString string containing the command parameters (string)
+        """
+        if self.__inSharedEdit:
+            hashStr = str(
+                QCryptographicHash.hash(
+                    Utilities.encode(self.__savedText, self.encoding)[0],
+                    QCryptographicHash.Algorithm.Sha1).toHex(),
+                encoding="utf-8")
+            
+            if hashStr == argsString:
+                self.__send(Editor.SyncToken, self.__savedText)
+    
+    def __processSyncCommand(self, argsString):
+        """
+        Private slot to process a remote Sync command.
+        
+        @param argsString string containing the command parameters (string)
+        """
+        if self.__isSyncing:
+            cur = self.getCursorPosition()
+            
+            self.setReadOnly(False)
+            self.beginUndoAction()
+            self.selectAll()
+            self.removeSelectedText()
+            self.insertAt(argsString, 0, 0)
+            self.endUndoAction()
+            self.setReadOnly(True)
+            
+            self.setCursorPosition(*cur)
+            
+            while self.__receivedWhileSyncing:
+                command = self.__receivedWhileSyncing.pop(0)
+                self.__dispatchCommand(command)
+            
+            self.__isSyncing = False
+    
+    #######################################################################
+    ## Special search related methods
+    #######################################################################
+    
+    def searchCurrentWordForward(self):
+        """
+        Public slot to search the current word forward.
+        """
+        self.__searchCurrentWord(forward=True)
+    
+    def searchCurrentWordBackward(self):
+        """
+        Public slot to search the current word backward.
+        """
+        self.__searchCurrentWord(forward=False)
+    
+    def __searchCurrentWord(self, forward=True):
+        """
+        Private slot to search the next occurrence of the current word.
+        
+        @param forward flag indicating the search direction (boolean)
+        """
+        self.hideFindIndicator()
+        line, index = self.getCursorPosition()
+        word = self.getCurrentWord()
+        wordStart, wordEnd = self.getCurrentWordBoundaries()
+        wordStartPos = self.positionFromLineIndex(line, wordStart)
+        wordEndPos = self.positionFromLineIndex(line, wordEnd)
+        
+        regExp = re.compile(r"\b{0}\b".format(word))
+        startPos = wordEndPos if forward else wordStartPos
+        
+        matches = [m for m in regExp.finditer(self.text())]
+        if matches:
+            if forward:
+                matchesAfter = [m for m in matches if m.start() >= startPos]
+                if matchesAfter:
+                    match = matchesAfter[0]
+                else:
+                    # wrap around
+                    match = matches[0]
+            else:
+                matchesBefore = [m for m in matches if m.start() < startPos]
+                if matchesBefore:
+                    match = matchesBefore[-1]
+                else:
+                    # wrap around
+                    match = matches[-1]
+            line, index = self.lineIndexFromPosition(match.start())
+            self.setSelection(line, index + len(match.group(0)), line, index)
+            self.showFindIndicator(line, index,
+                                   line, index + len(match.group(0)))
+    
+    #######################################################################
+    ## Sort related methods
+    #######################################################################
+    
+    def sortLines(self):
+        """
+        Public slot to sort the lines spanned by a rectangular selection.
+        """
+        if not self.selectionIsRectangle():
+            return
+        
+        from .SortOptionsDialog import SortOptionsDialog
+        dlg = SortOptionsDialog()
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            ascending, alnum, caseSensitive = dlg.getData()
+            origStartLine, origStartIndex, origEndLine, origEndIndex = (
+                self.getRectangularSelection()
+            )
+            # convert to upper-left to lower-right
+            startLine = min(origStartLine, origEndLine)
+            startIndex = min(origStartIndex, origEndIndex)
+            endLine = max(origStartLine, origEndLine)
+            endIndex = max(origStartIndex, origEndIndex)
+            
+            # step 1: extract the text of the rectangular selection and
+            #         the lines
+            selText = {}
+            txtLines = {}
+            for line in range(startLine, endLine + 1):
+                txtLines[line] = self.text(line)
+                txt = txtLines[line][startIndex:endIndex].strip()
+                if not alnum:
+                    try:
+                        txt = float(txt)
+                    except ValueError:
+                        EricMessageBox.critical(
+                            self,
+                            self.tr("Sort Lines"),
+                            self.tr(
+                                """The selection contains illegal data for a"""
+                                """ numerical sort."""))
+                        return
+                
+                if txt in selText:
+                    selText[txt].append(line)
+                else:
+                    selText[txt] = [line]
+            
+            # step 2: calculate the sort parameters
+            reverse = not ascending
+            if alnum and not caseSensitive:
+                keyFun = str.lower
+            else:
+                keyFun = None
+            
+            # step 3: sort the lines
+            eol = self.getLineSeparator()
+            lastWithEol = True
+            newLines = []
+            for txt in sorted(selText.keys(), key=keyFun, reverse=reverse):
+                for line in selText[txt]:
+                    txt = txtLines[line]
+                    if not txt.endswith(eol):
+                        lastWithEol = False
+                        txt += eol
+                    newLines.append(txt)
+            if not lastWithEol:
+                newLines[-1] = newLines[-1][:-len(eol)]
+            
+            # step 4: replace the lines by the sorted ones
+            self.setSelection(startLine, 0, endLine + 1, 0)
+            self.beginUndoAction()
+            self.replaceSelectedText("".join(newLines))
+            self.endUndoAction()
+            
+            # step 5: reset the rectangular selection
+            self.setRectangularSelection(origStartLine, origStartIndex,
+                                         origEndLine, origEndIndex)
+            self.selectionChanged.emit()
+    
+    #######################################################################
+    ## Mouse click handler related methods
+    #######################################################################
+    
+    def mouseReleaseEvent(self, evt):
+        """
+        Protected method calling a registered mouse click handler function.
+        
+        @param evt event object
+        @type QMouseEvent
+        """
+        modifiers = evt.modifiers()
+        button = evt.button()
+        key = (modifiers, button)
+        
+        self.vm.eventFilter(self, evt)
+        super().mouseReleaseEvent(evt)
+        
+        if (
+            button != Qt.MouseButton.NoButton and
+            Preferences.getEditor("MouseClickHandlersEnabled") and
+            key in self.__mouseClickHandlers
+        ):
+            evt.accept()
+            self.__mouseClickHandlers[key][1](self)
+        else:
+            super().mouseReleaseEvent(evt)
+    
+    def setMouseClickHandler(self, name, modifiers, button, function):
+        """
+        Public method to set a mouse click handler.
+        
+        @param name name of the plug-in (or 'internal') setting this handler
+        @type str
+        @param modifiers keyboard modifiers of the handler
+        @type Qt.KeyboardModifiers or int
+        @param button mouse button of the handler
+        @type Qt.MouseButton or int
+        @param function handler function
+        @type func
+        @return flag indicating success
+        @rtype bool
+        """
+        if button and button != Qt.MouseButton.NoButton:
+            key = (modifiers, button)
+            if key in self.__mouseClickHandlers:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("Register Mouse Click Handler"),
+                    self.tr("""A mouse click handler for "{0}" was already"""
+                            """ registered by "{1}". Aborting request by"""
+                            """ "{2}"...""").format(
+                        MouseUtilities.MouseButtonModifier2String(
+                            modifiers, button),
+                        self.__mouseClickHandlers[key][0],
+                        name))
+                return False
+            
+            self.__mouseClickHandlers[key] = (name, function)
+            return True
+        
+        return False
+    
+    def getMouseClickHandler(self, modifiers, button):
+        """
+        Public method to get a registered mouse click handler.
+        
+        @param modifiers keyboard modifiers of the handler
+        @type Qt.KeyboardModifiers
+        @param button mouse button of the handler
+        @type Qt.MouseButton
+        @return plug-in name and registered function
+        @rtype tuple of str and func
+        """
+        key = (modifiers, button)
+        if key in self.__mouseClickHandlers:
+            return self.__mouseClickHandlers[key]
+        else:
+            return ("", None)
+    
+    def getMouseClickHandlers(self, name):
+        """
+        Public method to get all registered mouse click handlers of
+        a plug-in.
+        
+        @param name name of the plug-in
+        @type str
+        @return registered mouse click handlers as list of modifiers,
+            mouse button and function
+        @rtype list of tuple of (Qt.KeyboardModifiers, Qt.MouseButton, func)
+        """
+        lst = []
+        for key, value in self.__mouseClickHandlers.items():
+            if value[0] == name:
+                lst.append((key[0], key[1], value[1]))
+        return lst
+    
+    def removeMouseClickHandler(self, modifiers, button):
+        """
+        Public method to un-registered a mouse click handler.
+        
+        @param modifiers keyboard modifiers of the handler
+        @type Qt.KeyboardModifiers
+        @param button mouse button of the handler
+        @type Qt.MouseButton
+        """
+        key = (modifiers, button)
+        if key in self.__mouseClickHandlers:
+            del self.__mouseClickHandlers[key]
+    
+    def removeMouseClickHandlers(self, name):
+        """
+        Public method to un-registered all mouse click handlers of
+        a plug-in.
+        
+        @param name name of the plug-in
+        @type str
+        """
+        keys = []
+        for key in self.__mouseClickHandlers:
+            if self.__mouseClickHandlers[key][0] == name:
+                keys.append(key)
+        for key in keys:
+            del self.__mouseClickHandlers[key]
+    
+    def gotoReferenceHandler(self, referencesList):
+        """
+        Public method to handle a list of references to perform a goto.
+        
+        @param referencesList list of references for a 'goto' action
+        @type ReferenceItem
+        """
+        references = []
+        referencePositions = []
+        
+        for reference in referencesList:
+            if (
+                reference.modulePath != self.getFileName() or
+                self.getCursorPosition()[0] + 1 != reference.line
+            ):
+                if reference.modulePath == self.getFileName():
+                    references.append(
+                        self.tr("{0:4d}    {1}", "line number, source code")
+                        .format(reference.line, reference.codeLine.strip())
+                    )
+                else:
+                    references.append(
+                        self.tr("{0:4d}    {1}\n    =>  {2}",
+                                "line number, source code, file name")
+                        .format(
+                            reference.line, reference.codeLine.strip(),
+                            self.project.getRelativePath(reference.modulePath)
+                        )
+                    )
+                referencePositions.append(
+                    (reference.modulePath, reference.line, reference.column))
+        
+        if references:
+            if self.isCallTipActive():
+                self.cancelCallTips()
+            self.__referencesList = references
+            self.__referencesPositionsList = referencePositions
+            self.showUserList(ReferencesListID, references)
+    
+    #######################################################################
+    ## Methods implementing a Shell interface
+    #######################################################################
+    
+    def __executeSelection(self):
+        """
+        Private slot to execute the selected text in the shell window.
+        """
+        txt = self.selectedText()
+        ericApp().getObject("Shell").executeLines(txt)
+    
+    #######################################################################
+    ## Methods implementing the interface to EditorConfig
+    #######################################################################
+    
+    def __loadEditorConfig(self, fileName=""):
+        """
+        Private method to load the EditorConfig properties.
+        
+        @param fileName name of the file
+        @type str
+        """
+        if not fileName:
+            fileName = self.fileName
+        
+        self.__editorConfig = self.__loadEditorConfigObject(fileName)
+        
+        if fileName:
+            self.__setTabAndIndent()
+    
+    def __loadEditorConfigObject(self, fileName):
+        """
+        Private method to load the EditorConfig properties for the given
+        file name.
+        
+        @param fileName name of the file
+        @type str
+        @return EditorConfig dictionary
+        @rtype dict
+        """
+        editorConfig = {}
+        
+        if fileName:
+            try:
+                editorConfig = editorconfig.get_properties(fileName)
+            except editorconfig.EditorConfigError:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("EditorConfig Properties"),
+                    self.tr("""<p>The EditorConfig properties for file"""
+                            """ <b>{0}</b> could not be loaded.</p>""")
+                    .format(fileName))
+        
+        return editorConfig
+    
+    def __getEditorConfig(self, option, nodefault=False, config=None):
+        """
+        Private method to get the requested option via EditorConfig.
+        
+        If there is no EditorConfig defined, the equivalent built-in option
+        will be used (Preferences.getEditor() ). The option must be given as
+        the Preferences option key. The mapping to the EditorConfig option name
+        will be done within this method.
+        
+        @param option Preferences option key
+        @type str
+        @param nodefault flag indicating to not get the default value from
+            Preferences but return None instead
+        @type bool
+        @param config reference to an EditorConfig object or None
+        @type dict
+        @return value of requested setting or None if nothing was found and
+            nodefault parameter was True
+        @rtype any
+        """
+        if config is None:
+            config = self.__editorConfig
+        
+        if not config:
+            if nodefault:
+                return None
+            else:
+                value = self.__getOverrideValue(option)
+                if value is None:
+                    # no override
+                    value = Preferences.getEditor(option)
+                return value
+        
+        try:
+            if option == "EOLMode":
+                value = config["end_of_line"]
+                if value == "lf":
+                    value = QsciScintilla.EolMode.EolUnix
+                elif value == "crlf":
+                    value = QsciScintilla.EolMode.EolWindows
+                elif value == "cr":
+                    value = QsciScintilla.EolMode.EolMac
+                else:
+                    value = None
+            elif option == "DefaultEncoding":
+                value = config["charset"]
+            elif option == "InsertFinalNewline":
+                value = Utilities.toBool(config["insert_final_newline"])
+            elif option == "StripTrailingWhitespace":
+                value = Utilities.toBool(config["trim_trailing_whitespace"])
+            elif option == "TabWidth":
+                value = int(config["tab_width"])
+            elif option == "IndentWidth":
+                value = config["indent_size"]
+                if value == "tab":
+                    value = self.__getEditorConfig("TabWidth", config=config)
+                else:
+                    value = int(value)
+            elif option == "TabForIndentation":
+                value = config["indent_style"] == "tab"
+        except KeyError:
+            value = None
+        
+        if value is None and not nodefault:
+            # use Preferences in case of error
+            value = self.__getOverrideValue(option)
+            if value is None:
+                # no override
+                value = Preferences.getEditor(option)
+        
+        return value
+    
+    def getEditorConfig(self, option):
+        """
+        Public method to get the requested option via EditorConfig.
+        
+        @param option Preferences option key
+        @type str
+        @return value of requested setting
+        @rtype any
+        """
+        return self.__getEditorConfig(option)
+    
+    def __getOverrideValue(self, option):
+        """
+        Private method to get an override value for the current file type.
+        
+        @param option Preferences option key
+        @type str
+        @return override value; None in case nothing is defined
+        @rtype any
+        """
+        if option in ("TabWidth", "IndentWidth"):
+            overrides = Preferences.getEditor("TabIndentOverride")
+            language = self.filetype or self.apiLanguage
+            if language in overrides:
+                if option == "TabWidth":
+                    return overrides[language][0]
+                elif option == "IndentWidth":
+                    return overrides[language][1]
+        
+        return None
+    
+    #######################################################################
+    ## Methods implementing the docstring generator interface
+    #######################################################################
+    
+    def getDocstringGenerator(self):
+        """
+        Public method to get a reference to the docstring generator.
+        
+        @return reference to the docstring generator
+        @rtype BaseDocstringGenerator
+        """
+        if self.__docstringGenerator is None:
+            from . import DocstringGenerator
+            self.__docstringGenerator = (
+                DocstringGenerator.getDocstringGenerator(self)
+            )
+        
+        return self.__docstringGenerator
+    
+    def insertDocstring(self):
+        """
+        Public method to generate and insert a docstring for the function under
+        the cursor.
+        
+        Note: This method is called via a keyboard shortcut or through the
+        global 'Edit' menu.
+        """
+        generator = self.getDocstringGenerator()
+        generator.insertDocstringFromShortcut(self.getCursorPosition())
+    
+    @pyqtSlot()
+    def __insertDocstring(self):
+        """
+        Private slot to generate and insert a docstring for the function under
+        the cursor.
+        """
+        generator = self.getDocstringGenerator()
+        generator.insertDocstring(self.getCursorPosition(), fromStart=True)
+    
+    def __delayedDocstringMenuPopup(self, cursorPosition):
+        """
+        Private method to test, if the user might want to insert a docstring.
+        
+        @param cursorPosition current cursor position (line and column)
+        @type tuple of (int, int)
+        """
+        if (
+            Preferences.getEditor("DocstringAutoGenerate") and
+            self.getDocstringGenerator().isDocstringIntro(cursorPosition)
+        ):
+            lineText2Cursor = self.text(cursorPosition[0])[:cursorPosition[1]]
+            
+            QTimer.singleShot(
+                300,
+                lambda: self.__popupDocstringMenu(lineText2Cursor,
+                                                  cursorPosition)
+            )
+    
+    def __popupDocstringMenu(self, lastLineText, lastCursorPosition):
+        """
+        Private slot to pop up a menu asking the user, if a docstring should be
+        inserted.
+        
+        @param lastLineText line contents when the delay timer was started
+        @type str
+        @param lastCursorPosition position of the cursor when the delay timer
+            was started (line and index)
+        @type tuple of (int, int)
+        """
+        cursorPosition = self.getCursorPosition()
+        if lastCursorPosition != cursorPosition:
+            return
+        
+        if self.text(cursorPosition[0])[:cursorPosition[1]] != lastLineText:
+            return
+        
+        generator = self.getDocstringGenerator()
+        if generator.hasFunctionDefinition(cursorPosition):
+            from .DocstringGenerator.BaseDocstringGenerator import (
+                DocstringMenuForEnterOnly
+            )
+            docstringMenu = DocstringMenuForEnterOnly(self)
+            act = docstringMenu.addAction(
+                UI.PixmapCache.getIcon("fileText"),
+                self.tr("Generate Docstring"),
+                lambda: generator.insertDocstring(cursorPosition,
+                                                  fromStart=False)
+            )
+            docstringMenu.setActiveAction(act)
+            docstringMenu.popup(
+                self.mapToGlobal(self.getGlobalCursorPosition()))
+    
+    #######################################################################
+    ## Methods implementing the mouse hover help interface
+    #######################################################################
+    
+    @pyqtSlot(int, int, int)
+    def __showMouseHoverHelp(self, pos, x, y):
+        """
+        Private slot showing code information about the symbol under the
+        cursor.
+        
+        @param pos mouse position into the document
+        @type int
+        @param x x-value of mouse screen position
+        @type int
+        @param y y-value of mouse screen position
+        @type int
+        """
+        if not self.isCallTipActive() and not self.isListActive():
+            if self.__mouseHoverHelp is not None and pos > 0 and y > 0:
+                line, index = self.lineIndexFromPosition(pos)
+                if index > 0:
+                    self.__mouseHoverHelp(self, line, index)
+                else:
+                    self.__cancelMouseHoverHelp()
+            else:
+                self.__cancelMouseHoverHelp()
+    
+    def __cancelMouseHoverHelp(self):
+        """
+        Private slot cancelling the display of mouse hover help.
+        """
+        if self.__showingMouseHoverHelp:
+            self.cancelCallTips()
+            self.__showingMouseHoverHelp = False
+    
+    def registerMouseHoverHelpFunction(self, func):
+        """
+        Public method to register a mouse hover help function.
+        
+        Note: Only one plugin should provide this function. Otherwise
+        the last one wins.
+        
+        @param func function accepting a reference to the calling editor and
+            the line and column position (zero based each)
+        @type func
+        """
+        self.__mouseHoverHelp = func
+    
+    def unregisterMouseHoverHelpFunction(self, func):
+        """
+        Public method to unregister a mouse hover help function.
+        
+        @param func function accepting a reference to the calling editor and
+            the line and column position (zero based each)
+        @type func
+        """
+        if self.__mouseHoverHelp is func:
+            self.__mouseHoverHelp = None
+    
+    def showMouseHoverHelpData(self, line, index, data):
+        """
+        Public method to show the mouse hover help data.
+        
+        @param line line of mouse cursor position
+        @type int
+        @param index column of mouse cursor position
+        @type int
+        @param data information text to be shown
+        @type str
+        """
+        if data and self.hasFocus() and not self.isListActive():
+            pos = self.positionFromLineIndex(line, index)
+            self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW,
+                               pos, self._encodeString(data))
+            self.__showingMouseHoverHelp = True
+        else:
+            self.__cancelMouseHoverHelp()

eric ide

mercurial