--- a/QScintilla/Editor.py Sat Jan 30 16:07:16 2010 +0000 +++ b/QScintilla/Editor.py Sat Jan 30 18:37:18 2010 +0000 @@ -1,5274 +1,5454 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2002 - 2010 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing the editor component of the eric5 IDE. -""" - -import os -import re -import types - -from PyQt4.Qsci import QsciScintilla, QsciMacro -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from E5Gui.E5Application import e5App - -from . import Exporters -from . import Lexers -from . import TypingCompleters -from .QsciScintillaCompat import QsciScintillaCompat, QSCINTILLA_VERSION -from .SpellChecker import SpellChecker -from .SpellCheckingDialog import SpellCheckingDialog - -from Debugger.EditBreakpointDialog import EditBreakpointDialog - -from DebugClients.Python3.coverage import coverage - -from DataViews.CodeMetricsDialog import CodeMetricsDialog -from DataViews.PyCoverageDialog import PyCoverageDialog -from DataViews.PyProfileDialog import PyProfileDialog - -from .Printer import Printer - -import Preferences -import Utilities - -import UI.PixmapCache - -EditorAutoCompletionListID = 1 -TemplateCompletionListID = 2 - -class Editor(QsciScintillaCompat): - """ - Class implementing the editor component of the eric5 IDE. - - @signal modificationStatusChanged(boolean, editor) emitted when the - modification status has changed - @signal undoAvailable(boolean) emitted to signal the undo availability - @signal redoAvailable(boolean) emitted to signal the redo availability - @signal cursorChanged(string, int, int) emitted when the cursor position - was changed - @signal editorAboutToBeSaved(string) emitted before the editor is saved - @signal editorSaved(string) emitted after the editor has been saved - @signal editorRenamed(string) emitted after the editor got a new name - (i.e. after a 'Save As') - @signal captionChanged(string, editor) emitted when the caption is - updated. Typically due to a readOnly attribute change. - @signal breakpointToggled(editor) emitted when a breakpoint is toggled - @signal bookmarkToggled(editor) emitted when a bookmark is toggled - @signal syntaxerrorToggled(editor) emitted when a syntax error was discovered - @signal autoCompletionAPIsAvailable(avail) emitted after the autocompletion - function has been configured - @signal coverageMarkersShown(boolean) emitted after the coverage markers have been - shown or cleared - @signal taskMarkersUpdated(editor) emitted when the task markers were updated - @signal showMenu(string, QMenu, editor) 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(language) emitted when the editors language was set. The - language is passed as a parameter. - @signal eolChanged(eol) emitted when the editors eol type was set. The eol string - is passed as a parameter. - @signal encodingChanged(encoding) emitted when the editors encoding was set. The - encoding name is passed as a parameter. - """ - ClassID = 1 - ClassProtectedID = 2 - ClassPrivateID = 3 - MethodID = 4 - MethodProtectedID = 5 - MethodPrivateID = 6 - AttributeID = 7 - AttributeProtectedID = 8 - AttributePrivateID = 9 - EnumID = 10 - - FromDocumentID = 99 - - TemplateImageID = 100 - - def __init__(self, dbs, fn = None, vm = None, - filetype = "", editor = None, tv = None): - """ - Constructor - - @param dbs reference to the debug server object - @param fn name of the file to be opened (string). If it is None, - a new (empty) editor is opened - @param vm reference to the view manager object (ViewManager.ViewManager) - @param filetype type of the source file (string) - @param editor reference to an Editor object, if this is a cloned view - @param tv reference to the task viewer object - """ - QsciScintillaCompat.__init__(self) - self.setAttribute(Qt.WA_DeleteOnClose) - self.setAttribute(Qt.WA_KeyCompression) - self.setUtf8(True) - - self.pyExtensions = dbs.getExtensions('Python') - self.py3Extensions = dbs.getExtensions('Python3') - self.rbExtensions = dbs.getExtensions('Ruby') - - self.dbs = dbs - self.taskViewer = tv - self.fileName = fn - self.vm = vm - self.filetype = filetype - self.noName = "" - - # 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: error message - self.notcoveredMarkers = [] # just a list of marker handles - - self.condHistory = [] - self.lexer_ = None - self.__lexerReset = False - self.completer = None - self.encoding = Preferences.getEditor("DefaultEncoding") - self.apiLanguage = '' - 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.__hasTaskMarkers = False # no task markers present - - self.macros = {} # list of defined macros - self.curMacro = None - self.recording = False - - self.acAPI = False - - # list of clones - self.__clones = [] - - # clear QScintilla defined keyboard commands - # we do our own handling through the view manager - self.clearAlternateKeys() - self.clearKeys() - - # initialise the mark occurrences timer - self.__markOccurrencesTimer = QTimer(self) - self.__markOccurrencesTimer.setSingleShot(True) - self.__markOccurrencesTimer.setInterval( - Preferences.getEditor("MarkOccurrencesTimeout")) - self.connect(self.__markOccurrencesTimer, SIGNAL("timeout()"), - self.__markOccurrences) - self.__markedText = "" - - # initialise some spellchecking stuff - self.spell = None - self.lastLine = 0 - self.lastIndex = 0 - - self.connect(self, SIGNAL('modificationChanged(bool)'), - self.__modificationChanged) - self.connect(self, SIGNAL('cursorPositionChanged(int,int)'), - self.__cursorPositionChanged) - self.connect(self, SIGNAL('modificationAttempted()'), - self.__modificationReadOnly) - self.connect(self, SIGNAL('userListActivated(int, const QString)'), - self.__completionListSelected) - - # margins layout - if QSCINTILLA_VERSION() >= 0x020301: - self.__unifiedMargins = Preferences.getEditor("UnifiedMargins") - else: - self.__unifiedMargins = True - - # define the margins markers - self.breakpoint = \ - self.markerDefine(UI.PixmapCache.getPixmap("break.png")) - self.cbreakpoint = \ - self.markerDefine(UI.PixmapCache.getPixmap("cBreak.png")) - self.tbreakpoint = \ - self.markerDefine(UI.PixmapCache.getPixmap("tBreak.png")) - self.tcbreakpoint = \ - self.markerDefine(UI.PixmapCache.getPixmap("tCBreak.png")) - self.dbreakpoint = \ - self.markerDefine(UI.PixmapCache.getPixmap("breakDisabled.png")) - self.bookmark = \ - self.markerDefine(UI.PixmapCache.getPixmap("bookmark.png")) - self.syntaxerror = \ - self.markerDefine(UI.PixmapCache.getPixmap("syntaxError.png")) - self.notcovered = \ - self.markerDefine(UI.PixmapCache.getPixmap("notcovered.png")) - self.taskmarker = \ - self.markerDefine(UI.PixmapCache.getPixmap("task.png")) - self.warning = \ - self.markerDefine(UI.PixmapCache.getPixmap("warning.png")) - - # define the line markers - self.currentline = self.markerDefine(QsciScintilla.Background) - self.errorline = self.markerDefine(QsciScintilla.Background) - self.__setLineMarkerColours() - - self.breakpointMask = (1 << self.breakpoint) | \ - (1 << self.cbreakpoint) | \ - (1 << self.tbreakpoint) | \ - (1 << self.tcbreakpoint) | \ - (1 << self.dbreakpoint) - - # configure the margins - self.__setMarginsDisplay() - - self.connect(self, SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'), - self.__marginClicked) - - # set the eol mode - self.__setEolMode() - - self.isResourcesFile = False - if editor is None: - if self.fileName is not None: - if (QFileInfo(self.fileName).size() // 1024) > \ - Preferences.getEditor("WarnFilesize"): - res = QMessageBox.warning(None, - self.trUtf8("Open File"), - self.trUtf8("""<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, - QFileInfo(self.fileName).size() // 1024), - QMessageBox.StandardButtons(\ - QMessageBox.No | \ - QMessageBox.Yes), - QMessageBox.No) - if res == QMessageBox.No or res == QMessageBox.Cancel: - raise IOError() - self.readFile(self.fileName, True) - bindName = self.__bindName(self.text(0)) - self.__bindLexer(bindName) - self.__bindCompleter(bindName) - self.__autoSyntaxCheck() - self.isResourcesFile = self.fileName.endswith(".qrc") - - 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.isResourcesFile = editor.isResourcesFile - self.lastModified = editor.lastModified - - self.addClone(editor) - editor.addClone(self) - - self.gotoLine(0) - - # set the text display - self.__setTextDisplay() - - # set the autocompletion and calltips function - self.__acHookFunction = None - self.__setAutoCompletion() - self.__ctHookFunction = None - self.__setCallTips() - - sh = self.sizeHint() - if sh.height() < 300: - sh.setHeight(300) - self.resize(sh) - - # Make sure tabbing through a QWorkspace works. - self.setFocusPolicy(Qt.StrongFocus) - - self.__updateReadOnly(True) - - self.setWhatsThis(self.trUtf8( - """<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 - - self.__initContextMenu() - self.__initContextMenuMargins() - - self.__checkEol() - if editor is None: - self.__checkLanguage() - self.__checkEncoding() - else: - # it's a clone - self.languageChanged(editor.apiLanguage, propagate = False) - self.__encodingChanged(editor.encoding, propagate = False) - - self.coverageMarkersShown = False # flag remembering the current status of the - # code coverage markers - - self.setAcceptDrops(True) - - # breakpoint handling - self.breakpointModel = self.dbs.getBreakPointModel() - self.__restoreBreakpoints() - self.connect(self.breakpointModel, - SIGNAL("rowsAboutToBeRemoved(const QModelIndex &, int, int)"), - self.__deleteBreakPoints) - self.connect(self.breakpointModel, - SIGNAL("dataAboutToBeChanged(const QModelIndex &, const QModelIndex &)"), - self.__breakPointDataAboutToBeChanged) - self.connect(self.breakpointModel, - SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), - self.__changeBreakPoints) - self.connect(self.breakpointModel, - SIGNAL("rowsInserted(const QModelIndex &, int, int)"), - self.__addBreakPoints) - self.connect(self, SIGNAL("linesChanged()"), self.__linesChanged) - - # 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() - - def __registerImages(self): - """ - Private method to register images for autocompletion lists. - """ - self.registerImage(self.ClassID, - UI.PixmapCache.getPixmap("class.png")) - self.registerImage(self.ClassProtectedID, - UI.PixmapCache.getPixmap("class_protected.png")) - self.registerImage(self.ClassPrivateID, - UI.PixmapCache.getPixmap("class_private.png")) - self.registerImage(self.MethodID, - UI.PixmapCache.getPixmap("method.png")) - self.registerImage(self.MethodProtectedID, - UI.PixmapCache.getPixmap("method_protected.png")) - self.registerImage(self.MethodPrivateID, - UI.PixmapCache.getPixmap("method_private.png")) - self.registerImage(self.AttributeID, - UI.PixmapCache.getPixmap("attribute.png")) - self.registerImage(self.AttributeProtectedID, - UI.PixmapCache.getPixmap("attribute_protected.png")) - self.registerImage(self.AttributePrivateID, - UI.PixmapCache.getPixmap("attribute_private.png")) - self.registerImage(self.EnumID, - UI.PixmapCache.getPixmap("enum.png")) - - self.registerImage(self.FromDocumentID, - UI.PixmapCache.getPixmap("editor.png")) - - self.registerImage(self.TemplateImageID, - UI.PixmapCache.getPixmap("templateViewer.png")) - - def addClone(self, editor): - """ - Public method to add a clone to our list. - - @param clone reference to the cloned editor (Editor) - """ - self.__clones.append(editor) - - self.connect(editor, SIGNAL('editorRenamed'), self.fileRenamed) - self.connect(editor, SIGNAL('languageChanged'), self.languageChanged) - self.connect(editor, SIGNAL('eolChanged'), self.__eolChanged) - self.connect(editor, SIGNAL('encodingChanged'), self.__encodingChanged) - - def removeClone(self, editor): - """ - Public method to remove a clone from our list. - - @param clone reference to the cloned editor (Editor) - """ - if editor in self.__clones: - self.disconnect(editor, SIGNAL('editorRenamed'), self.fileRenamed) - self.disconnect(editor, SIGNAL('languageChanged'), self.languageChanged) - self.disconnect(editor, SIGNAL('eolChanged'), self.__eolChanged) - self.disconnect(editor, SIGNAL('encodingChanged'), self.__encodingChanged) - self.__clones.remove(editor) - - 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) - """ - bindName = self.fileName - - if line0.startswith("<?xml"): - # override extension for XML files - bindName = "dummy.xml" - - # check filetype - if self.filetype == "Python": - bindName = "dummy.py" - elif self.filetype == "Ruby": - bindName = "dummy.rb" - elif self.filetype == "D": - bindName = "dummy.d" - elif self.filetype == "Properties": - bindName = "dummy.ini" - - # #! marker detection - if line0.startswith("#!"): - if "python3" in line0: - bindName = "dummy.py" - self.filetype = "Python3" - elif "python2" in line0: - bindName = "dummy.py" - self.filetype = "Python" - elif "python" in line0: - bindName = "dummy.py" - self.filetype = "Python" - 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" - 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.showMenu = self.__initContextMenuShow() - self.graphicsMenu = self.__initContextMenuGraphics() - self.autocompletionMenu = self.__initContextMenuAutocompletion() - self.__menus["Checks"] = self.checksMenu - self.__menus["Show"] = self.showMenu - self.__menus["Graphics"] = self.graphicsMenu - self.__menus["Autocompletion"] = self.autocompletionMenu - self.exportersMenu = self.__initContextMenuExporters() - self.__menus["Exporters"] = self.exportersMenu - self.eolMenu = self.__initContextMenuEol() - self.__menus["Eol"] = self.eolMenu - self.encodingsMenu = self.__initContextMenuEncodings() - self.__menus["Encodings"] = self.encodingsMenu - - self.menuActs["Undo"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("editUndo.png"), - self.trUtf8('Undo'), self.undo) - self.menuActs["Redo"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("editRedo.png"), - self.trUtf8('Redo'), self.redo) - self.menuActs["Revert"] = \ - self.menu.addAction(self.trUtf8("Revert to last saved state"), - self.revertToUnmodified) - self.menu.addSeparator() - self.menuActs["Cut"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("editCut.png"), - self.trUtf8('Cut'), self.cut) - self.menuActs["Copy"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("editCopy.png"), - self.trUtf8('Copy'), self.copy) - self.menu.addAction(UI.PixmapCache.getIcon("editPaste.png"), - self.trUtf8('Paste'), self.paste) - if not self.miniMenu: - self.menu.addSeparator() - self.menu.addAction(UI.PixmapCache.getIcon("editIndent.png"), - self.trUtf8('Indent'), self.indentLineOrSelection) - self.menu.addAction(UI.PixmapCache.getIcon("editUnindent.png"), - self.trUtf8('Unindent'), self.unindentLineOrSelection) - self.menuActs["Comment"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("editComment.png"), - self.trUtf8('Comment'), self.commentLineOrSelection) - self.menuActs["Uncomment"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("editUncomment.png"), - self.trUtf8('Uncomment'), self.uncommentLineOrSelection) - self.menuActs["StreamComment"] = \ - self.menu.addAction(self.trUtf8('Stream Comment'), - self.streamCommentLineOrSelection) - self.menuActs["BoxComment"] = \ - self.menu.addAction(self.trUtf8('Box Comment'), - self.boxCommentLineOrSelection) - self.menu.addSeparator() - self.menu.addAction(self.trUtf8('Select to brace'), - self.selectToMatchingBrace) - self.menu.addAction(self.trUtf8('Select all'), self.__selectAll) - self.menu.addAction(self.trUtf8('Deselect all'), self.__deselectAll) - self.menu.addSeparator() - self.menuActs["SpellCheck"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("spellchecking.png"), - self.trUtf8('Check spelling...'), self.checkSpelling) - self.menuActs["SpellCheckSelection"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("spellchecking.png"), - self.trUtf8('Check spelling of selection...'), - self.__checkSpellingSelection) - self.menuActs["SpellCheckRemove"] = \ - self.menu.addAction(self.trUtf8("Remove from dictionary"), - self.__removeFromSpellingDictionary) - self.menu.addSeparator() - self.menu.addAction(self.trUtf8('Shorten empty lines'), - self.shortenEmptyLines) - 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.trUtf8("Use Monospaced Font"), - self.handleMonospacedEnable) - self.menuActs["MonospacedFont"].setCheckable(True) - self.menuActs["MonospacedFont"].setChecked(self.useMonospaced) - self.menuActs["AutosaveEnable"] = \ - self.menu.addAction(self.trUtf8("Autosave enabled"), - self.__autosaveEnable) - self.menuActs["AutosaveEnable"].setCheckable(True) - self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled) - self.menuActs["TypingAidsEnabled"] = \ - self.menu.addAction(self.trUtf8("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.trUtf8("Autocompletion 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.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.showMenu) - self.menu.addSeparator() - self.menuActs["Diagrams"] = self.menu.addMenu(self.graphicsMenu) - self.menu.addSeparator() - self.menu.addAction(self.trUtf8('New view'), self.__newView) - act = self.menu.addAction(self.trUtf8('New view (with new split)'), - self.__newViewNewSplit) - if not self.vm.canSplit(): - act.setEnabled(False) - self.menu.addAction(UI.PixmapCache.getIcon("close.png"), - self.trUtf8('Close'), self.__contextClose) - self.menu.addSeparator() - self.menuActs["Save"] = \ - self.menu.addAction(UI.PixmapCache.getIcon("fileSave.png"), - self.trUtf8('Save'), self.__contextSave) - self.menu.addAction(UI.PixmapCache.getIcon("fileSaveAs.png"), - self.trUtf8('Save As...'), self.__contextSaveAs) - if not self.miniMenu: - self.menu.addMenu(self.exportersMenu) - self.menu.addSeparator() - self.menu.addAction(UI.PixmapCache.getIcon("printPreview.png"), - self.trUtf8("Print Preview"), self.printPreviewFile) - self.menu.addAction(UI.PixmapCache.getIcon("print.png"), - self.trUtf8('Print'), self.printFile) - - self.connect(self.menu, SIGNAL('aboutToShow()'), self.__showContextMenu) - - self.spellingMenu = QMenu() - self.__menus["Spelling"] = self.spellingMenu - - self.connect(self.spellingMenu, SIGNAL('aboutToShow()'), - self.__showContextMenuSpelling) - self.connect(self.spellingMenu, SIGNAL('triggered(QAction *)'), - self.__contextMenuSpellingTriggered) - - def __initContextMenuAutocompletion(self): - """ - Private method used to setup the Checks context sub menu. - """ - menu = QMenu(self.trUtf8('Autocomplete')) - - self.menuActs["acDynamic"] = \ - menu.addAction(self.trUtf8('dynamic'), - self.autoComplete) - menu.addSeparator() - menu.addAction(self.trUtf8('from Document'), - self.autoCompleteFromDocument) - self.menuActs["acAPI"] = \ - menu.addAction(self.trUtf8('from APIs'), - self.autoCompleteFromAPIs) - self.menuActs["acAPIDocument"] = \ - menu.addAction(self.trUtf8('from Document and APIs'), - self.autoCompleteFromAll) - menu.addSeparator() - self.menuActs["calltip"] = \ - menu.addAction(self.trUtf8('Calltip'), self.callTip) - - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuAutocompletion) - - return menu - - def __initContextMenuChecks(self): - """ - Private method used to setup the Checks context sub menu. - """ - menu = QMenu(self.trUtf8('Check')) - self.connect(menu, SIGNAL("aboutToShow()"), self.__showContextMenuChecks) - return menu - - def __initContextMenuShow(self): - """ - Private method used to setup the Show context sub menu. - """ - menu = QMenu(self.trUtf8('Show')) - - menu.addAction(self.trUtf8('Code metrics...'), self.__showCodeMetrics) - self.coverageMenuAct = \ - menu.addAction(self.trUtf8('Code coverage...'), self.__showCodeCoverage) - self.coverageShowAnnotationMenuAct = \ - menu.addAction(self.trUtf8('Show code coverage annotations'), - self.__codeCoverageShowAnnotations) - self.coverageHideAnnotationMenuAct = \ - menu.addAction(self.trUtf8('Hide code coverage annotations'), - self.__codeCoverageHideAnnotations) - self.profileMenuAct = \ - menu.addAction(self.trUtf8('Profile data...'), self.__showProfileData) - - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuShow) - - return menu - - def __initContextMenuGraphics(self): - """ - Private method used to setup the diagrams context sub menu. - """ - menu = QMenu(self.trUtf8('Diagrams')) - - menu.addAction(self.trUtf8('Class Diagram...'), - self.__showClassDiagram) - menu.addAction(self.trUtf8('Package Diagram...'), - self.__showPackageDiagram) - menu.addAction(self.trUtf8('Imports Diagram...'), - self.__showImportsDiagram) - self.applicationDiagramMenuAct = \ - menu.addAction(self.trUtf8('Application Diagram...'), - self.__showApplicationDiagram) - - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuGraphics) - - return menu - - def __initContextMenuLanguages(self): - """ - Private method used to setup the Languages context sub menu. - """ - menu = QMenu(self.trUtf8("Languages")) - - self.languagesActGrp = QActionGroup(self) - self.noLanguageAct = menu.addAction(self.trUtf8("No Language")) - self.noLanguageAct.setCheckable(True) - self.noLanguageAct.setData("None") - self.languagesActGrp.addAction(self.noLanguageAct) - menu.addSeparator() - - self.supportedLanguages = {} - supportedLanguages = Lexers.getSupportedLanguages() - languages = sorted(list(supportedLanguages.keys())) - for language in languages: - if language != "Guessed": - self.supportedLanguages[language] = supportedLanguages[language][:] - act = menu.addAction(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.trUtf8("Guessed")) - self.pygmentsAct.setCheckable(True) - self.pygmentsAct.setData("Guessed") - self.languagesActGrp.addAction(self.pygmentsAct) - self.pygmentsSelAct = menu.addAction(self.trUtf8("Alternatives")) - self.pygmentsSelAct.setData("Alternatives") - - self.connect(menu, SIGNAL('triggered(QAction *)'), self.__languageMenuTriggered) - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuLanguages) - - return menu - - def __initContextMenuEncodings(self): - """ - Private method used to setup the Encodings context sub menu. - """ - self.supportedEncodings = {} - - menu = QMenu(self.trUtf8("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) - - self.connect(menu, SIGNAL('triggered(QAction *)'), self.__encodingsMenuTriggered) - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuEncodings) - - return menu - - def __initContextMenuEol(self): - """ - Private method to setup the eol context sub menu. - """ - self.supportedEols = {} - - menu = QMenu(self.trUtf8("End-of-Line Type")) - - self.eolActGrp = QActionGroup(self) - - act = menu.addAction(self.trUtf8("Unix")) - act.setCheckable(True) - act.setData('\n') - self.supportedEols['\n'] = act - self.eolActGrp.addAction(act) - - act = menu.addAction(self.trUtf8("Windows")) - act.setCheckable(True) - act.setData('\r\n') - self.supportedEols['\r\n'] = act - self.eolActGrp.addAction(act) - - act = menu.addAction(self.trUtf8("Macintosh")) - act.setCheckable(True) - act.setData('\r') - self.supportedEols['\r'] = act - self.eolActGrp.addAction(act) - - self.connect(menu, SIGNAL('triggered(QAction *)'), self.__eolMenuTriggered) - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuEol) - - return menu - - def __initContextMenuExporters(self): - """ - Private method used to setup the Exporters context sub menu. - """ - menu = QMenu(self.trUtf8("Export as")) - - supportedExporters = Exporters.getSupportedFormats() - exporters = sorted(list(supportedExporters.keys())) - for exporter in exporters: - act = menu.addAction(supportedExporters[exporter]) - act.setData(exporter) - - self.connect(menu, SIGNAL('triggered(QAction *)'), self.__exportMenuTriggered) - - return menu - - def __initContextMenuMargins(self): - """ - Private method used to setup the context menu for the margins - """ - self.marginMenuActs = {} - - if self.__unifiedMargins: - self.__initContextMenuUnifiedMargins() - else: - self.__initContextMenuSeparateMargins() - - def __initContextMenuSeparateMargins(self): - """ - Private method used to setup the context menu for the separated margins - """ - # bookmark margin - self.bmMarginMenu = QMenu() - - self.bmMarginMenu.addAction(self.trUtf8('Toggle bookmark'), - self.menuToggleBookmark) - self.marginMenuActs["NextBookmark"] = \ - self.bmMarginMenu.addAction(self.trUtf8('Next bookmark'), - self.nextBookmark) - self.marginMenuActs["PreviousBookmark"] = \ - self.bmMarginMenu.addAction(self.trUtf8('Previous bookmark'), - self.previousBookmark) - self.marginMenuActs["ClearBookmark"] = \ - self.bmMarginMenu.addAction(self.trUtf8('Clear all bookmarks'), - self.clearBookmarks) - - self.connect(self.bmMarginMenu, SIGNAL('aboutToShow()'), - self.__showContextMenuMargin) - - # breakpoint margin - self.bpMarginMenu = QMenu() - - self.marginMenuActs["Breakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Toggle breakpoint'), - self.menuToggleBreakpoint) - self.marginMenuActs["TempBreakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Toggle temporary breakpoint'), - self.__menuToggleTemporaryBreakpoint) - self.marginMenuActs["EditBreakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Edit breakpoint...'), - self.menuEditBreakpoint) - self.marginMenuActs["EnableBreakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Enable breakpoint'), - self.__menuToggleBreakpointEnabled) - self.marginMenuActs["NextBreakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Next breakpoint'), - self.menuNextBreakpoint) - self.marginMenuActs["PreviousBreakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Previous breakpoint'), - self.menuPreviousBreakpoint) - self.marginMenuActs["ClearBreakpoint"] = \ - self.bpMarginMenu.addAction(self.trUtf8('Clear all breakpoints'), - self.__menuClearBreakpoints) - - self.connect(self.bpMarginMenu, SIGNAL('aboutToShow()'), - self.__showContextMenuMargin) - - # indicator margin - self.indicMarginMenu = QMenu() - - self.marginMenuActs["GotoSyntaxError"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Goto syntax error'), - self.gotoSyntaxError) - self.marginMenuActs["ShowSyntaxError"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Show syntax error message'), - self.__showSyntaxError) - self.marginMenuActs["ClearSyntaxError"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Clear syntax error'), - self.clearSyntaxError) - self.indicMarginMenu.addSeparator() - self.marginMenuActs["NextCoverageMarker"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Next uncovered line'), - self.nextUncovered) - self.marginMenuActs["PreviousCoverageMarker"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Previous uncovered line'), - self.previousUncovered) - self.indicMarginMenu.addSeparator() - self.marginMenuActs["NextTaskMarker"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Next task'), - self.nextTask) - self.marginMenuActs["PreviousTaskMarker"] = \ - self.indicMarginMenu.addAction(self.trUtf8('Previous task'), - self.previousTask) - - self.connect(self.indicMarginMenu, SIGNAL('aboutToShow()'), - self.__showContextMenuMargin) - - def __initContextMenuUnifiedMargins(self): - """ - Private method used to setup the context menu for the unified margins - """ - self.marginMenu = QMenu() - - self.marginMenu.addAction(self.trUtf8('Toggle bookmark'), - self.menuToggleBookmark) - self.marginMenuActs["NextBookmark"] = \ - self.marginMenu.addAction(self.trUtf8('Next bookmark'), - self.nextBookmark) - self.marginMenuActs["PreviousBookmark"] = \ - self.marginMenu.addAction(self.trUtf8('Previous bookmark'), - self.previousBookmark) - self.marginMenuActs["ClearBookmark"] = \ - self.marginMenu.addAction(self.trUtf8('Clear all bookmarks'), - self.clearBookmarks) - self.marginMenu.addSeparator() - self.marginMenuActs["GotoSyntaxError"] = \ - self.marginMenu.addAction(self.trUtf8('Goto syntax error'), - self.gotoSyntaxError) - self.marginMenuActs["ShowSyntaxError"] = \ - self.marginMenu.addAction(self.trUtf8('Show syntax error message'), - self.__showSyntaxError) - self.marginMenuActs["ClearSyntaxError"] = \ - self.marginMenu.addAction(self.trUtf8('Clear syntax error'), - self.clearSyntaxError) - self.marginMenu.addSeparator() - self.marginMenuActs["Breakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Toggle breakpoint'), - self.menuToggleBreakpoint) - self.marginMenuActs["TempBreakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Toggle temporary breakpoint'), - self.__menuToggleTemporaryBreakpoint) - self.marginMenuActs["EditBreakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Edit breakpoint...'), - self.menuEditBreakpoint) - self.marginMenuActs["EnableBreakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Enable breakpoint'), - self.__menuToggleBreakpointEnabled) - self.marginMenuActs["NextBreakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Next breakpoint'), - self.menuNextBreakpoint) - self.marginMenuActs["PreviousBreakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Previous breakpoint'), - self.menuPreviousBreakpoint) - self.marginMenuActs["ClearBreakpoint"] = \ - self.marginMenu.addAction(self.trUtf8('Clear all breakpoints'), - self.__menuClearBreakpoints) - self.marginMenu.addSeparator() - self.marginMenuActs["NextCoverageMarker"] = \ - self.marginMenu.addAction(self.trUtf8('Next uncovered line'), - self.nextUncovered) - self.marginMenuActs["PreviousCoverageMarker"] = \ - self.marginMenu.addAction(self.trUtf8('Previous uncovered line'), - self.previousUncovered) - self.marginMenu.addSeparator() - self.marginMenuActs["NextTaskMarker"] = \ - self.marginMenu.addAction(self.trUtf8('Next task'), - self.nextTask) - self.marginMenuActs["PreviousTaskMarker"] = \ - self.marginMenu.addAction(self.trUtf8('Previous task'), - self.previousTask) - self.marginMenu.addSeparator() - self.marginMenuActs["LMBbookmarks"] = \ - self.marginMenu.addAction(self.trUtf8('LMB toggles bookmarks'), - self.__lmBbookmarks) - self.marginMenuActs["LMBbookmarks"].setCheckable(True) - self.marginMenuActs["LMBbookmarks"].setChecked(False) - self.marginMenuActs["LMBbreakpoints"] = \ - self.marginMenu.addAction(self.trUtf8('LMB toggles breakpoints'), - self.__lmBbreakpoints) - self.marginMenuActs["LMBbreakpoints"].setCheckable(True) - self.marginMenuActs["LMBbreakpoints"].setChecked(True) - - self.connect(self.marginMenu, SIGNAL('aboutToShow()'), - self.__showContextMenuMargin) - - def __exportMenuTriggered(self, act): - """ - Private method to handle the selection of an export format. - - @param act reference to the action that was triggered (QAction) - """ - exporterFormat = act.data() - self.exportFile(exporterFormat) - - def exportFile(self, exporterFormat): - """ - Public method to export the file. - - @param exporterFormat format the file should be exported into (string) - """ - if exporterFormat: - exporter = Exporters.getExporter(exporterFormat, self) - if exporter: - exporter.exportSource() - else: - QMessageBox.critical(self, - self.trUtf8("Export source"), - self.trUtf8("""<p>No exporter available for the """ - """export format <b>{0}</b>. Aborting...</p>""")\ - .format(exporterFormat), - QMessageBox.StandardButtons(\ - QMessageBox.Ok)) - else: - QMessageBox.critical(self, - self.trUtf8("Export source"), - self.trUtf8("""No export format given. Aborting..."""), - QMessageBox.StandardButtons(\ - QMessageBox.Ok)) - - def __showContextMenuLanguages(self): - """ - Private slot handling the aboutToShow signal of the languages context menu. - """ - if self.apiLanguage.startswith("Pygments|"): - self.pygmentsSelAct.setText( - self.trUtf8("Alternatives ({0})").format(self.getLanguage())) - else: - self.pygmentsSelAct.setText(self.trUtf8("Alternatives")) - self.emit(SIGNAL("showMenu"), "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([l[0] for l in get_all_lexers()]) - try: - lexerSel = lexerList.index(self.getLanguage()) - except ValueError: - lexerSel = 0 - lexerName, ok = QInputDialog.getItem(\ - self, - self.trUtf8("Pygments Lexer"), - self.trUtf8("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.setLanguage(self.supportedLanguages[language][1]) - - def languageChanged(self, language, propagate = True): - """ - Public slot handling a change of a connected editor's language. - - @param language language to be set (string) - @keyparam propagate flag indicating to propagate the change (boolean) - """ - if language == '': - self.__resetLanguage(propagate = propagate) - elif language == "Guessed": - self.setLanguage("dummy.pygments") - elif language.startswith("Pygments|"): - pyname = language.split("|", 1)[1] - self.setLanguage("dummy.pygments", pyname = pyname) - else: - self.setLanguage(self.supportedLanguages[language][1], propagate = propagate) - - def __resetLanguage(self, propagate = True): - """ - Private method used to reset the language selection. - - @keyparam 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): - self.disconnect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded) - - self.apiLanguage = "" - self.lexer_ = None - self.__lexerReset = True - self.setLexer() - self.setMonospaced(self.useMonospaced) - if self.completer is not None: - self.completer.setEnabled(False) - self.completer = None - self.__setTextDisplay() - - if not self.inLanguageChanged and propagate: - self.inLanguageChanged = True - self.emit(SIGNAL('languageChanged'), 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) - @keyparam propagate flag indicating to propagate the change (boolean) - @keyparam pyname name of the pygments lexer to use (string) - """ - self.__lexerReset = False - self.__bindLexer(filename, pyname = pyname) - self.__bindCompleter(filename) - self.recolor() - self.__checkLanguage() - - # set the text display - if initTextDisplay: - self.__setTextDisplay() - - # set the autocompletion and calltips function - self.__setAutoCompletion() - self.__setCallTips() - - if not self.inLanguageChanged and propagate: - self.inLanguageChanged = True - self.emit(SIGNAL('languageChanged'), 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.emit(SIGNAL("showMenu"), "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.__encodingChanged("%s-selected" % encoding) - - def __checkEncoding(self): - """ - Private method to check the selected encoding of the encodings submenu. - """ - try: - self.supportedEncodings[self.__normalizedEncoding()].setChecked(True) - except (AttributeError, KeyError): - pass - - def __encodingChanged(self, encoding, propagate = True): - """ - Private slot to handle a change of the encoding. - - @keyparam propagate flag indicating to propagate the change (boolean) - """ - self.encoding = encoding - self.__checkEncoding() - - if not self.inEncodingChanged and propagate: - self.inEncodingChanged = True - self.emit(SIGNAL("encodingChanged"), self.encoding) - self.inEncodingChanged = False - - def __normalizedEncoding(self): - """ - Private method to calculate the normalized encoding string. - - @return normalized encoding (string) - """ - return self.encoding.replace("-default", "")\ - .replace("-guessed", "")\ - .replace("-selected", "") - - def __showContextMenuEol(self): - """ - Private slot handling the aboutToShow signal of the eol context menu. - """ - self.emit(SIGNAL("showMenu"), "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. - """ - try: - self.supportedEols[self.getLineSeparator()].setChecked(True) - except AttributeError: - pass - - 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.emit(SIGNAL("eolChanged"), eol) - self.inEolChanged = 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) - @keyparam 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.disconnect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded) - - language = "" - project = e5App().getObject("Project") - if project.isOpen() and project.isProjectFile(filename): - language = project.getEditorLexerAssoc(os.path.basename(filename)) - if not language: - filename = os.path.basename(filename) - language = Preferences.getEditorLexerAssoc(filename) - if language.startswith("Pygments|"): - pyname = language.split("|", 1)[1] - language = "" - - self.lexer_ = Lexers.getLexer(language, self, pyname = pyname) - if self.lexer_ is None: - self.setLexer() - self.apiLanguage = "" - return - - if pyname: - self.apiLanguage = "Pygments|%s" % pyname - else: - self.apiLanguage = self.lexer_.language() - if self.apiLanguage == "POV": - self.apiLanguage = "Povray" - self.setLexer(self.lexer_) - self.__setMarginsDisplay() - if self.lexer_.lexer() == "container" or self.lexer_.lexer() is None: - self.setStyleBits(self.lexer_.styleBitsNeeded()) - self.connect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded) - - # get the font for style 0 and set it as the default font - key = 'Scintilla/%s/style0/font' % self.lexer_.language() - fdesc = Preferences.Prefs.settings.value(key) - if fdesc is not None: - font = QFont(fdesc[0], int(fdesc[1])) - self.lexer_.setDefaultFont(font) - self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla") - - # now set the lexer properties - self.lexer_.initProperties() - - # initialize the auto indent style of the lexer - ais = self.lexer_.autoIndentStyle() - - # initialize the lexer APIs settings - api = self.vm.getAPIsManager().getAPIs(self.apiLanguage) - if api is not None: - self.lexer_.setAPIs(api.getQsciAPIs()) - self.acAPI = True - else: - self.acAPI = False - self.emit(SIGNAL("autoCompletionAPIsAvailable"), self.acAPI) - - 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): - """ - Public method to retrieve the language of the editor. - - @return language of the editor (string) - """ - if self.apiLanguage == "Guessed" or self.apiLanguage.startswith("Pygments|"): - return self.lexer_.name() - else: - 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) - - 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 self.fileName is not None: - self.lastModified = QFileInfo(self.fileName).lastModified() - if Preferences.getEditor("AutoCheckSyntax"): - self.clearSyntaxError() - self.emit(SIGNAL('modificationStatusChanged'), m, self) - self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable()) - self.emit(SIGNAL('redoAvailable'), 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.emit(SIGNAL('cursorChanged'), self.fileName, line+1, index) - - if Preferences.getEditor("MarkOccurrencesEnabled"): - self.__markOccurrencesTimer.stop() - self.__markOccurrencesTimer.start() - - 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) - - self.lastLine = line - self.lastIndex = index - - def __modificationReadOnly(self): - """ - Private slot to handle the modificationAttempted signal. - """ - QMessageBox.warning(None, - self.trUtf8("Modification of Read Only file"), - self.trUtf8("""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 getEncoding(self): - """ - Public method to return the current encoding. - - @return current encoding (string) - """ - return self.encoding - - def getFolds(self): - """ - Public method to get a list line numbers of collapsed folds. - - @return list of line numbers of folded lines (list of integer) - """ - line = 0 - folds = [] - maxline = self.lines() - while line < maxline: - if self.foldHeaderAt(line) and not self.foldExpandedAt(line): - folds.append(line) - line += 1 - return folds - - def isPyFile(self): - """ - Public method to return a flag indicating a Python file. - - @return flag indicating a Python file (boolean) - """ - return self.filetype == "Python" or \ - (self.fileName is not None and \ - os.path.splitext(self.fileName)[1] in self.pyExtensions) - - def isPy3File(self): - """ - Public method to return a flag indicating a Python3 file. - - @return flag indicating a Python3 file (boolean) - """ - return self.filetype == "Python3" or \ - (self.fileName is not None and \ - os.path.splitext(self.fileName)[1] in self.py3Extensions) - - def isRubyFile(self): - """ - Public method to return a flag indicating a Ruby file. - - @return flag indicating a Ruby file (boolean) - """ - return self.filetype == "Ruby" or \ - (self.fileName is not None and \ - os.path.splitext(self.fileName)[1] in self.rbExtensions) - - 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 __linesChanged(self): - """ - Private method to track text changes. - - This method checks, if lines have been inserted or removed in order to - update the breakpoints. - """ - if self.breaks: - bps = [] # list of breakpoints - for handle, (ln, cond, temp, enabled, ignorecount) in list(self.breaks.items()): - line = self.markerLine(handle) + 1 - bps.append((ln, line, (cond, temp, enabled, ignorecount))) - self.markerDeleteHandle(handle) - self.breaks = {} - for bp in bps: - index = self.breakpointModel.getBreakPointIndex(self.fileName, bp[0]) - self.breakpointModel.setBreakPointByIndex(index, - self.fileName, bp[1], bp[2]) - - 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) - - 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 indexes indexes of changed breakpoints. - """ - 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 linenumber of the breakpoint (integer) - """ - for handle, (ln, _, _, _, _) in list(self.breaks.items()): - if self.markerLine(handle) == line-1: - break - else: - # not found, simply ignore it - return - - del self.breaks[handle] - self.markerDeleteHandle(handle) - - def newBreakpointWithProperties(self, line, properties): - """ - Private 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 - - handle = self.markerAdd(line-1, marker) - self.breaks[handle] = (line,) + properties - self.emit(SIGNAL('breakpointToggled'), self) - - 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, (ln, _, _, _, _) in list(self.breaks.items()): - 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.emit(SIGNAL('breakpointToggled'), 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() or self.isPy3File() or self.isRubyFile()): - self.breakpointModel.addBreakPoint(self.fileName, line, - ('', temporary, True, 0)) - self.emit(SIGNAL('breakpointToggled'), self) - - def __toggleBreakpointEnabled(self, line): - """ - Private method to toggle a breakpoints enabled status. - - @param line line number of the breakpoint (integer) - """ - for handle, (ln, cond, temp, enabled, ignorecount) in list(self.breaks.items()): - if self.markerLine(handle) == line - 1: - break - else: - # no breakpoint found on that line - return - - index = self.breakpointModel.getBreakPointIndex(self.fileName, line) - self.breakpointModel.setBreakPointEnabledByIndex(index, not enabled) - - 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 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() - found = False - for handle, (ln, cond, temp, enabled, ignorecount) in list(self.breaks.items()): - if self.markerLine(handle) == self.line: - found = True - break - - if found: - index = self.breakpointModel.getBreakPointIndex(self.fileName, ln) - if not index.isValid(): - return - - dlg = EditBreakpointDialog((self.fileName, ln), - (cond, temp, enabled, ignorecount), - self.condHistory, self, modal = True) - if dlg.exec_() == QDialog.Accepted: - cond, temp, enabled, ignorecount = dlg.getData() - self.breakpointModel.setBreakPointByIndex(index, - self.fileName, ln, (cond, temp, enabled, ignorecount)) - - 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. - """ - idxList = [] - for handle, (ln, _, _, _, _) in list(self.breaks.items()): - 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: - break - else: - # set a new bookmark - handle = self.markerAdd(line - 1, self.bookmark) - self.bookmarks.append(handle) - self.emit(SIGNAL('bookmarkToggled'), self) - return - - self.bookmarks.remove(handle) - self.markerDeleteHandle(handle) - self.emit(SIGNAL('bookmarkToggled'), self) - - 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 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 = [] - self.emit(SIGNAL('bookmarkToggled'), self) - - ############################################################################ - ## Printing methods below - ############################################################################ - - def printFile(self): - """ - Public slot to print the text. - """ - printer = Printer(mode = QPrinter.HighResolution) - sb = e5App().getObject("UserInterface").statusBar() - printDialog = QPrintDialog(printer, self) - if self.hasSelectedText(): - printDialog.addEnabledOption(QAbstractPrintDialog.PrintSelection) - if printDialog.exec_() == QDialog.Accepted: - sb.showMessage(self.trUtf8('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.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.trUtf8('Printing completed'), 2000) - else: - sb.showMessage(self.trUtf8('Error while printing'), 2000) - QApplication.processEvents() - else: - sb.showMessage(self.trUtf8('Printing aborted'), 2000) - QApplication.processEvents() - - def printPreviewFile(self): - """ - Public slot to show a print preview of the text. - """ - from PyQt4.QtGui import QPrintPreviewDialog - - printer = Printer(mode = QPrinter.HighResolution) - fn = self.getFileName() - if fn is not None: - printer.setDocName(os.path.basename(fn)) - else: - printer.setDocName(self.noName) - preview = QPrintPreviewDialog(printer, self) - self.connect(preview, SIGNAL("paintRequested(QPrinter*)"), 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 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. - """ - todoMarkers = Preferences.getTasks("TasksMarkers").split() - bugfixMarkers = Preferences.getTasks("TasksMarkersBugfix").split() - 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 - lineIndex = -1 - for line in txtList: - lineIndex += 1 - shouldContinue = False - # normal tasks first - for tasksMarker in todoMarkers: - index = line.find(tasksMarker) - if index > -1: - task = line[index:] - self.markerAdd(lineIndex, self.taskmarker) - self.taskViewer.addFileTask(task, self.fileName, lineIndex + 1, False) - self.__hasTaskMarkers = True - shouldContinue = True - break - if shouldContinue: - continue - - # bugfix tasks second - for tasksMarker in bugfixMarkers: - index = line.find(tasksMarker) - if index > -1: - task = line[index:] - self.markerAdd(lineIndex, self.taskmarker) - self.taskViewer.addFileTask(task, self.fileName, lineIndex+1, True) - self.__hasTaskMarkers = True - break - self.emit(SIGNAL('taskMarkersUpdated'), self) - - ############################################################################ - ## 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 = QMessageBox.warning(self.vm, - self.trUtf8("File Modified"), - self.trUtf8("<p>The file <b>{0}</b> has unsaved changes.</p>") - .format(fn), - QMessageBox.StandardButtons(\ - QMessageBox.Abort | \ - QMessageBox.Discard | \ - QMessageBox.Save), - QMessageBox.Save) - if res == QMessageBox.Save: - ok, newName = self.saveFile() - if ok: - self.vm.setEditorName(self, newName) - return ok - elif res == QMessageBox.Abort or res == QMessageBox.Cancel: - return False - - 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): - """ - 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) - """ - QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) - - try: - if createIt and not os.path.exists(fn): - f = open(fn, "w") - f.close() - txt, self.encoding = Utilities.readEncodedFile(fn) - except (UnicodeDecodeError, IOError) as why: - QApplication.restoreOverrideCursor() - QMessageBox.critical(self.vm, self.trUtf8('Open File'), - self.trUtf8('<p>The file <b>{0}</b> could not be opened.</p>' - '<p>Reason: {1}</p>') - .format(fn, str(why))) - QApplication.restoreOverrideCursor() - raise - fileEol = self.detectEolString(txt) - - modified = False - if (not Preferences.getEditor("TabForIndentation")) and \ - Preferences.getEditor("ConvertTabsOnLoad") and \ - not (self.lexer_ and \ - self.lexer_.alwaysKeepTabs()): - txtExpanded = txt.expandtabs(Preferences.getEditor("TabWidth")) - if txtExpanded != txt: - modified = True - txt = txtExpanded - del txtExpanded - - self.setText(txt) - - # perform automatic eol conversion - if Preferences.getEditor("AutomaticEOLConversion"): - self.convertEols(self.eolMode()) - else: - self.setEolModeByEolString(fileEol) - - self.extractTasks() - - QApplication.restoreOverrideCursor() - - self.setModified(modified) - self.lastModified = QFileInfo(self.fileName).lastModified() - - def setEolModeByEolString(self, eolStr): - """ - Public method to set the eol mode given the eol string. - - @param eolStr eol string (string) - """ - if eolStr == '\r\n': - self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolWindows)) - elif eolStr == '\n': - self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolUnix)) - elif eolStr == '\r': - self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolMac)) - self.__eolChanged() - - 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): - """ - Public slot to write the text to a file. - - @param fn filename to write to (string) - @return flag indicating success (boolean) - """ - if Preferences.getEditor("StripTrailingWhitespace"): - self.__removeTrailingWhitespace() - - txt = self.text() - # work around glitch in scintilla: always make sure, - # that the last line is terminated properly - 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 = Preferences.getEditor("CreateBackupFile") - if createBackup: - if os.path.islink(fn): - fn = os.path.realpath(fn) - bfn = '%s~' % fn - try: - permissions = os.stat(fn).st_mode - perms_valid = True - except EnvironmentError: - # if there was an error, ignore it - perms_valid = False - try: - os.remove(bfn) - except EnvironmentError: - # if there was an error, ignore it - pass - try: - os.rename(fn, bfn) - except EnvironmentError: - # if there was an error, ignore it - pass - - # now write text to the file fn - try: - self.encoding = Utilities.writeEncodedFile(fn, txt, self.encoding) - if createBackup and perms_valid: - os.chmod(fn, permissions) - return True - except (IOError, Utilities.CodingError, UnicodeError) as why: - QMessageBox.critical(self, self.trUtf8('Save File'), - self.trUtf8('<p>The file <b>{0}</b> could not be saved.<br/>' - 'Reason: {1}</p>') - .format(fn, str(why))) - return False - - def saveFile(self, saveas = False, path = None): - """ - Public slot 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 tuple of two values (boolean, string) giving a success indicator and - the name of the saved file - """ - if not saveas and not self.isModified(): - return (False, None) # do nothing if text wasn't changed - - newName = None - if saveas or self.fileName is None: - saveas = True - if not path and self.fileName is not None: - path = os.path.dirname(self.fileName) - if path is None: - path = "" - defaultFilter = Preferences.getEditor("DefaultSaveFilter") - fn, selectedFilter = QFileDialog.getSaveFileNameAndFilter( - self, - self.trUtf8("Save File"), - path, - Lexers.getSaveFileFiltersList(True, True), - defaultFilter, - QFileDialog.Options(QFileDialog.DontConfirmOverwrite)) - - if fn: - ext = QFileInfo(fn).suffix() - if not ext: - ex = selectedFilter.split("(*")[1].split(")")[0] - if ex: - fn += ex - if QFileInfo(fn).exists(): - res = QMessageBox.warning(self, - self.trUtf8("Save File"), - self.trUtf8("<p>The file <b>{0}</b> already exists.</p>") - .format(fn), - QMessageBox.StandardButtons(\ - QMessageBox.Abort | \ - QMessageBox.Save), - QMessageBox.Abort) - if res == QMessageBox.Abort or res == QMessageBox.Cancel: - return (False, None) - fn = Utilities.toNativeSeparators(fn) - newName = fn - else: - return (False, None) - else: - fn = self.fileName - - self.emit(SIGNAL('editorAboutToBeSaved'), self.fileName) - if self.writeFile(fn): - if saveas: - self.__clearBreakpoints(self.fileName) - self.fileName = fn - self.setModified(False) - self.setReadOnly(False) - self.setWindowTitle(self.fileName) - if self.lexer_ is None and not self.__lexerReset: - self.setLanguage(self.fileName) - - if saveas: - self.isResourcesFile = self.fileName.endswith(".qrc") - self.__initContextMenu() - self.emit(SIGNAL('editorRenamed'), self.fileName) - self.lastModified = QFileInfo(self.fileName).lastModified() - if newName is not None: - self.vm.addToRecentList(newName) - self.emit(SIGNAL('editorSaved'), self.fileName) - self.__autoSyntaxCheck() - self.extractTasks() - return (True, self.fileName) - else: - self.lastModified = QFileInfo(fn).lastModified() - return (False, None) - - def saveFileAs(self, path = None): - """ - Public slot to save a file with a new name. - - @param path directory to save the file in (string) - @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.fileName = fn - self.setWindowTitle(self.fileName) - - if self.lexer_ is None: - self.setLanguage(self.fileName) - - self.lastModified = QFileInfo(self.fileName).lastModified() - 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.emit(SIGNAL('editorRenamed'), self.fileName) - self.inFileRenamed = False - - ############################################################################ - ## Utility methods below - ############################################################################ - - def ensureVisible(self, line): - """ - Public slot to ensure, that the specified line is visible. - - @param line line number to make visible - """ - self.ensureLineVisible(line-1) - - def ensureVisibleTop(self, line): - """ - Public slot to ensure, that the specified line is visible at the top - of the editor. - - @param line line number to make visible - """ - topLine = self.firstVisibleLine() - linesToScroll = line - topLine - self.scrollVertical(linesToScroll) - - 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 self.__unifiedMargins: - if margin == 1: - if modifiers & Qt.KeyboardModifiers(Qt.ShiftModifier): - if self.marginMenuActs["LMBbreakpoints"].isChecked(): - self.toggleBookmark(line + 1) - else: - self.__toggleBreakpoint(line + 1) - elif modifiers & Qt.KeyboardModifiers(Qt.ControlModifier): - if self.markersAtLine(line) & (1 << self.syntaxerror): - self.__showSyntaxError(line) - else: - if self.marginMenuActs["LMBbreakpoints"].isChecked(): - self.__toggleBreakpoint(line + 1) - else: - self.toggleBookmark(line + 1) - else: - 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) - - def handleMonospacedEnable(self): - """ - Private slot to handle the Use Monospaced Font context menu entry. - """ - if self.menuActs["MonospacedFont"].isChecked(): - self.setMonospaced(True) - else: - if self.lexer_: - self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla") - 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) - @keyparam useWordChars flag indicating to use the wordCharacters - method (boolean) - @return tuple with start and end indices of the word at the position - (integer, integer) - """ - text = self.text(line) - if self.caseSensitive(): - cs = Qt.CaseSensitive - else: - cs = Qt.CaseInsensitive - wc = self.wordCharacters() - if wc is None or not useWordChars: - regExp = QRegExp('[^\w_]', cs) - else: - regExp = QRegExp('[^%s]' % re.escape(wc), cs) - start = regExp.lastIndexIn(text, index) + 1 - end = regExp.indexIn(text, index) - if start == end + 1 and index > 0: - # we are on a word boundary, try again - start = regExp.lastIndexIn(text, index - 1) + 1 - if start == -1: - start = 0 - if end == -1: - end = len(text) - - return (start, end) - - 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) - @keyparam 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 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 == '\n' or ch == '\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) - - def clearSearchIndicators(self): - """ - Public method to clear all search indicators. - """ - self.clearAllIndicators(self.searchIndicator) - self.__markedText = "" - - 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 - - ############################################################################ - ## Comment handling methods below - ############################################################################ - - 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: - self.insertAt(self.lexer_.commentStr(), line, self.indentation(line)) - 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.text(line).strip().startswith(commentStr): - return - - # now remove the comment string - self.beginUndoAction() - if Preferences.getEditor("CommentColumn0"): - self.setSelection(line, 0, line, len(commentStr)) - else: - self.setSelection(line, self.indentation(line), - line, self.indentation(line) + 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() - if indexTo == 0: - endLine = lineTo - 1 - else: - endLine = lineTo - - self.beginUndoAction() - # iterate over the lines - for line in range(lineFrom, endLine + 1): - if Preferences.getEditor("CommentColumn0"): - self.insertAt(commentStr, line, 0) - else: - self.insertAt(commentStr, line, self.indentation(line)) - - # 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() - if indexTo == 0: - endLine = lineTo - 1 - else: - endLine = lineTo - - 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.text(line).strip().startswith(commentStr): - continue - - if Preferences.getEditor("CommentColumn0"): - self.setSelection(line, 0, line, len(commentStr)) - else: - self.setSelection(line, self.indentation(line), - line, self.indentation(line) + 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() - if indexTo == 0: - endLine = lineTo - 1 - else: - endLine = lineTo - - 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() - - if indexTo == 0: - endLine = lineTo - 1 - else: - endLine = lineTo - - 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): - """ - Public slot to jump to the beginning of a line. - - @param line line number to go to (integer) - """ - self.setCursorPosition(line - 1, 0) - self.ensureVisible(line) - - ############################################################################ - ## 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.Prefs.settings, "Scintilla") - self.lexer_.initProperties() - - # initialize the auto indent style of the lexer - ais = self.lexer_.autoIndentStyle() - - # read the typing completer settings - if self.completer is not None: - self.completer.readSettings() - - # set the margins layout - if QSCINTILLA_VERSION() >= 0x020301: - self.__unifiedMargins = Preferences.getEditor("UnifiedMargins") - - # set the line marker colours - self.__setLineMarkerColours() - - # set the text display - self.__setTextDisplay() - - # set margin 0 and 2 configuration - self.__setMarginsDisplay() - - # set the autocompletion and calltips function - self.__setAutoCompletion() - 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() - - 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 - if self.__unifiedMargins: - margin1Mask = (1 << self.breakpoint) | \ - (1 << self.cbreakpoint) | \ - (1 << self.tbreakpoint) | \ - (1 << self.tcbreakpoint) | \ - (1 << self.dbreakpoint) | \ - (1 << self.currentline) | \ - (1 << self.errorline) | \ - (1 << self.bookmark) | \ - (1 << self.syntaxerror) | \ - (1 << self.notcovered) | \ +# -*- coding: utf-8 -*- + +# Copyright (c) 2002 - 2010 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the editor component of the eric5 IDE. +""" + +import os +import re +import types + +from PyQt4.Qsci import QsciScintilla, QsciMacro +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from E5Gui.E5Application import e5App + +from . import Exporters +from . import Lexers +from . import TypingCompleters +from .QsciScintillaCompat import QsciScintillaCompat, QSCINTILLA_VERSION +from .SpellChecker import SpellChecker +from .SpellCheckingDialog import SpellCheckingDialog + +from Debugger.EditBreakpointDialog import EditBreakpointDialog + +from DebugClients.Python3.coverage import coverage + +from DataViews.CodeMetricsDialog import CodeMetricsDialog +from DataViews.PyCoverageDialog import PyCoverageDialog +from DataViews.PyProfileDialog import PyProfileDialog + +from .Printer import Printer + +import Preferences +import Utilities +from Utilities.py3flakes.checker import Checker +from Utilities.py3flakes.messages import ImportStarUsed + +import UI.PixmapCache + +EditorAutoCompletionListID = 1 +TemplateCompletionListID = 2 + +class Editor(QsciScintillaCompat): + """ + Class implementing the editor component of the eric5 IDE. + + @signal modificationStatusChanged(boolean, editor) emitted when the + modification status has changed + @signal undoAvailable(boolean) emitted to signal the undo availability + @signal redoAvailable(boolean) emitted to signal the redo availability + @signal cursorChanged(string, int, int) emitted when the cursor position + was changed + @signal editorAboutToBeSaved(string) emitted before the editor is saved + @signal editorSaved(string) emitted after the editor has been saved + @signal editorRenamed(string) emitted after the editor got a new name + (i.e. after a 'Save As') + @signal captionChanged(string, editor) emitted when the caption is + updated. Typically due to a readOnly attribute change. + @signal breakpointToggled(editor) emitted when a breakpoint is toggled + @signal bookmarkToggled(editor) emitted when a bookmark is toggled + @signal syntaxerrorToggled(editor) emitted when a syntax error was discovered + @signal autoCompletionAPIsAvailable(avail) emitted after the autocompletion + function has been configured + @signal coverageMarkersShown(boolean) emitted after the coverage markers have been + shown or cleared + @signal taskMarkersUpdated(editor) emitted when the task markers were updated + @signal showMenu(string, QMenu, editor) 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(language) emitted when the editors language was set. The + language is passed as a parameter. + @signal eolChanged(eol) emitted when the editors eol type was set. The eol string + is passed as a parameter. + @signal encodingChanged(encoding) emitted when the editors encoding was set. The + encoding name is passed as a parameter. + """ + ClassID = 1 + ClassProtectedID = 2 + ClassPrivateID = 3 + MethodID = 4 + MethodProtectedID = 5 + MethodPrivateID = 6 + AttributeID = 7 + AttributeProtectedID = 8 + AttributePrivateID = 9 + EnumID = 10 + + FromDocumentID = 99 + + TemplateImageID = 100 + + def __init__(self, dbs, fn = None, vm = None, + filetype = "", editor = None, tv = None): + """ + Constructor + + @param dbs reference to the debug server object + @param fn name of the file to be opened (string). If it is None, + a new (empty) editor is opened + @param vm reference to the view manager object (ViewManager.ViewManager) + @param filetype type of the source file (string) + @param editor reference to an Editor object, if this is a cloned view + @param tv reference to the task viewer object + """ + QsciScintillaCompat.__init__(self) + self.setAttribute(Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WA_KeyCompression) + self.setUtf8(True) + + self.pyExtensions = dbs.getExtensions('Python') + self.py3Extensions = dbs.getExtensions('Python3') + self.rbExtensions = dbs.getExtensions('Ruby') + + self.dbs = dbs + self.taskViewer = tv + self.fileName = fn + self.vm = vm + self.filetype = filetype + self.noName = "" + + # 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: error message + self.warnings = {} # key: marker handle + # value: warning message + self.notcoveredMarkers = [] # just a list of marker handles + + self.condHistory = [] + self.lexer_ = None + self.__lexerReset = False + self.completer = None + self.encoding = Preferences.getEditor("DefaultEncoding") + self.apiLanguage = '' + 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.__hasTaskMarkers = False # no task markers present + + self.macros = {} # list of defined macros + self.curMacro = None + self.recording = False + + self.acAPI = False + + # list of clones + self.__clones = [] + + # clear QScintilla defined keyboard commands + # we do our own handling through the view manager + self.clearAlternateKeys() + self.clearKeys() + + # initialise the mark occurrences timer + self.__markOccurrencesTimer = QTimer(self) + self.__markOccurrencesTimer.setSingleShot(True) + self.__markOccurrencesTimer.setInterval( + Preferences.getEditor("MarkOccurrencesTimeout")) + self.connect(self.__markOccurrencesTimer, SIGNAL("timeout()"), + self.__markOccurrences) + self.__markedText = "" + + # initialise some spellchecking stuff + self.spell = None + self.lastLine = 0 + self.lastIndex = 0 + + self.connect(self, SIGNAL('modificationChanged(bool)'), + self.__modificationChanged) + self.connect(self, SIGNAL('cursorPositionChanged(int,int)'), + self.__cursorPositionChanged) + self.connect(self, SIGNAL('modificationAttempted()'), + self.__modificationReadOnly) + self.connect(self, SIGNAL('userListActivated(int, const QString)'), + self.__completionListSelected) + + # margins layout + if QSCINTILLA_VERSION() >= 0x020301: + self.__unifiedMargins = Preferences.getEditor("UnifiedMargins") + else: + self.__unifiedMargins = True + + # define the margins markers + self.breakpoint = \ + self.markerDefine(UI.PixmapCache.getPixmap("break.png")) + self.cbreakpoint = \ + self.markerDefine(UI.PixmapCache.getPixmap("cBreak.png")) + self.tbreakpoint = \ + self.markerDefine(UI.PixmapCache.getPixmap("tBreak.png")) + self.tcbreakpoint = \ + self.markerDefine(UI.PixmapCache.getPixmap("tCBreak.png")) + self.dbreakpoint = \ + self.markerDefine(UI.PixmapCache.getPixmap("breakDisabled.png")) + self.bookmark = \ + self.markerDefine(UI.PixmapCache.getPixmap("bookmark.png")) + self.syntaxerror = \ + self.markerDefine(UI.PixmapCache.getPixmap("syntaxError.png")) + self.notcovered = \ + self.markerDefine(UI.PixmapCache.getPixmap("notcovered.png")) + self.taskmarker = \ + self.markerDefine(UI.PixmapCache.getPixmap("task.png")) + self.warning = \ + self.markerDefine(UI.PixmapCache.getPixmap("warning.png")) + + # define the line markers + self.currentline = self.markerDefine(QsciScintilla.Background) + self.errorline = self.markerDefine(QsciScintilla.Background) + self.__setLineMarkerColours() + + self.breakpointMask = (1 << self.breakpoint) | \ + (1 << self.cbreakpoint) | \ + (1 << self.tbreakpoint) | \ + (1 << self.tcbreakpoint) | \ + (1 << self.dbreakpoint) + + # configure the margins + self.__setMarginsDisplay() + + self.connect(self, SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'), + self.__marginClicked) + + # set the eol mode + self.__setEolMode() + + self.isResourcesFile = False + if editor is None: + if self.fileName is not None: + if (QFileInfo(self.fileName).size() // 1024) > \ + Preferences.getEditor("WarnFilesize"): + res = QMessageBox.warning(None, + self.trUtf8("Open File"), + self.trUtf8("""<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, + QFileInfo(self.fileName).size() // 1024), + QMessageBox.StandardButtons(\ + QMessageBox.No | \ + QMessageBox.Yes), + QMessageBox.No) + if res == QMessageBox.No or res == QMessageBox.Cancel: + raise IOError() + self.readFile(self.fileName, True) + bindName = self.__bindName(self.text(0)) + self.__bindLexer(bindName) + self.__bindCompleter(bindName) + self.__autoSyntaxCheck() + self.isResourcesFile = self.fileName.endswith(".qrc") + + 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.isResourcesFile = editor.isResourcesFile + self.lastModified = editor.lastModified + + self.addClone(editor) + editor.addClone(self) + + self.gotoLine(0) + + # set the text display + self.__setTextDisplay() + + # set the autocompletion and calltips function + self.__acHookFunction = None + self.__setAutoCompletion() + self.__ctHookFunction = None + self.__setCallTips() + + sh = self.sizeHint() + if sh.height() < 300: + sh.setHeight(300) + self.resize(sh) + + # Make sure tabbing through a QWorkspace works. + self.setFocusPolicy(Qt.StrongFocus) + + self.__updateReadOnly(True) + + self.setWhatsThis(self.trUtf8( + """<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 + + self.__initContextMenu() + self.__initContextMenuMargins() + + self.__checkEol() + if editor is None: + self.__checkLanguage() + self.__checkEncoding() + else: + # it's a clone + self.languageChanged(editor.apiLanguage, propagate = False) + self.__encodingChanged(editor.encoding, propagate = False) + + self.coverageMarkersShown = False # flag remembering the current status of the + # code coverage markers + + self.setAcceptDrops(True) + + # breakpoint handling + self.breakpointModel = self.dbs.getBreakPointModel() + self.__restoreBreakpoints() + self.connect(self.breakpointModel, + SIGNAL("rowsAboutToBeRemoved(const QModelIndex &, int, int)"), + self.__deleteBreakPoints) + self.connect(self.breakpointModel, + SIGNAL("dataAboutToBeChanged(const QModelIndex &, const QModelIndex &)"), + self.__breakPointDataAboutToBeChanged) + self.connect(self.breakpointModel, + SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), + self.__changeBreakPoints) + self.connect(self.breakpointModel, + SIGNAL("rowsInserted(const QModelIndex &, int, int)"), + self.__addBreakPoints) + self.connect(self, SIGNAL("linesChanged()"), self.__linesChanged) + + # 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() + + def __registerImages(self): + """ + Private method to register images for autocompletion lists. + """ + self.registerImage(self.ClassID, + UI.PixmapCache.getPixmap("class.png")) + self.registerImage(self.ClassProtectedID, + UI.PixmapCache.getPixmap("class_protected.png")) + self.registerImage(self.ClassPrivateID, + UI.PixmapCache.getPixmap("class_private.png")) + self.registerImage(self.MethodID, + UI.PixmapCache.getPixmap("method.png")) + self.registerImage(self.MethodProtectedID, + UI.PixmapCache.getPixmap("method_protected.png")) + self.registerImage(self.MethodPrivateID, + UI.PixmapCache.getPixmap("method_private.png")) + self.registerImage(self.AttributeID, + UI.PixmapCache.getPixmap("attribute.png")) + self.registerImage(self.AttributeProtectedID, + UI.PixmapCache.getPixmap("attribute_protected.png")) + self.registerImage(self.AttributePrivateID, + UI.PixmapCache.getPixmap("attribute_private.png")) + self.registerImage(self.EnumID, + UI.PixmapCache.getPixmap("enum.png")) + + self.registerImage(self.FromDocumentID, + UI.PixmapCache.getPixmap("editor.png")) + + self.registerImage(self.TemplateImageID, + UI.PixmapCache.getPixmap("templateViewer.png")) + + def addClone(self, editor): + """ + Public method to add a clone to our list. + + @param clone reference to the cloned editor (Editor) + """ + self.__clones.append(editor) + + self.connect(editor, SIGNAL('editorRenamed'), self.fileRenamed) + self.connect(editor, SIGNAL('languageChanged'), self.languageChanged) + self.connect(editor, SIGNAL('eolChanged'), self.__eolChanged) + self.connect(editor, SIGNAL('encodingChanged'), self.__encodingChanged) + + def removeClone(self, editor): + """ + Public method to remove a clone from our list. + + @param clone reference to the cloned editor (Editor) + """ + if editor in self.__clones: + self.disconnect(editor, SIGNAL('editorRenamed'), self.fileRenamed) + self.disconnect(editor, SIGNAL('languageChanged'), self.languageChanged) + self.disconnect(editor, SIGNAL('eolChanged'), self.__eolChanged) + self.disconnect(editor, SIGNAL('encodingChanged'), self.__encodingChanged) + self.__clones.remove(editor) + + 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) + """ + bindName = self.fileName + + if line0.startswith("<?xml"): + # override extension for XML files + bindName = "dummy.xml" + + # check filetype + if self.filetype == "Python": + bindName = "dummy.py" + elif self.filetype == "Ruby": + bindName = "dummy.rb" + elif self.filetype == "D": + bindName = "dummy.d" + elif self.filetype == "Properties": + bindName = "dummy.ini" + + # #! marker detection + if line0.startswith("#!"): + if "python3" in line0: + bindName = "dummy.py" + self.filetype = "Python3" + elif "python2" in line0: + bindName = "dummy.py" + self.filetype = "Python" + elif "python" in line0: + bindName = "dummy.py" + self.filetype = "Python" + 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" + 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.showMenu = self.__initContextMenuShow() + self.graphicsMenu = self.__initContextMenuGraphics() + self.autocompletionMenu = self.__initContextMenuAutocompletion() + self.__menus["Checks"] = self.checksMenu + self.__menus["Show"] = self.showMenu + self.__menus["Graphics"] = self.graphicsMenu + self.__menus["Autocompletion"] = self.autocompletionMenu + self.exportersMenu = self.__initContextMenuExporters() + self.__menus["Exporters"] = self.exportersMenu + self.eolMenu = self.__initContextMenuEol() + self.__menus["Eol"] = self.eolMenu + self.encodingsMenu = self.__initContextMenuEncodings() + self.__menus["Encodings"] = self.encodingsMenu + + self.menuActs["Undo"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("editUndo.png"), + self.trUtf8('Undo'), self.undo) + self.menuActs["Redo"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("editRedo.png"), + self.trUtf8('Redo'), self.redo) + self.menuActs["Revert"] = \ + self.menu.addAction(self.trUtf8("Revert to last saved state"), + self.revertToUnmodified) + self.menu.addSeparator() + self.menuActs["Cut"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("editCut.png"), + self.trUtf8('Cut'), self.cut) + self.menuActs["Copy"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("editCopy.png"), + self.trUtf8('Copy'), self.copy) + self.menu.addAction(UI.PixmapCache.getIcon("editPaste.png"), + self.trUtf8('Paste'), self.paste) + if not self.miniMenu: + self.menu.addSeparator() + self.menu.addAction(UI.PixmapCache.getIcon("editIndent.png"), + self.trUtf8('Indent'), self.indentLineOrSelection) + self.menu.addAction(UI.PixmapCache.getIcon("editUnindent.png"), + self.trUtf8('Unindent'), self.unindentLineOrSelection) + self.menuActs["Comment"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("editComment.png"), + self.trUtf8('Comment'), self.commentLineOrSelection) + self.menuActs["Uncomment"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("editUncomment.png"), + self.trUtf8('Uncomment'), self.uncommentLineOrSelection) + self.menuActs["StreamComment"] = \ + self.menu.addAction(self.trUtf8('Stream Comment'), + self.streamCommentLineOrSelection) + self.menuActs["BoxComment"] = \ + self.menu.addAction(self.trUtf8('Box Comment'), + self.boxCommentLineOrSelection) + self.menu.addSeparator() + self.menu.addAction(self.trUtf8('Select to brace'), + self.selectToMatchingBrace) + self.menu.addAction(self.trUtf8('Select all'), self.__selectAll) + self.menu.addAction(self.trUtf8('Deselect all'), self.__deselectAll) + self.menu.addSeparator() + self.menuActs["SpellCheck"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("spellchecking.png"), + self.trUtf8('Check spelling...'), self.checkSpelling) + self.menuActs["SpellCheckSelection"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("spellchecking.png"), + self.trUtf8('Check spelling of selection...'), + self.__checkSpellingSelection) + self.menuActs["SpellCheckRemove"] = \ + self.menu.addAction(self.trUtf8("Remove from dictionary"), + self.__removeFromSpellingDictionary) + self.menu.addSeparator() + self.menu.addAction(self.trUtf8('Shorten empty lines'), + self.shortenEmptyLines) + 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.trUtf8("Use Monospaced Font"), + self.handleMonospacedEnable) + self.menuActs["MonospacedFont"].setCheckable(True) + self.menuActs["MonospacedFont"].setChecked(self.useMonospaced) + self.menuActs["AutosaveEnable"] = \ + self.menu.addAction(self.trUtf8("Autosave enabled"), + self.__autosaveEnable) + self.menuActs["AutosaveEnable"].setCheckable(True) + self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled) + self.menuActs["TypingAidsEnabled"] = \ + self.menu.addAction(self.trUtf8("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.trUtf8("Autocompletion 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.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.showMenu) + self.menu.addSeparator() + self.menuActs["Diagrams"] = self.menu.addMenu(self.graphicsMenu) + self.menu.addSeparator() + self.menu.addAction(self.trUtf8('New view'), self.__newView) + act = self.menu.addAction(self.trUtf8('New view (with new split)'), + self.__newViewNewSplit) + if not self.vm.canSplit(): + act.setEnabled(False) + self.menu.addAction(UI.PixmapCache.getIcon("close.png"), + self.trUtf8('Close'), self.__contextClose) + self.menu.addSeparator() + self.menuActs["Save"] = \ + self.menu.addAction(UI.PixmapCache.getIcon("fileSave.png"), + self.trUtf8('Save'), self.__contextSave) + self.menu.addAction(UI.PixmapCache.getIcon("fileSaveAs.png"), + self.trUtf8('Save As...'), self.__contextSaveAs) + if not self.miniMenu: + self.menu.addMenu(self.exportersMenu) + self.menu.addSeparator() + self.menu.addAction(UI.PixmapCache.getIcon("printPreview.png"), + self.trUtf8("Print Preview"), self.printPreviewFile) + self.menu.addAction(UI.PixmapCache.getIcon("print.png"), + self.trUtf8('Print'), self.printFile) + + self.connect(self.menu, SIGNAL('aboutToShow()'), self.__showContextMenu) + + self.spellingMenu = QMenu() + self.__menus["Spelling"] = self.spellingMenu + + self.connect(self.spellingMenu, SIGNAL('aboutToShow()'), + self.__showContextMenuSpelling) + self.connect(self.spellingMenu, SIGNAL('triggered(QAction *)'), + self.__contextMenuSpellingTriggered) + + def __initContextMenuAutocompletion(self): + """ + Private method used to setup the Checks context sub menu. + """ + menu = QMenu(self.trUtf8('Autocomplete')) + + self.menuActs["acDynamic"] = \ + menu.addAction(self.trUtf8('dynamic'), + self.autoComplete) + menu.addSeparator() + menu.addAction(self.trUtf8('from Document'), + self.autoCompleteFromDocument) + self.menuActs["acAPI"] = \ + menu.addAction(self.trUtf8('from APIs'), + self.autoCompleteFromAPIs) + self.menuActs["acAPIDocument"] = \ + menu.addAction(self.trUtf8('from Document and APIs'), + self.autoCompleteFromAll) + menu.addSeparator() + self.menuActs["calltip"] = \ + menu.addAction(self.trUtf8('Calltip'), self.callTip) + + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuAutocompletion) + + return menu + + def __initContextMenuChecks(self): + """ + Private method used to setup the Checks context sub menu. + """ + menu = QMenu(self.trUtf8('Check')) + self.connect(menu, SIGNAL("aboutToShow()"), self.__showContextMenuChecks) + return menu + + def __initContextMenuShow(self): + """ + Private method used to setup the Show context sub menu. + """ + menu = QMenu(self.trUtf8('Show')) + + menu.addAction(self.trUtf8('Code metrics...'), self.__showCodeMetrics) + self.coverageMenuAct = \ + menu.addAction(self.trUtf8('Code coverage...'), self.__showCodeCoverage) + self.coverageShowAnnotationMenuAct = \ + menu.addAction(self.trUtf8('Show code coverage annotations'), + self.__codeCoverageShowAnnotations) + self.coverageHideAnnotationMenuAct = \ + menu.addAction(self.trUtf8('Hide code coverage annotations'), + self.__codeCoverageHideAnnotations) + self.profileMenuAct = \ + menu.addAction(self.trUtf8('Profile data...'), self.__showProfileData) + + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuShow) + + return menu + + def __initContextMenuGraphics(self): + """ + Private method used to setup the diagrams context sub menu. + """ + menu = QMenu(self.trUtf8('Diagrams')) + + menu.addAction(self.trUtf8('Class Diagram...'), + self.__showClassDiagram) + menu.addAction(self.trUtf8('Package Diagram...'), + self.__showPackageDiagram) + menu.addAction(self.trUtf8('Imports Diagram...'), + self.__showImportsDiagram) + self.applicationDiagramMenuAct = \ + menu.addAction(self.trUtf8('Application Diagram...'), + self.__showApplicationDiagram) + + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuGraphics) + + return menu + + def __initContextMenuLanguages(self): + """ + Private method used to setup the Languages context sub menu. + """ + menu = QMenu(self.trUtf8("Languages")) + + self.languagesActGrp = QActionGroup(self) + self.noLanguageAct = menu.addAction(self.trUtf8("No Language")) + self.noLanguageAct.setCheckable(True) + self.noLanguageAct.setData("None") + self.languagesActGrp.addAction(self.noLanguageAct) + menu.addSeparator() + + self.supportedLanguages = {} + supportedLanguages = Lexers.getSupportedLanguages() + languages = sorted(list(supportedLanguages.keys())) + for language in languages: + if language != "Guessed": + self.supportedLanguages[language] = supportedLanguages[language][:] + act = menu.addAction(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.trUtf8("Guessed")) + self.pygmentsAct.setCheckable(True) + self.pygmentsAct.setData("Guessed") + self.languagesActGrp.addAction(self.pygmentsAct) + self.pygmentsSelAct = menu.addAction(self.trUtf8("Alternatives")) + self.pygmentsSelAct.setData("Alternatives") + + self.connect(menu, SIGNAL('triggered(QAction *)'), self.__languageMenuTriggered) + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuLanguages) + + return menu + + def __initContextMenuEncodings(self): + """ + Private method used to setup the Encodings context sub menu. + """ + self.supportedEncodings = {} + + menu = QMenu(self.trUtf8("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) + + self.connect(menu, SIGNAL('triggered(QAction *)'), self.__encodingsMenuTriggered) + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuEncodings) + + return menu + + def __initContextMenuEol(self): + """ + Private method to setup the eol context sub menu. + """ + self.supportedEols = {} + + menu = QMenu(self.trUtf8("End-of-Line Type")) + + self.eolActGrp = QActionGroup(self) + + act = menu.addAction(self.trUtf8("Unix")) + act.setCheckable(True) + act.setData('\n') + self.supportedEols['\n'] = act + self.eolActGrp.addAction(act) + + act = menu.addAction(self.trUtf8("Windows")) + act.setCheckable(True) + act.setData('\r\n') + self.supportedEols['\r\n'] = act + self.eolActGrp.addAction(act) + + act = menu.addAction(self.trUtf8("Macintosh")) + act.setCheckable(True) + act.setData('\r') + self.supportedEols['\r'] = act + self.eolActGrp.addAction(act) + + self.connect(menu, SIGNAL('triggered(QAction *)'), self.__eolMenuTriggered) + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuEol) + + return menu + + def __initContextMenuExporters(self): + """ + Private method used to setup the Exporters context sub menu. + """ + menu = QMenu(self.trUtf8("Export as")) + + supportedExporters = Exporters.getSupportedFormats() + exporters = sorted(list(supportedExporters.keys())) + for exporter in exporters: + act = menu.addAction(supportedExporters[exporter]) + act.setData(exporter) + + self.connect(menu, SIGNAL('triggered(QAction *)'), self.__exportMenuTriggered) + + return menu + + def __initContextMenuMargins(self): + """ + Private method used to setup the context menu for the margins + """ + self.marginMenuActs = {} + + if self.__unifiedMargins: + self.__initContextMenuUnifiedMargins() + else: + self.__initContextMenuSeparateMargins() + + def __initContextMenuSeparateMargins(self): + """ + Private method used to setup the context menu for the separated margins + """ + # bookmark margin + self.bmMarginMenu = QMenu() + + self.bmMarginMenu.addAction(self.trUtf8('Toggle bookmark'), + self.menuToggleBookmark) + self.marginMenuActs["NextBookmark"] = \ + self.bmMarginMenu.addAction(self.trUtf8('Next bookmark'), + self.nextBookmark) + self.marginMenuActs["PreviousBookmark"] = \ + self.bmMarginMenu.addAction(self.trUtf8('Previous bookmark'), + self.previousBookmark) + self.marginMenuActs["ClearBookmark"] = \ + self.bmMarginMenu.addAction(self.trUtf8('Clear all bookmarks'), + self.clearBookmarks) + + self.connect(self.bmMarginMenu, SIGNAL('aboutToShow()'), + self.__showContextMenuMargin) + + # breakpoint margin + self.bpMarginMenu = QMenu() + + self.marginMenuActs["Breakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Toggle breakpoint'), + self.menuToggleBreakpoint) + self.marginMenuActs["TempBreakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Toggle temporary breakpoint'), + self.__menuToggleTemporaryBreakpoint) + self.marginMenuActs["EditBreakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Edit breakpoint...'), + self.menuEditBreakpoint) + self.marginMenuActs["EnableBreakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Enable breakpoint'), + self.__menuToggleBreakpointEnabled) + self.marginMenuActs["NextBreakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Next breakpoint'), + self.menuNextBreakpoint) + self.marginMenuActs["PreviousBreakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Previous breakpoint'), + self.menuPreviousBreakpoint) + self.marginMenuActs["ClearBreakpoint"] = \ + self.bpMarginMenu.addAction(self.trUtf8('Clear all breakpoints'), + self.__menuClearBreakpoints) + + self.connect(self.bpMarginMenu, SIGNAL('aboutToShow()'), + self.__showContextMenuMargin) + + # indicator margin + self.indicMarginMenu = QMenu() + + self.marginMenuActs["GotoSyntaxError"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Goto syntax error'), + self.gotoSyntaxError) + self.marginMenuActs["ShowSyntaxError"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Show syntax error message'), + self.__showSyntaxError) + self.marginMenuActs["ClearSyntaxError"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Clear syntax error'), + self.clearSyntaxError) + self.indicMarginMenu.addSeparator() + self.marginMenuActs["NextWarningMarker"] = \ + self.indicMarginMenu.addAction(self.trUtf8("Next warning"), + self.nextFlakesWarning) + self.marginMenuActs["PreviousWarningMarker"] = \ + self.indicMarginMenu.addAction(self.trUtf8("Previous warning"), + self.previousFlakesWarning) + self.marginMenuActs["ShowWarning"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Show warning message'), + self.__showFlakesWarning) + self.marginMenuActs["ClearWarnings"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Clear warnings'), + self.clearFlakesWarnings) + self.indicMarginMenu.addSeparator() + self.marginMenuActs["NextCoverageMarker"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Next uncovered line'), + self.nextUncovered) + self.marginMenuActs["PreviousCoverageMarker"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Previous uncovered line'), + self.previousUncovered) + self.indicMarginMenu.addSeparator() + self.marginMenuActs["NextTaskMarker"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Next task'), + self.nextTask) + self.marginMenuActs["PreviousTaskMarker"] = \ + self.indicMarginMenu.addAction(self.trUtf8('Previous task'), + self.previousTask) + + self.connect(self.indicMarginMenu, SIGNAL('aboutToShow()'), + self.__showContextMenuMargin) + + def __initContextMenuUnifiedMargins(self): + """ + Private method used to setup the context menu for the unified margins + """ + self.marginMenu = QMenu() + + self.marginMenu.addAction(self.trUtf8('Toggle bookmark'), + self.menuToggleBookmark) + self.marginMenuActs["NextBookmark"] = \ + self.marginMenu.addAction(self.trUtf8('Next bookmark'), + self.nextBookmark) + self.marginMenuActs["PreviousBookmark"] = \ + self.marginMenu.addAction(self.trUtf8('Previous bookmark'), + self.previousBookmark) + self.marginMenuActs["ClearBookmark"] = \ + self.marginMenu.addAction(self.trUtf8('Clear all bookmarks'), + self.clearBookmarks) + self.marginMenu.addSeparator() + self.marginMenuActs["GotoSyntaxError"] = \ + self.marginMenu.addAction(self.trUtf8('Goto syntax error'), + self.gotoSyntaxError) + self.marginMenuActs["ShowSyntaxError"] = \ + self.marginMenu.addAction(self.trUtf8('Show syntax error message'), + self.__showSyntaxError) + self.marginMenuActs["ClearSyntaxError"] = \ + self.marginMenu.addAction(self.trUtf8('Clear syntax error'), + self.clearSyntaxError) + self.marginMenu.addSeparator() + self.marginMenuActs["NextWarningMarker"] = \ + self.marginMenu.addAction(self.trUtf8("Next warning"), + self.nextFlakesWarning) + self.marginMenuActs["PreviousWarningMarker"] = \ + self.marginMenu.addAction(self.trUtf8("Previous warning"), + self.previousFlakesWarning) + self.marginMenuActs["ShowWarning"] = \ + self.marginMenu.addAction(self.trUtf8('Show warning message'), + self.__showFlakesWarning) + self.marginMenuActs["ClearWarnings"] = \ + self.marginMenu.addAction(self.trUtf8('Clear warnings'), + self.clearFlakesWarnings) + self.marginMenu.addSeparator() + self.marginMenuActs["Breakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Toggle breakpoint'), + self.menuToggleBreakpoint) + self.marginMenuActs["TempBreakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Toggle temporary breakpoint'), + self.__menuToggleTemporaryBreakpoint) + self.marginMenuActs["EditBreakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Edit breakpoint...'), + self.menuEditBreakpoint) + self.marginMenuActs["EnableBreakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Enable breakpoint'), + self.__menuToggleBreakpointEnabled) + self.marginMenuActs["NextBreakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Next breakpoint'), + self.menuNextBreakpoint) + self.marginMenuActs["PreviousBreakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Previous breakpoint'), + self.menuPreviousBreakpoint) + self.marginMenuActs["ClearBreakpoint"] = \ + self.marginMenu.addAction(self.trUtf8('Clear all breakpoints'), + self.__menuClearBreakpoints) + self.marginMenu.addSeparator() + self.marginMenuActs["NextCoverageMarker"] = \ + self.marginMenu.addAction(self.trUtf8('Next uncovered line'), + self.nextUncovered) + self.marginMenuActs["PreviousCoverageMarker"] = \ + self.marginMenu.addAction(self.trUtf8('Previous uncovered line'), + self.previousUncovered) + self.marginMenu.addSeparator() + self.marginMenuActs["NextTaskMarker"] = \ + self.marginMenu.addAction(self.trUtf8('Next task'), + self.nextTask) + self.marginMenuActs["PreviousTaskMarker"] = \ + self.marginMenu.addAction(self.trUtf8('Previous task'), + self.previousTask) + self.marginMenu.addSeparator() + self.marginMenuActs["LMBbookmarks"] = \ + self.marginMenu.addAction(self.trUtf8('LMB toggles bookmarks'), + self.__lmBbookmarks) + self.marginMenuActs["LMBbookmarks"].setCheckable(True) + self.marginMenuActs["LMBbookmarks"].setChecked(False) + self.marginMenuActs["LMBbreakpoints"] = \ + self.marginMenu.addAction(self.trUtf8('LMB toggles breakpoints'), + self.__lmBbreakpoints) + self.marginMenuActs["LMBbreakpoints"].setCheckable(True) + self.marginMenuActs["LMBbreakpoints"].setChecked(True) + + self.connect(self.marginMenu, SIGNAL('aboutToShow()'), + self.__showContextMenuMargin) + + def __exportMenuTriggered(self, act): + """ + Private method to handle the selection of an export format. + + @param act reference to the action that was triggered (QAction) + """ + exporterFormat = act.data() + self.exportFile(exporterFormat) + + def exportFile(self, exporterFormat): + """ + Public method to export the file. + + @param exporterFormat format the file should be exported into (string) + """ + if exporterFormat: + exporter = Exporters.getExporter(exporterFormat, self) + if exporter: + exporter.exportSource() + else: + QMessageBox.critical(self, + self.trUtf8("Export source"), + self.trUtf8("""<p>No exporter available for the """ + """export format <b>{0}</b>. Aborting...</p>""")\ + .format(exporterFormat), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + else: + QMessageBox.critical(self, + self.trUtf8("Export source"), + self.trUtf8("""No export format given. Aborting..."""), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + + def __showContextMenuLanguages(self): + """ + Private slot handling the aboutToShow signal of the languages context menu. + """ + if self.apiLanguage.startswith("Pygments|"): + self.pygmentsSelAct.setText( + self.trUtf8("Alternatives ({0})").format(self.getLanguage())) + else: + self.pygmentsSelAct.setText(self.trUtf8("Alternatives")) + self.emit(SIGNAL("showMenu"), "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([l[0] for l in get_all_lexers()]) + try: + lexerSel = lexerList.index(self.getLanguage()) + except ValueError: + lexerSel = 0 + lexerName, ok = QInputDialog.getItem(\ + self, + self.trUtf8("Pygments Lexer"), + self.trUtf8("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.setLanguage(self.supportedLanguages[language][1]) + + def languageChanged(self, language, propagate = True): + """ + Public slot handling a change of a connected editor's language. + + @param language language to be set (string) + @keyparam propagate flag indicating to propagate the change (boolean) + """ + if language == '': + self.__resetLanguage(propagate = propagate) + elif language == "Guessed": + self.setLanguage("dummy.pygments") + elif language.startswith("Pygments|"): + pyname = language.split("|", 1)[1] + self.setLanguage("dummy.pygments", pyname = pyname) + else: + self.setLanguage(self.supportedLanguages[language][1], propagate = propagate) + + def __resetLanguage(self, propagate = True): + """ + Private method used to reset the language selection. + + @keyparam 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): + self.disconnect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded) + + self.apiLanguage = "" + self.lexer_ = None + self.__lexerReset = True + self.setLexer() + self.setMonospaced(self.useMonospaced) + if self.completer is not None: + self.completer.setEnabled(False) + self.completer = None + self.__setTextDisplay() + + if not self.inLanguageChanged and propagate: + self.inLanguageChanged = True + self.emit(SIGNAL('languageChanged'), 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) + @keyparam propagate flag indicating to propagate the change (boolean) + @keyparam pyname name of the pygments lexer to use (string) + """ + self.__lexerReset = False + self.__bindLexer(filename, pyname = pyname) + self.__bindCompleter(filename) + self.recolor() + self.__checkLanguage() + + # set the text display + if initTextDisplay: + self.__setTextDisplay() + + # set the autocompletion and calltips function + self.__setAutoCompletion() + self.__setCallTips() + + if not self.inLanguageChanged and propagate: + self.inLanguageChanged = True + self.emit(SIGNAL('languageChanged'), 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.emit(SIGNAL("showMenu"), "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.__encodingChanged("%s-selected" % encoding) + + def __checkEncoding(self): + """ + Private method to check the selected encoding of the encodings submenu. + """ + try: + self.supportedEncodings[self.__normalizedEncoding()].setChecked(True) + except (AttributeError, KeyError): + pass + + def __encodingChanged(self, encoding, propagate = True): + """ + Private slot to handle a change of the encoding. + + @keyparam propagate flag indicating to propagate the change (boolean) + """ + self.encoding = encoding + self.__checkEncoding() + + if not self.inEncodingChanged and propagate: + self.inEncodingChanged = True + self.emit(SIGNAL("encodingChanged"), self.encoding) + self.inEncodingChanged = False + + def __normalizedEncoding(self): + """ + Private method to calculate the normalized encoding string. + + @return normalized encoding (string) + """ + return self.encoding.replace("-default", "")\ + .replace("-guessed", "")\ + .replace("-selected", "") + + def __showContextMenuEol(self): + """ + Private slot handling the aboutToShow signal of the eol context menu. + """ + self.emit(SIGNAL("showMenu"), "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. + """ + try: + self.supportedEols[self.getLineSeparator()].setChecked(True) + except AttributeError: + pass + + 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.emit(SIGNAL("eolChanged"), eol) + self.inEolChanged = 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) + @keyparam 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.disconnect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded) + + language = "" + project = e5App().getObject("Project") + if project.isOpen() and project.isProjectFile(filename): + language = project.getEditorLexerAssoc(os.path.basename(filename)) + if not language: + filename = os.path.basename(filename) + language = Preferences.getEditorLexerAssoc(filename) + if language.startswith("Pygments|"): + pyname = language.split("|", 1)[1] + language = "" + + self.lexer_ = Lexers.getLexer(language, self, pyname = pyname) + if self.lexer_ is None: + self.setLexer() + self.apiLanguage = "" + return + + if pyname: + self.apiLanguage = "Pygments|%s" % pyname + else: + self.apiLanguage = self.lexer_.language() + if self.apiLanguage == "POV": + self.apiLanguage = "Povray" + self.setLexer(self.lexer_) + self.__setMarginsDisplay() + if self.lexer_.lexer() == "container" or self.lexer_.lexer() is None: + self.setStyleBits(self.lexer_.styleBitsNeeded()) + self.connect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded) + + # get the font for style 0 and set it as the default font + key = 'Scintilla/%s/style0/font' % self.lexer_.language() + fdesc = Preferences.Prefs.settings.value(key) + if fdesc is not None: + font = QFont(fdesc[0], int(fdesc[1])) + self.lexer_.setDefaultFont(font) + self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla") + + # now set the lexer properties + self.lexer_.initProperties() + + # initialize the auto indent style of the lexer + ais = self.lexer_.autoIndentStyle() + + # initialize the lexer APIs settings + api = self.vm.getAPIsManager().getAPIs(self.apiLanguage) + if api is not None: + self.lexer_.setAPIs(api.getQsciAPIs()) + self.acAPI = True + else: + self.acAPI = False + self.emit(SIGNAL("autoCompletionAPIsAvailable"), self.acAPI) + + 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): + """ + Public method to retrieve the language of the editor. + + @return language of the editor (string) + """ + if self.apiLanguage == "Guessed" or self.apiLanguage.startswith("Pygments|"): + return self.lexer_.name() + else: + 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) + + 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 self.fileName is not None: + self.lastModified = QFileInfo(self.fileName).lastModified() + if Preferences.getEditor("AutoCheckSyntax"): + self.clearSyntaxError() + self.emit(SIGNAL('modificationStatusChanged'), m, self) + self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable()) + self.emit(SIGNAL('redoAvailable'), 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.emit(SIGNAL('cursorChanged'), self.fileName, line+1, index) + + if Preferences.getEditor("MarkOccurrencesEnabled"): + self.__markOccurrencesTimer.stop() + self.__markOccurrencesTimer.start() + + 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) + + self.lastLine = line + self.lastIndex = index + + def __modificationReadOnly(self): + """ + Private slot to handle the modificationAttempted signal. + """ + QMessageBox.warning(None, + self.trUtf8("Modification of Read Only file"), + self.trUtf8("""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 getEncoding(self): + """ + Public method to return the current encoding. + + @return current encoding (string) + """ + return self.encoding + + def getFolds(self): + """ + Public method to get a list line numbers of collapsed folds. + + @return list of line numbers of folded lines (list of integer) + """ + line = 0 + folds = [] + maxline = self.lines() + while line < maxline: + if self.foldHeaderAt(line) and not self.foldExpandedAt(line): + folds.append(line) + line += 1 + return folds + + def isPyFile(self): + """ + Public method to return a flag indicating a Python file. + + @return flag indicating a Python file (boolean) + """ + return self.filetype == "Python" or \ + (self.fileName is not None and \ + os.path.splitext(self.fileName)[1] in self.pyExtensions) + + def isPy3File(self): + """ + Public method to return a flag indicating a Python3 file. + + @return flag indicating a Python3 file (boolean) + """ + return self.filetype == "Python3" or \ + (self.fileName is not None and \ + os.path.splitext(self.fileName)[1] in self.py3Extensions) + + def isRubyFile(self): + """ + Public method to return a flag indicating a Ruby file. + + @return flag indicating a Ruby file (boolean) + """ + return self.filetype == "Ruby" or \ + (self.fileName is not None and \ + os.path.splitext(self.fileName)[1] in self.rbExtensions) + + 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 __linesChanged(self): + """ + Private method to track text changes. + + This method checks, if lines have been inserted or removed in order to + update the breakpoints. + """ + if self.breaks: + bps = [] # list of breakpoints + for handle, (ln, cond, temp, enabled, ignorecount) in list(self.breaks.items()): + line = self.markerLine(handle) + 1 + bps.append((ln, line, (cond, temp, enabled, ignorecount))) + self.markerDeleteHandle(handle) + self.breaks = {} + for bp in bps: + index = self.breakpointModel.getBreakPointIndex(self.fileName, bp[0]) + self.breakpointModel.setBreakPointByIndex(index, + self.fileName, bp[1], bp[2]) + + 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) + + 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 indexes indexes of changed breakpoints. + """ + 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 linenumber of the breakpoint (integer) + """ + for handle, (ln, _, _, _, _) in list(self.breaks.items()): + if self.markerLine(handle) == line-1: + break + else: + # not found, simply ignore it + return + + del self.breaks[handle] + self.markerDeleteHandle(handle) + + def newBreakpointWithProperties(self, line, properties): + """ + Private 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 + + handle = self.markerAdd(line-1, marker) + self.breaks[handle] = (line,) + properties + self.emit(SIGNAL('breakpointToggled'), self) + + 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, (ln, _, _, _, _) in list(self.breaks.items()): + 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.emit(SIGNAL('breakpointToggled'), 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() or self.isPy3File() or self.isRubyFile()): + self.breakpointModel.addBreakPoint(self.fileName, line, + ('', temporary, True, 0)) + self.emit(SIGNAL('breakpointToggled'), self) + + def __toggleBreakpointEnabled(self, line): + """ + Private method to toggle a breakpoints enabled status. + + @param line line number of the breakpoint (integer) + """ + for handle, (ln, cond, temp, enabled, ignorecount) in list(self.breaks.items()): + if self.markerLine(handle) == line - 1: + break + else: + # no breakpoint found on that line + return + + index = self.breakpointModel.getBreakPointIndex(self.fileName, line) + self.breakpointModel.setBreakPointEnabledByIndex(index, not enabled) + + 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 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() + found = False + for handle, (ln, cond, temp, enabled, ignorecount) in list(self.breaks.items()): + if self.markerLine(handle) == self.line: + found = True + break + + if found: + index = self.breakpointModel.getBreakPointIndex(self.fileName, ln) + if not index.isValid(): + return + + dlg = EditBreakpointDialog((self.fileName, ln), + (cond, temp, enabled, ignorecount), + self.condHistory, self, modal = True) + if dlg.exec_() == QDialog.Accepted: + cond, temp, enabled, ignorecount = dlg.getData() + self.breakpointModel.setBreakPointByIndex(index, + self.fileName, ln, (cond, temp, enabled, ignorecount)) + + 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. + """ + idxList = [] + for handle, (ln, _, _, _, _) in list(self.breaks.items()): + 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: + break + else: + # set a new bookmark + handle = self.markerAdd(line - 1, self.bookmark) + self.bookmarks.append(handle) + self.emit(SIGNAL('bookmarkToggled'), self) + return + + self.bookmarks.remove(handle) + self.markerDeleteHandle(handle) + self.emit(SIGNAL('bookmarkToggled'), self) + + 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 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 = [] + self.emit(SIGNAL('bookmarkToggled'), self) + + ############################################################################ + ## Printing methods below + ############################################################################ + + def printFile(self): + """ + Public slot to print the text. + """ + printer = Printer(mode = QPrinter.HighResolution) + sb = e5App().getObject("UserInterface").statusBar() + printDialog = QPrintDialog(printer, self) + if self.hasSelectedText(): + printDialog.addEnabledOption(QAbstractPrintDialog.PrintSelection) + if printDialog.exec_() == QDialog.Accepted: + sb.showMessage(self.trUtf8('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.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.trUtf8('Printing completed'), 2000) + else: + sb.showMessage(self.trUtf8('Error while printing'), 2000) + QApplication.processEvents() + else: + sb.showMessage(self.trUtf8('Printing aborted'), 2000) + QApplication.processEvents() + + def printPreviewFile(self): + """ + Public slot to show a print preview of the text. + """ + from PyQt4.QtGui import QPrintPreviewDialog + + printer = Printer(mode = QPrinter.HighResolution) + fn = self.getFileName() + if fn is not None: + printer.setDocName(os.path.basename(fn)) + else: + printer.setDocName(self.noName) + preview = QPrintPreviewDialog(printer, self) + self.connect(preview, SIGNAL("paintRequested(QPrinter*)"), 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 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. + """ + todoMarkers = Preferences.getTasks("TasksMarkers").split() + bugfixMarkers = Preferences.getTasks("TasksMarkersBugfix").split() + 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 + lineIndex = -1 + for line in txtList: + lineIndex += 1 + shouldContinue = False + # normal tasks first + for tasksMarker in todoMarkers: + index = line.find(tasksMarker) + if index > -1: + task = line[index:] + self.markerAdd(lineIndex, self.taskmarker) + self.taskViewer.addFileTask(task, self.fileName, lineIndex + 1, False) + self.__hasTaskMarkers = True + shouldContinue = True + break + if shouldContinue: + continue + + # bugfix tasks second + for tasksMarker in bugfixMarkers: + index = line.find(tasksMarker) + if index > -1: + task = line[index:] + self.markerAdd(lineIndex, self.taskmarker) + self.taskViewer.addFileTask(task, self.fileName, lineIndex+1, True) + self.__hasTaskMarkers = True + break + self.emit(SIGNAL('taskMarkersUpdated'), self) + + ############################################################################ + ## 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 = QMessageBox.warning(self.vm, + self.trUtf8("File Modified"), + self.trUtf8("<p>The file <b>{0}</b> has unsaved changes.</p>") + .format(fn), + QMessageBox.StandardButtons(\ + QMessageBox.Abort | \ + QMessageBox.Discard | \ + QMessageBox.Save), + QMessageBox.Save) + if res == QMessageBox.Save: + ok, newName = self.saveFile() + if ok: + self.vm.setEditorName(self, newName) + return ok + elif res == QMessageBox.Abort or res == QMessageBox.Cancel: + return False + + 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): + """ + 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) + """ + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + + try: + if createIt and not os.path.exists(fn): + f = open(fn, "w") + f.close() + txt, self.encoding = Utilities.readEncodedFile(fn) + except (UnicodeDecodeError, IOError) as why: + QApplication.restoreOverrideCursor() + QMessageBox.critical(self.vm, self.trUtf8('Open File'), + self.trUtf8('<p>The file <b>{0}</b> could not be opened.</p>' + '<p>Reason: {1}</p>') + .format(fn, str(why))) + QApplication.restoreOverrideCursor() + raise + fileEol = self.detectEolString(txt) + + modified = False + if (not Preferences.getEditor("TabForIndentation")) and \ + Preferences.getEditor("ConvertTabsOnLoad") and \ + not (self.lexer_ and \ + self.lexer_.alwaysKeepTabs()): + txtExpanded = txt.expandtabs(Preferences.getEditor("TabWidth")) + if txtExpanded != txt: + modified = True + txt = txtExpanded + del txtExpanded + + self.setText(txt) + + # perform automatic eol conversion + if Preferences.getEditor("AutomaticEOLConversion"): + self.convertEols(self.eolMode()) + else: + self.setEolModeByEolString(fileEol) + + self.extractTasks() + + QApplication.restoreOverrideCursor() + + self.setModified(modified) + self.lastModified = QFileInfo(self.fileName).lastModified() + + def setEolModeByEolString(self, eolStr): + """ + Public method to set the eol mode given the eol string. + + @param eolStr eol string (string) + """ + if eolStr == '\r\n': + self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolWindows)) + elif eolStr == '\n': + self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolUnix)) + elif eolStr == '\r': + self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolMac)) + self.__eolChanged() + + 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): + """ + Public slot to write the text to a file. + + @param fn filename to write to (string) + @return flag indicating success (boolean) + """ + if Preferences.getEditor("StripTrailingWhitespace"): + self.__removeTrailingWhitespace() + + txt = self.text() + # work around glitch in scintilla: always make sure, + # that the last line is terminated properly + 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 = Preferences.getEditor("CreateBackupFile") + if createBackup: + if os.path.islink(fn): + fn = os.path.realpath(fn) + bfn = '%s~' % fn + try: + permissions = os.stat(fn).st_mode + perms_valid = True + except EnvironmentError: + # if there was an error, ignore it + perms_valid = False + try: + os.remove(bfn) + except EnvironmentError: + # if there was an error, ignore it + pass + try: + os.rename(fn, bfn) + except EnvironmentError: + # if there was an error, ignore it + pass + + # now write text to the file fn + try: + self.encoding = Utilities.writeEncodedFile(fn, txt, self.encoding) + if createBackup and perms_valid: + os.chmod(fn, permissions) + return True + except (IOError, Utilities.CodingError, UnicodeError) as why: + QMessageBox.critical(self, self.trUtf8('Save File'), + self.trUtf8('<p>The file <b>{0}</b> could not be saved.<br/>' + 'Reason: {1}</p>') + .format(fn, str(why))) + return False + + def saveFile(self, saveas = False, path = None): + """ + Public slot 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 tuple of two values (boolean, string) giving a success indicator and + the name of the saved file + """ + if not saveas and not self.isModified(): + return (False, None) # do nothing if text wasn't changed + + newName = None + if saveas or self.fileName is None: + saveas = True + if not path and self.fileName is not None: + path = os.path.dirname(self.fileName) + if path is None: + path = "" + defaultFilter = Preferences.getEditor("DefaultSaveFilter") + fn, selectedFilter = QFileDialog.getSaveFileNameAndFilter( + self, + self.trUtf8("Save File"), + path, + Lexers.getSaveFileFiltersList(True, True), + defaultFilter, + QFileDialog.Options(QFileDialog.DontConfirmOverwrite)) + + if fn: + ext = QFileInfo(fn).suffix() + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fn += ex + if QFileInfo(fn).exists(): + res = QMessageBox.warning(self, + self.trUtf8("Save File"), + self.trUtf8("<p>The file <b>{0}</b> already exists.</p>") + .format(fn), + QMessageBox.StandardButtons(\ + QMessageBox.Abort | \ + QMessageBox.Save), + QMessageBox.Abort) + if res == QMessageBox.Abort or res == QMessageBox.Cancel: + return (False, None) + fn = Utilities.toNativeSeparators(fn) + newName = fn + else: + return (False, None) + else: + fn = self.fileName + + self.emit(SIGNAL('editorAboutToBeSaved'), self.fileName) + if self.writeFile(fn): + if saveas: + self.__clearBreakpoints(self.fileName) + self.fileName = fn + self.setModified(False) + self.setReadOnly(False) + self.setWindowTitle(self.fileName) + if self.lexer_ is None and not self.__lexerReset: + self.setLanguage(self.fileName) + + if saveas: + self.isResourcesFile = self.fileName.endswith(".qrc") + self.__initContextMenu() + self.emit(SIGNAL('editorRenamed'), self.fileName) + self.lastModified = QFileInfo(self.fileName).lastModified() + if newName is not None: + self.vm.addToRecentList(newName) + self.emit(SIGNAL('editorSaved'), self.fileName) + self.__autoSyntaxCheck() + self.extractTasks() + return (True, self.fileName) + else: + self.lastModified = QFileInfo(fn).lastModified() + return (False, None) + + def saveFileAs(self, path = None): + """ + Public slot to save a file with a new name. + + @param path directory to save the file in (string) + @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.fileName = fn + self.setWindowTitle(self.fileName) + + if self.lexer_ is None: + self.setLanguage(self.fileName) + + self.lastModified = QFileInfo(self.fileName).lastModified() + 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.emit(SIGNAL('editorRenamed'), self.fileName) + self.inFileRenamed = False + + ############################################################################ + ## Utility methods below + ############################################################################ + + def ensureVisible(self, line): + """ + Public slot to ensure, that the specified line is visible. + + @param line line number to make visible + """ + self.ensureLineVisible(line-1) + + def ensureVisibleTop(self, line): + """ + Public slot to ensure, that the specified line is visible at the top + of the editor. + + @param line line number to make visible + """ + topLine = self.firstVisibleLine() + linesToScroll = line - topLine + self.scrollVertical(linesToScroll) + + 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 self.__unifiedMargins: + if margin == 1: + if modifiers & Qt.KeyboardModifiers(Qt.ShiftModifier): + if self.marginMenuActs["LMBbreakpoints"].isChecked(): + self.toggleBookmark(line + 1) + else: + self.__toggleBreakpoint(line + 1) + elif modifiers & Qt.KeyboardModifiers(Qt.ControlModifier): + if self.markersAtLine(line) & (1 << self.syntaxerror): + self.__showSyntaxError(line) + elif self.markersAtLine(line) & (1 << self.warning): + self.__showFlakesWarning(line) + else: + if self.marginMenuActs["LMBbreakpoints"].isChecked(): + self.__toggleBreakpoint(line + 1) + else: + self.toggleBookmark(line + 1) + else: + 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.__showFlakesWarning(line) + + def handleMonospacedEnable(self): + """ + Private slot to handle the Use Monospaced Font context menu entry. + """ + if self.menuActs["MonospacedFont"].isChecked(): + self.setMonospaced(True) + else: + if self.lexer_: + self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla") + 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) + @keyparam useWordChars flag indicating to use the wordCharacters + method (boolean) + @return tuple with start and end indices of the word at the position + (integer, integer) + """ + text = self.text(line) + if self.caseSensitive(): + cs = Qt.CaseSensitive + else: + cs = Qt.CaseInsensitive + wc = self.wordCharacters() + if wc is None or not useWordChars: + regExp = QRegExp('[^\w_]', cs) + else: + regExp = QRegExp('[^%s]' % re.escape(wc), cs) + start = regExp.lastIndexIn(text, index) + 1 + end = regExp.indexIn(text, index) + if start == end + 1 and index > 0: + # we are on a word boundary, try again + start = regExp.lastIndexIn(text, index - 1) + 1 + if start == -1: + start = 0 + if end == -1: + end = len(text) + + return (start, end) + + 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) + @keyparam 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 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 == '\n' or ch == '\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) + + def clearSearchIndicators(self): + """ + Public method to clear all search indicators. + """ + self.clearAllIndicators(self.searchIndicator) + self.__markedText = "" + + 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 + + ############################################################################ + ## Comment handling methods below + ############################################################################ + + 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: + self.insertAt(self.lexer_.commentStr(), line, self.indentation(line)) + 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.text(line).strip().startswith(commentStr): + return + + # now remove the comment string + self.beginUndoAction() + if Preferences.getEditor("CommentColumn0"): + self.setSelection(line, 0, line, len(commentStr)) + else: + self.setSelection(line, self.indentation(line), + line, self.indentation(line) + 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() + if indexTo == 0: + endLine = lineTo - 1 + else: + endLine = lineTo + + self.beginUndoAction() + # iterate over the lines + for line in range(lineFrom, endLine + 1): + if Preferences.getEditor("CommentColumn0"): + self.insertAt(commentStr, line, 0) + else: + self.insertAt(commentStr, line, self.indentation(line)) + + # 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() + if indexTo == 0: + endLine = lineTo - 1 + else: + endLine = lineTo + + 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.text(line).strip().startswith(commentStr): + continue + + if Preferences.getEditor("CommentColumn0"): + self.setSelection(line, 0, line, len(commentStr)) + else: + self.setSelection(line, self.indentation(line), + line, self.indentation(line) + 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() + if indexTo == 0: + endLine = lineTo - 1 + else: + endLine = lineTo + + 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() + + if indexTo == 0: + endLine = lineTo - 1 + else: + endLine = lineTo + + 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): + """ + Public slot to jump to the beginning of a line. + + @param line line number to go to (integer) + """ + self.setCursorPosition(line - 1, 0) + self.ensureVisible(line) + + ############################################################################ + ## 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.Prefs.settings, "Scintilla") + self.lexer_.initProperties() + + # initialize the auto indent style of the lexer + ais = self.lexer_.autoIndentStyle() + + # read the typing completer settings + if self.completer is not None: + self.completer.readSettings() + + # set the margins layout + if QSCINTILLA_VERSION() >= 0x020301: + self.__unifiedMargins = Preferences.getEditor("UnifiedMargins") + + # set the line marker colours + self.__setLineMarkerColours() + + # set the text display + self.__setTextDisplay() + + # set margin 0 and 2 configuration + self.__setMarginsDisplay() + + # set the autocompletion and calltips function + self.__setAutoCompletion() + 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() + + 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 + if self.__unifiedMargins: + margin1Mask = (1 << self.breakpoint) | \ + (1 << self.cbreakpoint) | \ + (1 << self.tbreakpoint) | \ + (1 << self.tcbreakpoint) | \ + (1 << self.dbreakpoint) | \ + (1 << self.currentline) | \ + (1 << self.errorline) | \ + (1 << self.bookmark) | \ + (1 << self.syntaxerror) | \ + (1 << self.notcovered) | \ (1 << self.taskmarker) | \ - (1 << self.warning) - self.setMarginWidth(1, 16) - self.setMarginSensitivity(1, True) - self.setMarginMarkerMask(1, margin1Mask) - - self.__linenoMargin = 0 - self.__foldMargin = 2 - else: - - 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) | \ - (1 << self.currentline) | \ - (1 << self.errorline) - self.setMarginWidth(self.__bpMargin, 16) - self.setMarginSensitivity(self.__bpMargin, True) - self.setMarginMarkerMask(self.__bpMargin, marginBpMask) - - marginIndicMask = (1 << self.syntaxerror) | \ - (1 << self.notcovered) | \ + (1 << self.warning) + self.setMarginWidth(1, 16) + self.setMarginSensitivity(1, True) + self.setMarginMarkerMask(1, margin1Mask) + + self.__linenoMargin = 0 + self.__foldMargin = 2 + else: + + 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) | \ + (1 << self.currentline) | \ + (1 << self.errorline) + 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) - 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.setMarginWidth(self.__linenoMargin, - ' ' + '8' * Preferences.getEditor("LinenoWidth")) - else: - self.setMarginWidth(self.__linenoMargin, 0) - - # set folding margin settings - if Preferences.getEditor("FoldingMargin"): - self.setMarginWidth(self.__foldMargin, 16) - folding = Preferences.getEditor("FoldingStyle") - try: - folding = QsciScintilla.FoldStyle(folding) - except AttributeError: - pass - try: - self.setFolding(folding, self.__foldMargin) - except TypeError: - self.setFolding(folding) - self.setFoldMarginColors(Preferences.getEditorColour("FoldmarginBackground"), - Preferences.getEditorColour("FoldmarginBackground")) - else: - self.setMarginWidth(self.__foldMargin, 0) - try: - self.setFolding(QsciScintilla.NoFoldStyle, self.__foldMargin) - except TypeError: - self.setFolding(QsciScintilla.NoFoldStyle) - - def __setTextDisplay(self): - """ - Private method to configure the text display. - """ - self.setTabWidth(Preferences.getEditor("TabWidth")) - self.setIndentationWidth(Preferences.getEditor("IndentWidth")) - if self.lexer_ and self.lexer_.alwaysKeepTabs(): - self.setIndentationsUseTabs(True) - else: - self.setIndentationsUseTabs(Preferences.getEditor("TabForIndentation")) - self.setTabIndents(Preferences.getEditor("TabIndents")) - self.setBackspaceUnindents(Preferences.getEditor("TabIndents")) - self.setIndentationGuides(Preferences.getEditor("IndentationGuides")) - if Preferences.getEditor("ShowWhitespace"): - self.setWhitespaceVisibility(QsciScintilla.WsVisible) - else: - self.setWhitespaceVisibility(QsciScintilla.WsInvisible) - self.setEolVisibility(Preferences.getEditor("ShowEOL")) - self.setAutoIndent(Preferences.getEditor("AutoIndentation")) - if Preferences.getEditor("BraceHighlighting"): - self.setBraceMatching(QsciScintilla.SloppyBraceMatch) - else: - self.setBraceMatching(QsciScintilla.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.Highlight)) - if Preferences.getEditor("ColourizeSelText"): - self.resetSelectionForegroundColor() - elif Preferences.getEditor("CustomSelectionColours"): - self.setSelectionForegroundColor(\ - Preferences.getEditorColour("SelectionForeground")) - else: - self.setSelectionForegroundColor(\ - QApplication.palette().color(QPalette.HighlightedText)) - self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol")) - self.setCaretForegroundColor( - Preferences.getEditorColour("CaretForeground")) - self.setCaretLineBackgroundColor( - Preferences.getEditorColour("CaretLineBackground")) - self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible")) - self.caretWidth = Preferences.getEditor("CaretWidth") - self.setCaretWidth(self.caretWidth) - 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")) - - if Preferences.getEditor("WrapLongLines"): - self.setWrapMode(QsciScintilla.WrapWord) - self.setWrapVisualFlags(\ - QsciScintilla.WrapFlagByBorder, QsciScintilla.WrapFlagByBorder) - else: - self.setWrapMode(QsciScintilla.WrapNone) - self.setWrapVisualFlags(\ - QsciScintilla.WrapFlagNone, QsciScintilla.WrapFlagNone) - - 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() - - def __setEolMode(self): - """ - Private method to configure the eol mode of the editor. - """ - eolMode = Preferences.getEditor("EOLMode") - eolMode = QsciScintilla.EolMode(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.setAutoCompletionShowSingle( - Preferences.getEditor("AutoCompletionShowSingle")) - autoCompletionSource = Preferences.getEditor("AutoCompletionSource") - if autoCompletionSource == QsciScintilla.AcsDocument: - self.setAutoCompletionSource(QsciScintilla.AcsDocument) - elif autoCompletionSource == QsciScintilla.AcsAPIs: - self.setAutoCompletionSource(QsciScintilla.AcsAPIs) - else: - self.setAutoCompletionSource(QsciScintilla.AcsAll) - if Preferences.getEditor("AutoCompletionEnabled"): - if self.__acHookFunction is None: - self.setAutoCompletionThreshold( - Preferences.getEditor("AutoCompletionThreshold")) - else: - self.setAutoCompletionThreshold(0) - else: - self.setAutoCompletionThreshold(-1) - self.setAutoCompletionSource(QsciScintilla.AcsNone) - - def __setCallTips(self): - """ - Private method to configure the calltips function. - """ - if Preferences.getEditor("CallTipsEnabled"): - self.setCallTipsBackgroundColor( - Preferences.getEditorColour("CallTipsBackground")) - self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible")) - calltipsStyle = Preferences.getEditor("CallTipsStyle") - if calltipsStyle == QsciScintilla.CallTipsNoContext: - self.setCallTipsStyle(QsciScintilla.CallTipsNoContext) - elif calltipsStyle == QsciScintilla.CallTipsNoAutoCompletionContext: - self.setCallTipsStyle(QsciScintilla.CallTipsNoAutoCompletionContext) - else: - self.setCallTipsStyle(QsciScintilla.CallTipsContext) - else: - self.setCallTipsStyle(QsciScintilla.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. - """ - acs = Preferences.getEditor("AutoCompletionSource") - if acs == QsciScintilla.AcsDocument: - self.autoCompleteFromDocument() - elif acs == QsciScintilla.AcsAPIs: - self.autoCompleteFromAPIs() - elif acs == QsciScintilla.AcsAll: - self.autoCompleteFromAll() - else: - QMessageBox.information(None, - self.trUtf8("Autocompletion"), - self.trUtf8("""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: - self.setAutoCompletionThreshold( - Preferences.getEditor("AutoCompletionThreshold")) - autoCompletionSource = Preferences.getEditor("AutoCompletionSource") - if autoCompletionSource == QsciScintilla.AcsDocument: - self.setAutoCompletionSource(QsciScintilla.AcsDocument) - elif autoCompletionSource == QsciScintilla.AcsAPIs: - self.setAutoCompletionSource(QsciScintilla.AcsAPIs) - else: - self.setAutoCompletionSource(QsciScintilla.AcsAll) - else: - self.setAutoCompletionThreshold(-1) - self.setAutoCompletionSource(QsciScintilla.AcsNone) - - 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): - """ - Public slot called to handle the user entering a character. - - @param charNumber value of the character entered (integer) - """ - if self.isListActive(): - char = chr(charNumber) - if self.__isStartChar(char): - self.cancelList() - self.autoComplete(auto = True, context = True) - return - elif char == '(': - self.cancelList() - - if self.callTipsStyle() != QsciScintilla.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() - for wsep in wseps: - if wsep.endswith(ch): - return True - - return False - - def setAutoCompletionHook(self, func): - """ - Public method to set an autocompletion hook. - - @param func Function to be set to handle autocompletion. func - should be a function taking a reference to the editor and - a boolean indicating to complete a context. - """ - if self.autoCompletionThreshold() > 0: - self.setAutoCompletionThreshold(0) - self.__acHookFunction = func - self.connect(self, SIGNAL("SCN_CHARADDED(int)"), self.__charAdded) - - def unsetAutoCompletionHook(self): - """ - Public method to unset a previously installed autocompletion hook. - """ - self.disconnect(self, SIGNAL("SCN_CHARADDED(int)"), self.__charAdded) - self.__acHookFunction = None - if self.autoCompletionThreshold() == 0: - self.setAutoCompletionThreshold( - Preferences.getEditor("AutoCompletionThreshold")) - - def autoCompletionHook(self): - """ - Public method to get the autocompletion hook function. - - @return function set by setAutoCompletionHook() - """ - return self.__acHookFunction - - def autoComplete(self, auto = False, context = True): - """ - Public method to start autocompletion. - - @keyparam auto flag indicating a call from the __charAdded method (boolean) - @keyparam context flag indicating to complete a context (boolean) - """ - if auto and self.autoCompletionThreshold() == -1: - # autocompletion is disabled - return - - if self.__acHookFunction is not None: - self.__acHookFunction(self, context) - elif not auto: - self.autoCompleteQScintilla() - elif self.autoCompletionSource() != QsciScintilla.AcsNone: - self.autoCompleteQScintilla() - - def callTip(self): - """ - Public method to show calltips. - """ - if self.__ctHookFunction is not None: - self.__callTip() - else: - QsciScintillaCompat.callTip(self) - - 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.SendScintilla(QsciScintilla.SCI_CALLTIPCANCEL) - - if not found: - return - - try: - callTips = self.__ctHookFunction(self, pos, commas) - except TypeError: - # for backward compatibility - callTips = self.__ctHookFunction(self, pos) - if len(callTips) == 0: - if Preferences.getEditor("CallTipsScintillaOnFail"): - # try QScintilla calltips - QsciScintillaCompat.callTip(self) - return - - ctshift = 0 - for ct in callTips: - shift = ct.index("(") - if ctshift < shift: - ctshift = shift - - cv = self.callTipsVisible() - if cv > 0: - # this is just a safe guard - ct = "\n".join(callTips[:cv]) - else: - # until here and unindent below - ct = "\n".join(callTips) - - self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW, - self.__adjustedCallTipPosition(ctshift, pos), ct) - if '\n' in ct: - return - - # Highlight the current argument - if commas == 0: - astart = ct.find('(') - else: - astart = ct.find(',') - commas -= 1 - while astart != -1 and commas > 0: - astart = ct.find(',', astart + 1) - commas -= 1 - - if astart == -1: - return - - depth = 0 - for aend in range(astart + 1, len(ct)): - ch = ct[aend] - - if ch == ',' and depth == 0: - break - elif ch == '(': - depth += 1 - elif ch == ')': - if depth == 0: - break - - depth -= 1 - - if astart != aend: - self.SendScintilla(QsciScintilla.SCI_CALLTIPSETHLT, astart, 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 = ct - ctshift - return ct - - def setCallTipHook(self, func): - """ - Public method to set a calltip hook. - - @param func Function to be set to determine 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. - """ - self.__ctHookFunction = func - - def unsetCallTipHook(self): - """ - Public method to unset a calltip hook. - """ - self.__ctHookFunction = None - - def callTipHook(self): - """ - Public method to get the calltip hook function. - - @return function set by setCallTipHook() - """ - return self.__ctHookFunction - - ################################################################# - ## 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): - """ - Private 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.__unifiedMargins: - self.marginMenu.popup(evt.globalPos()) - else: - 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()) - - def __showContextMenu(self): - """ - Private slot handling the aboutToShow signal of the context menu. - """ - self.menuActs["Save"].setEnabled(self.isModified()) - self.menuActs["Undo"].setEnabled(self.isUndoAvailable()) - self.menuActs["Redo"].setEnabled(self.isRedoAvailable()) - self.menuActs["Revert"].setEnabled(self.isModified()) - if not self.miniMenu: - self.menuActs["Cut"].setEnabled(self.hasSelectedText()) - self.menuActs["Copy"].setEnabled(self.hasSelectedText()) - if not self.isResourcesFile: - if self.fileName and \ - (self.isPyFile() or self.isPy3File()): - self.menuActs["Show"].setEnabled(True) - else: - self.menuActs["Show"].setEnabled(False) - if self.fileName and \ - (self.isPyFile() or self.isPy3File() 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()) - self.menuActs["StreamComment"].setEnabled(self.lexer_.canStreamComment()) - self.menuActs["BoxComment"].setEnabled(self.lexer_.canBoxComment()) - else: - self.menuActs["Comment"].setEnabled(False) - self.menuActs["Uncomment"].setEnabled(False) - self.menuActs["StreamComment"].setEnabled(False) - self.menuActs["BoxComment"].setEnabled(False) - - self.menuActs["TypingAidsEnabled"].setEnabled(self.completer is not None) - self.menuActs["TypingAidsEnabled"].setChecked(\ - self.completer is not None and self.completer.isEnabled()) - - 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.emit(SIGNAL("showMenu"), "Main", self.menu, self) - - def __showContextMenuAutocompletion(self): - """ - Private slot called before the autocompletion menu is shown. - """ - self.menuActs["acDynamic"].setEnabled( - self.acAPI or self.__acHookFunction is not None) - self.menuActs["acAPI"].setEnabled(self.acAPI) - self.menuActs["acAPIDocument"].setEnabled(self.acAPI) - self.menuActs["calltip"].setEnabled(self.acAPI) - - self.emit(SIGNAL("showMenu"), "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 - project = e5App().getObject("Project") - if project.isOpen() and project.isProjectSource(self.fileName): - fn = project.getMainScript(True) - if fn is not None: - tfn = Utilities.getTestFileName(fn) - basename = os.path.splitext(fn)[0] - tbasename = os.path.splitext(tfn)[0] - prEnable = prEnable or \ - os.path.isfile("%s.profile" % basename) or \ - os.path.isfile("%s.profile" % tbasename) - coEnable = coEnable or \ - os.path.isfile("%s.coverage" % basename) or \ - os.path.isfile("%s.coverage" % tbasename) - - # now check ourself - fn = self.getFileName() - if fn is not None: - tfn = Utilities.getTestFileName(fn) - basename = os.path.splitext(fn)[0] - tbasename = os.path.splitext(tfn)[0] - prEnable = prEnable or \ - os.path.isfile("%s.profile" % basename) or \ - os.path.isfile("%s.profile" % tbasename) - coEnable = coEnable or \ - os.path.isfile("%s.coverage" % basename) or \ - os.path.isfile("%s.coverage" % tbasename) - - # now check for syntax errors - if self.hasSyntaxErrors(): - coEnable = False - - self.profileMenuAct.setEnabled(prEnable) - self.coverageMenuAct.setEnabled(coEnable) - self.coverageShowAnnotationMenuAct.setEnabled(\ - coEnable and not self.coverageMarkersShown) - self.coverageHideAnnotationMenuAct.setEnabled(\ - self.coverageMarkersShown) - - self.emit(SIGNAL("showMenu"), "Show", self.showMenu, self) - - def __showContextMenuGraphics(self): - """ - Private slot handling the aboutToShow signal of the diagrams context menu. - """ - project = e5App().getObject("Project") - if project.isOpen() and project.isProjectSource(self.fileName): - self.applicationDiagramMenuAct.setEnabled(True) - else: - self.applicationDiagramMenuAct.setEnabled(False) - - self.emit(SIGNAL("showMenu"), "Graphics", self.graphicsMenu, self) - - def __showContextMenuMargin(self): - """ - Private slot handling the aboutToShow signal of the margins context menu. - """ - if self.fileName and \ - (self.isPyFile() or self.isPy3File() or self.isRubyFile()): - self.marginMenuActs["Breakpoint"].setEnabled(True) - self.marginMenuActs["TempBreakpoint"].setEnabled(True) - if self.markersAtLine(self.line) & self.breakpointMask: - self.marginMenuActs["EditBreakpoint"].setEnabled(True) - self.marginMenuActs["EnableBreakpoint"].setEnabled(True) - else: - self.marginMenuActs["EditBreakpoint"].setEnabled(False) - self.marginMenuActs["EnableBreakpoint"].setEnabled(False) - if self.markersAtLine(self.line) & (1 << self.dbreakpoint): - self.marginMenuActs["EnableBreakpoint"].setText(\ - self.trUtf8('Enable breakpoint')) - else: - self.marginMenuActs["EnableBreakpoint"].setText(\ - self.trUtf8('Disable breakpoint')) - if self.breaks: - self.marginMenuActs["NextBreakpoint"].setEnabled(True) - self.marginMenuActs["PreviousBreakpoint"].setEnabled(True) - self.marginMenuActs["ClearBreakpoint"].setEnabled(True) - else: - self.marginMenuActs["NextBreakpoint"].setEnabled(False) - self.marginMenuActs["PreviousBreakpoint"].setEnabled(False) - self.marginMenuActs["ClearBreakpoint"].setEnabled(False) - else: - self.marginMenuActs["Breakpoint"].setEnabled(False) - self.marginMenuActs["TempBreakpoint"].setEnabled(False) - self.marginMenuActs["EditBreakpoint"].setEnabled(False) - self.marginMenuActs["EnableBreakpoint"].setEnabled(False) - self.marginMenuActs["NextBreakpoint"].setEnabled(False) - self.marginMenuActs["PreviousBreakpoint"].setEnabled(False) - self.marginMenuActs["ClearBreakpoint"].setEnabled(False) - - if self.bookmarks: - self.marginMenuActs["NextBookmark"].setEnabled(True) - self.marginMenuActs["PreviousBookmark"].setEnabled(True) - self.marginMenuActs["ClearBookmark"].setEnabled(True) - else: - self.marginMenuActs["NextBookmark"].setEnabled(False) - self.marginMenuActs["PreviousBookmark"].setEnabled(False) - self.marginMenuActs["ClearBookmark"].setEnabled(False) - - if len(self.syntaxerrors): - self.marginMenuActs["GotoSyntaxError"].setEnabled(True) - self.marginMenuActs["ClearSyntaxError"].setEnabled(True) - if self.markersAtLine(self.line) & (1 << self.syntaxerror): - self.marginMenuActs["ShowSyntaxError"].setEnabled(True) - else: - self.marginMenuActs["ShowSyntaxError"].setEnabled(False) - else: - self.marginMenuActs["GotoSyntaxError"].setEnabled(False) - self.marginMenuActs["ClearSyntaxError"].setEnabled(False) - self.marginMenuActs["ShowSyntaxError"].setEnabled(False) - - if self.notcoveredMarkers: - self.marginMenuActs["NextCoverageMarker"].setEnabled(True) - self.marginMenuActs["PreviousCoverageMarker"].setEnabled(True) - else: - self.marginMenuActs["NextCoverageMarker"].setEnabled(False) - self.marginMenuActs["PreviousCoverageMarker"].setEnabled(False) - - if self.__hasTaskMarkers: - self.marginMenuActs["PreviousTaskMarker"].setEnabled(True) - self.marginMenuActs["NextTaskMarker"].setEnabled(True) - else: - self.marginMenuActs["PreviousTaskMarker"].setEnabled(False) - self.marginMenuActs["NextTaskMarker"].setEnabled(False) - - self.emit(SIGNAL("showMenu"), "Margin", self.sender(), self) - - def __showContextMenuChecks(self): - """ - Private slot handling the aboutToShow signal of the checks context menu. - """ - self.emit(SIGNAL("showMenu"), "Checks", self.checksMenu, self) - - def __contextSave(self): - """ - Private slot handling the save context menu entry. - """ - ok, newName = self.saveFile() - if ok: - self.vm.setEditorName(self, newName) - - def __contextSaveAs(self): - """ - Private slot handling the save as context menu entry. - """ - ok, newName = self.saveFileAs() - if ok: - self.vm.setEditorName(self, newName) - - 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 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 self.fileName is not None and \ - not self.autosaveManuallyDisabled and \ - not self.isReadOnly() - - def __autoSyntaxCheck(self): - """ - Private method to perform an automatic syntax check of the file. - """ - if Preferences.getEditor("AutoCheckSyntax"): - self.clearSyntaxError() - if self.isPy3File(): - syntaxError, _fn, errorline, _code, _error = \ - Utilities.compile(self.fileName, self.text()) - if syntaxError: - self.toggleSyntaxError(int(errorline), 1, _error) - - def __showCodeMetrics(self): - """ - Private method to handle the code metrics context menu action. - """ - if not self.checkDirty(): - return - - self.codemetrics = CodeMetricsDialog() - self.codemetrics.show() - self.codemetrics.start(self.fileName) - - def __getCodeCoverageFile(self): - """ - Private method to get the filename of the file containing coverage info. - - @return filename of the coverage file (string) - """ - files = [] - - # first check if the file belongs to a project and there is - # a project coverage file - project = e5App().getObject("Project") - if project.isOpen() and project.isProjectSource(self.fileName): - fn = project.getMainScript(True) - if fn is not None: - tfn = Utilities.getTestFileName(fn) - basename = os.path.splitext(fn)[0] - tbasename = os.path.splitext(tfn)[0] - - f = "%s.coverage" % basename - tf = "%s.coverage" % tbasename - if os.path.isfile(f): - files.append(f) - if os.path.isfile(tf): - files.append(tf) - - # now check, if there are coverage files belonging to ourself - fn = self.getFileName() - if fn is not None: - tfn = Utilities.getTestFileName(fn) - basename = os.path.splitext(fn)[0] - tbasename = os.path.splitext(tfn)[0] - - f = "%s.coverage" % basename - tf = "%s.coverage" % tbasename - if os.path.isfile(f) and not f in files: - files.append(f) - if os.path.isfile(tf) and not tf in files: - files.append(tf) - - if files: - if len(files) > 1: - fn, ok = QInputDialog.getItem(\ - self, - self.trUtf8("Code Coverage"), - self.trUtf8("Please select a coverage file"), - files, - 0, False) - if not ok: - return - else: - fn = files[0] - else: - fn = None - - return fn - - def __showCodeCoverage(self): - """ - Private method to handle the code coverage context menu action. - """ - fn = self.__getCodeCoverageFile() - if fn: - self.codecoverage = PyCoverageDialog() - self.codecoverage.show() - self.codecoverage.start(fn, self.fileName) - - def __codeCoverageShowAnnotations(self): - """ - Private method to handle the show code coverage annotations context menu action. - """ - fn = self.__getCodeCoverageFile() - if fn: - cover = coverage(data_file = fn) - cover.use_cache(True) - 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.emit(SIGNAL('coverageMarkersShown'), True) - self.coverageMarkersShown = True - else: - QMessageBox.information(None, - self.trUtf8("Show Code Coverage Annotations"), - self.trUtf8("""All lines have been covered.""")) - else: - QMessageBox.warning(None, - self.trUtf8("Show Code Coverage Annotations"), - self.trUtf8("""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 = [] - self.emit(SIGNAL('coverageMarkersShown'), False) - self.coverageMarkersShown = False - - def hasCoverageMarkers(self): - """ - Public method to test, if there are coverage markers. - """ - 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 = [] - - # first check if the file belongs to a project and there is - # a project profile file - project = e5App().getObject("Project") - if project.isOpen() and project.isProjectSource(self.fileName): - fn = project.getMainScript(True) - if fn is not None: - tfn = Utilities.getTestFileName(fn) - basename = os.path.splitext(fn)[0] - tbasename = os.path.splitext(tfn)[0] - - f = "%s.profile" % basename - tf = "%s.profile" % tbasename - if os.path.isfile(f): - files.append(f) - if os.path.isfile(tf): - files.append(tf) - - # now check, if there are profile files belonging to ourself - fn = self.getFileName() - if fn is not None: - tfn = Utilities.getTestFileName(fn) - basename = os.path.splitext(fn)[0] - tbasename = os.path.splitext(tfn)[0] - - f = "%s.profile" % basename - tf = "%s.profile" % tbasename - if os.path.isfile(f) and not f in files: - files.append(f) - if os.path.isfile(tf) and not tf in files: - files.append(tf) - - if files: - if len(files) > 1: - fn, ok = QInputDialog.getItem(\ - self, - self.trUtf8("Profile Data"), - self.trUtf8("Please select a profile file"), - files, - 0, False) - if not ok: - return - else: - fn = files[0] - else: - return - - 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, error, msg = ""): - """ - Public method to toggle a syntax error indicator. - - @param line line number of the syntax error - @param error flag indicating if the error marker should be - set or deleted (boolean) - @param msg error message (string) - """ - 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) - if not (markers & (1 << self.syntaxerror)): - handle = self.markerAdd(line - 1, self.syntaxerror) - self.syntaxerrors[handle] = msg - self.emit(SIGNAL('syntaxerrorToggled'), self) - else: - for handle in list(self.syntaxerrors.keys()): - if self.markerLine(handle) == line - 1: - del self.syntaxerrors[handle] - self.markerDeleteHandle(handle) - self.emit(SIGNAL('syntaxerrorToggled'), self) - - 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 hasSyntaxErrors(self): - """ - Public method to check for the presence of bookmarks. - - @return flag indicating the presence of bookmarks (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: - self.setCursorPosition(seline, 0) - 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, False) - - 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: - QMessageBox.critical(None, - self.trUtf8("Syntax Error"), - self.syntaxerrors[handle]) - break - else: - QMessageBox.critical(None, - self.trUtf8("Syntax Error"), - self.trUtf8("No syntax error message available.")) - - ################################################################# - ## 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.trUtf8("Macro Name"), - self.trUtf8("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 = QFileDialog.getOpenFileName( - self, - self.trUtf8("Load macro file"), - configDir, - self.trUtf8("Macro files (*.macro)")) - - if not fname: - return # user aborted - - try: - f = open(fname, "r", encoding = "utf-8") - lines = f.readlines() - f.close() - except IOError: - QMessageBox.critical(self, - self.trUtf8("Error loading macro"), - self.trUtf8("<p>The macro file <b>{0}</b> could not be read.</p>") - .format(fname)) - return - - if len(lines) != 2: - QMessageBox.critical(self, - self.trUtf8("Error loading macro"), - self.trUtf8("<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 = QFileDialog.getSaveFileNameAndFilter(\ - self, - self.trUtf8("Save macro file"), - configDir, - self.trUtf8("Macro files (*.macro)"), - "", - QFileDialog.Options(QFileDialog.DontConfirmOverwrite)) - - if not fname: - return # user aborted - - ext = QFileInfo(fname).suffix() - if not ext: - ex = selectedFilter.split("(*")[1].split(")")[0] - if ex: - fname += ex - if QFileInfo(fname).exists(): - res = QMessageBox.warning(self, - self.trUtf8("Save macro"), - self.trUtf8("<p>The macro file <b>{0}</b> already exists.</p>") - .format(fname), - QMessageBox.StandardButtons(\ - QMessageBox.Abort | \ - QMessageBox.Save), - QMessageBox.Abort) - if res == QMessageBox.Abort or res == QMessageBox.Cancel: - return - fname = Utilities.toNativeSeparators(fname) - - try: - f = open(fname, "w", encoding = "utf-8") - f.write("%s%s" % (name, os.linesep)) - f.write(self.macros[name].save()) - f.close() - except IOError: - QMessageBox.critical(self, - self.trUtf8("Error saving macro"), - self.trUtf8("<p>The macro file <b>{0}</b> could not be written.</p>") - .format(fname)) - return - - def macroRecordingStart(self): - """ - Public method to start macro recording. - """ - if self.recording: - res = QMessageBox.warning(self, - self.trUtf8("Start Macro Recording"), - self.trUtf8("Macro recording is already active. Start new?"), - QMessageBox.StandardButtons(\ - QMessageBox.No | \ - QMessageBox.Yes), - QMessageBox.Yes) - if res == QMessageBox.Yes: - 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.trUtf8("Macro Recording"), - self.trUtf8("Enter name of the macro:"), - QLineEdit.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. - """ - QsciScintillaCompat.undo(self) - self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable()) - self.emit(SIGNAL('redoAvailable'), self.isRedoAvailable()) - - def redo(self): - """ - Public method to redo the last recorded change. - """ - QsciScintillaCompat.redo(self) - self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable()) - self.emit(SIGNAL('redoAvailable'), 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 self.vm.closeEditor(self) - - def closeIt(self): - """ - Public method called by the viewmanager to finally get rid of us. - """ - if Preferences.getEditor("ClearBreaksOnClose"): - self.__menuClearBreakpoints() - - for clone in self.__clones[:]: - self.removeClone(clone) - clone.removeClone(self) - - self.disconnect(self.breakpointModel, - SIGNAL("rowsAboutToBeRemoved(const QModelIndex &, int, int)"), - self.__deleteBreakPoints) - self.disconnect(self.breakpointModel, - SIGNAL("dataAboutToBeChanged(const QModelIndex &, const QModelIndex &)"), - self.__breakPointDataAboutToBeChanged) - self.disconnect(self.breakpointModel, - SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), - self.__changeBreakPoints) - self.disconnect(self.breakpointModel, - SIGNAL("rowsInserted(const QModelIndex &, int, int)"), - self.__addBreakPoints) - - self.disconnect(e5App().getObject("Project"), SIGNAL("projectPropertiesChanged"), - self.__projectPropertiesChanged) - - if self.spell: - self.spell.stopIncrementalCheck() - - QsciScintillaCompat.close(self) - - def keyPressEvent(self, ev): - """ - Re-implemented to handle the user input a key at a time. - - @param ev key event (QKeyEvent) - """ - txt = ev.text() - key = ev.key() - - # See it is text to insert. - if len(txt) and txt >= " ": - QsciScintillaCompat.keyPressEvent(self, 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 (QFocusEvent) - """ - self.recolor() - self.vm.editActGrp.setEnabled(True) - self.vm.editorActGrp.setEnabled(True) - self.vm.copyActGrp.setEnabled(True) - self.vm.viewActGrp.setEnabled(True) - try: - self.setCaretWidth(self.caretWidth) - except AttributeError: - pass - self.__updateReadOnly(False) - if self.vm.editorsCheckFocusInEnabled() and \ - not self.inReopenPrompt and self.fileName and \ - QFileInfo(self.fileName).lastModified().toString() != \ - self.lastModified.toString(): - if Preferences.getEditor("AutoReopen") and not self.isModified(): - self.refresh() - else: - self.inReopenPrompt = True - msg = self.trUtf8(\ - """<p>The file <b>{0}</b> has been changed while it was opened in""" - """ eric5. Reread it?</p>""").format(self.fileName) - default = QMessageBox.No - if self.isModified(): - msg += self.trUtf8(\ - """<br><b>Warning:</b> You will loose""" - """ your changes upon reopening it.""") - default = QMessageBox.Ok - res = QMessageBox.warning(None, - self.trUtf8("File changed"), msg, - QMessageBox.StandardButtons(\ - QMessageBox.Yes | \ - QMessageBox.No), - default) - if res == QMessageBox.Yes: - self.refresh() - else: - # do not prompt for this change again... - self.lastModified = QFileInfo(self.fileName).lastModified() - self.inReopenPrompt = False - - QsciScintillaCompat.focusInEvent(self, event) - - def focusOutEvent(self, event): - """ - Public method called when the editor loses focus. - - @param event the event object (QFocusEvent) - """ - self.vm.editorActGrp.setEnabled(False) - self.setCaretWidth(0) - - QsciScintillaCompat.focusOutEvent(self, 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 (QEvent) - @return flag indicating if the event could be processed (bool) - """ - if evt.type() == QEvent.WindowStateChange and \ - self.fileName is not None: - if self.windowState() == Qt.WindowStates(Qt.WindowMinimized): - cap = os.path.basename(self.fileName) - else: - cap = self.fileName - if self.isReadOnly(): - cap = self.trUtf8("{0} (ro)").format(cap) - self.setWindowTitle(cap) - - QsciScintillaCompat.changeEvent(self, evt) - - def mousePressEvent(self, event): - """ - Protected method to handle the mouse press event. - - @param event the mouse press event (QMouseEvent) - """ - self.vm.eventFilter(self, event) - QsciScintillaCompat.mousePressEvent(self, event) - - 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 is None: - return - readOnly = not QFileInfo(self.fileName).isWritable() - if not bForce and (readOnly == self.isReadOnly()): - return - cap = self.fileName - if readOnly: - cap = self.trUtf8("{0} (ro)".format(cap)) - self.setReadOnly(readOnly) - self.setWindowTitle(cap) - self.emit(SIGNAL('captionChanged'), 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 breakpoint markers - for handle in list(self.breaks.keys()): - self.markerDeleteHandle(handle) - self.breaks = {} - - # reread the file - try: - self.readFile(self.fileName) - except IOError: - # do not prompt for this change again... - self.lastModified = QDateTime.currentDateTime() - self.setModified(False) - - # 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.emit(SIGNAL('editorSaved'), self.fileName) - self.__autoSyntaxCheck() - - 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: - f = Preferences.getEditorOtherFonts("MonospacedFont") - self.monospacedStyles(f) - else: - if not self.lexer_: - self.clearStyles() - self.__setMarginsDisplay() - self.setFont(Preferences.getEditorOtherFonts("DefaultFont")) - - self.useMonospaced = on - - ################################################################# - ## 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: - QsciScintillaCompat.dragEnterEvent(self, 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: - QsciScintillaCompat.dragMoveEvent(self, 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: - QsciScintillaCompat.dragLeaveEvent(self, 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 QFileInfo(fname).isDir(): - self.vm.openSourceFile(fname) - else: - QMessageBox.information(None, - self.trUtf8("Drop Error"), - self.trUtf8("""<p><b>{0}</b> is not a file.</p>""") - .format(fname)) - event.acceptProposedAction() - else: - QsciScintillaCompat.dropEvent(self, event) - - self.inDragDrop = False - - ################################################################# - ## Support for Qt resources files - ################################################################# - - def __initContextMenuResources(self): - """ - Private method used to setup the Resources context sub menu. - """ - menu = QMenu(self.trUtf8('Resources')) - - menu.addAction(self.trUtf8('Add file...'), - self.__addFileResource) - menu.addAction(self.trUtf8('Add files...'), - self.__addFileResources) - menu.addAction(self.trUtf8('Add aliased file...'), - self.__addFileAliasResource) - menu.addAction(self.trUtf8('Add localized resource...'), - self.__addLocalizedResource) - menu.addSeparator() - menu.addAction(self.trUtf8('Add resource frame'), - self.__addResourceFrame) - - self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuResources) - - return menu - - def __showContextMenuResources(self): - """ - Private slot handling the aboutToShow signal of the resources context menu. - """ - self.emit(SIGNAL("showMenu"), "Resources", self.resourcesMenu, self) - - def __addFileResource(self): - """ - Private method to handle the Add file context menu action. - """ - dirStr = os.path.dirname(self.fileName) - file = QFileDialog.getOpenFileName(\ - self, - self.trUtf8("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 = QFileDialog.getOpenFileNames(\ - self, - self.trUtf8("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 = QFileDialog.getOpenFileName(\ - self, - self.trUtf8("Add aliased file resource"), - dirStr, - "") - if file: - relFile = QDir(dirStr).relativeFilePath(file) - alias, ok = QInputDialog.getText(\ - self, - self.trUtf8("Add aliased file resource"), - self.trUtf8("Alias for file <b>{0}</b>:").format(relFile), - QLineEdit.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.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.UMLClassDiagram import UMLClassDiagram - if not self.checkDirty(): - return - - self.classDiagram = UMLClassDiagram(self.fileName, self, noAttrs = False) - self.classDiagram.show() - - def __showPackageDiagram(self): - """ - Private method to handle the Package Diagram context menu action. - """ - from Graphics.PackageDiagram import PackageDiagram - if not self.checkDirty(): - return - - package = os.path.isdir(self.fileName) and self.fileName \ - or os.path.dirname(self.fileName) - res = QMessageBox.question(None, - self.trUtf8("Package Diagram"), - self.trUtf8("""Include class attributes?"""), - QMessageBox.StandardButtons(\ - QMessageBox.No | \ - QMessageBox.Yes), - QMessageBox.Yes) - self.packageDiagram = PackageDiagram(package, self, - noAttrs = (res == QMessageBox.No)) - self.packageDiagram.show() - - def __showImportsDiagram(self): - """ - Private method to handle the Imports Diagram context menu action. - """ - from Graphics.ImportsDiagram import ImportsDiagram - if not self.checkDirty(): - return - - package = os.path.isdir(self.fileName) and self.fileName \ - or os.path.dirname(self.fileName) - res = QMessageBox.question(None, - self.trUtf8("Imports Diagram"), - self.trUtf8("""Include imports from external modules?"""), - QMessageBox.StandardButtons(\ - QMessageBox.No | \ - QMessageBox.Yes), - QMessageBox.No) - self.importsDiagram = ImportsDiagram(package, self, - showExternalImports = (res == QMessageBox.Yes)) - self.importsDiagram.show() - - def __showApplicationDiagram(self): - """ - Private method to handle the Imports Diagram context menu action. - """ - from Graphics.ApplicationDiagram import ApplicationDiagram - res = QMessageBox.question(None, - self.trUtf8("Application Diagram"), - self.trUtf8("""Include module names?"""), - QMessageBox.StandardButtons(\ - QMessageBox.No | \ - QMessageBox.Yes), - QMessageBox.Yes) - self.applicationDiagram = ApplicationDiagram(e5App().getObject("Project"), - self, noModules = (res == QMessageBox.No)) - self.applicationDiagram.show() - - ####################################################################### - ## 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) - - ####################################################################### - ## Autocompleting 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: - line, index = self.getCursorPosition() - tmplName = self.getWordLeft(line, index) - if tmplName: - if e5App().getObject("TemplateViewer").hasTemplate(tmplName): - self.__applyTemplate(tmplName) - return - else: - templateNames = \ - e5App().getObject("TemplateViewer").getTemplateNames(tmplName) - if len(templateNames) == 1: - self.__applyTemplate(templateNames[0]) - return - elif len(templateNames) > 1: - self.showUserList(TemplateCompletionListID, - ["%s?%d" % (t, self.TemplateImageID) for t in templateNames]) - return - - QsciScintillaCompat.editorCommand(self, cmd) - - def __completionListSelected(self, id, txt): - """ - Private slot to handle the selection from the completion list. - - @param id the ID of the user list (should be 1) (integer) - @param txt the selected text (string) - """ - if id == TemplateCompletionListID: - self.__applyTemplate(txt) - - def __applyTemplate(self, templateName): - """ - Private method to apply a template by name. - - @param templateName name of the template to apply (string) - """ - if e5App().getObject("TemplateViewer").hasTemplate(templateName): - self.extendSelectionWordLeft() - e5App().getObject("TemplateViewer").applyNamedTemplate(templateName) - - ####################################################################### - ## Project related methods - ####################################################################### - - def __projectPropertiesChanged(self): - """ - Private slot to handle changes of the project properties. - """ - project = e5App().getObject("Project") - if self.spell: - pwl, pel = project.getProjectDictionaries() - self.__setSpellingLanguage(project.getProjectSpellLanguage(), - pwl = pwl, pel = pel) - - def addedToProject(self): - """ - Public method to signal, that this editor has been added to a project. - """ - project = e5App().getObject("Project") - if self.spell: - pwl, pel = project.getProjectDictionaries() - self.__setSpellingLanguage(project.getProjectSpellLanguage(), - pwl = pwl, pel = pel) - self.connect(project, SIGNAL("projectPropertiesChanged"), - self.__projectPropertiesChanged) - - ####################################################################### - ## Spellchecking related methods - ####################################################################### - - def __setSpellingLanguage(self, language, pwl = "", pel = ""): - """ - Private slot to set the spell checking language. - - @param language spell checking language to be set (string) - @keyparam pwl name of the personal/project word list (string) - @keyparam 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.connect(e5App().getObject("Project"), SIGNAL("projectPropertiesChanged"), - self.__projectPropertiesChanged) - 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. - """ - project = e5App().getObject("Project") - if self.fileName and \ - project.isOpen() and \ - project.isProjectSource(self.fileName): - pwl, pel = project.getProjectDictionaries() - self.__setSpellingLanguage(project.getProjectSpellLanguage(), - pwl = pwl, pel = pel) - - def setAutoSpellChecking(self): - """ - Public method to set the automatic spell checking. - """ - if Preferences.getEditor("AutoSpellCheckingEnabled"): - self.connect(self, SIGNAL("SCN_CHARADDED(int)"), self.__spellCharAdded) - self.spell.checkDocumentIncrementally() - else: - self.disconnect(self, SIGNAL("SCN_CHARADDED(int)"), 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. - - @param pos position to be checked (integer) - @return flag indicating pos is in a spell check region (boolean) - """ - if self.__spellCheckStringsOnly: - style = self.styleAt(pos) - if self.lexer_ is not None: - return self.lexer_.isCommentStyle(style) or \ - self.lexer_.isStringStyle(style) - return True - - def __spellCharAdded(self, charNumber): - """ - Public 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() - 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. - """ - 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. - """ - 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.png"), - self.trUtf8("Check spelling..."), self.__checkSpellingWord) - self.spellingMenu.addAction(self.trUtf8("Add to dictionary"), - self.__addToSpellingDictionary) - self.spellingMenu.addAction(self.trUtf8("Ignore All"), - self.__ignoreSpellingAlways) - - self.emit(SIGNAL("showMenu"), "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"): + (1 << self.warning) + 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.setMarginWidth(self.__linenoMargin, + ' ' + '8' * Preferences.getEditor("LinenoWidth")) + else: + self.setMarginWidth(self.__linenoMargin, 0) + + # set folding margin settings + if Preferences.getEditor("FoldingMargin"): + self.setMarginWidth(self.__foldMargin, 16) + folding = Preferences.getEditor("FoldingStyle") + try: + folding = QsciScintilla.FoldStyle(folding) + except AttributeError: + pass + try: + self.setFolding(folding, self.__foldMargin) + except TypeError: + self.setFolding(folding) + self.setFoldMarginColors(Preferences.getEditorColour("FoldmarginBackground"), + Preferences.getEditorColour("FoldmarginBackground")) + else: + self.setMarginWidth(self.__foldMargin, 0) + try: + self.setFolding(QsciScintilla.NoFoldStyle, self.__foldMargin) + except TypeError: + self.setFolding(QsciScintilla.NoFoldStyle) + + def __setTextDisplay(self): + """ + Private method to configure the text display. + """ + self.setTabWidth(Preferences.getEditor("TabWidth")) + self.setIndentationWidth(Preferences.getEditor("IndentWidth")) + if self.lexer_ and self.lexer_.alwaysKeepTabs(): + self.setIndentationsUseTabs(True) + else: + self.setIndentationsUseTabs(Preferences.getEditor("TabForIndentation")) + self.setTabIndents(Preferences.getEditor("TabIndents")) + self.setBackspaceUnindents(Preferences.getEditor("TabIndents")) + self.setIndentationGuides(Preferences.getEditor("IndentationGuides")) + if Preferences.getEditor("ShowWhitespace"): + self.setWhitespaceVisibility(QsciScintilla.WsVisible) + else: + self.setWhitespaceVisibility(QsciScintilla.WsInvisible) + self.setEolVisibility(Preferences.getEditor("ShowEOL")) + self.setAutoIndent(Preferences.getEditor("AutoIndentation")) + if Preferences.getEditor("BraceHighlighting"): + self.setBraceMatching(QsciScintilla.SloppyBraceMatch) + else: + self.setBraceMatching(QsciScintilla.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.Highlight)) + if Preferences.getEditor("ColourizeSelText"): + self.resetSelectionForegroundColor() + elif Preferences.getEditor("CustomSelectionColours"): + self.setSelectionForegroundColor(\ + Preferences.getEditorColour("SelectionForeground")) + else: + self.setSelectionForegroundColor(\ + QApplication.palette().color(QPalette.HighlightedText)) + self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol")) + self.setCaretForegroundColor( + Preferences.getEditorColour("CaretForeground")) + self.setCaretLineBackgroundColor( + Preferences.getEditorColour("CaretLineBackground")) + self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible")) + self.caretWidth = Preferences.getEditor("CaretWidth") + self.setCaretWidth(self.caretWidth) + 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")) + + if Preferences.getEditor("WrapLongLines"): + self.setWrapMode(QsciScintilla.WrapWord) + self.setWrapVisualFlags(\ + QsciScintilla.WrapFlagByBorder, QsciScintilla.WrapFlagByBorder) + else: + self.setWrapMode(QsciScintilla.WrapNone) + self.setWrapVisualFlags(\ + QsciScintilla.WrapFlagNone, QsciScintilla.WrapFlagNone) + + 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() + + def __setEolMode(self): + """ + Private method to configure the eol mode of the editor. + """ + eolMode = Preferences.getEditor("EOLMode") + eolMode = QsciScintilla.EolMode(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.setAutoCompletionShowSingle( + Preferences.getEditor("AutoCompletionShowSingle")) + autoCompletionSource = Preferences.getEditor("AutoCompletionSource") + if autoCompletionSource == QsciScintilla.AcsDocument: + self.setAutoCompletionSource(QsciScintilla.AcsDocument) + elif autoCompletionSource == QsciScintilla.AcsAPIs: + self.setAutoCompletionSource(QsciScintilla.AcsAPIs) + else: + self.setAutoCompletionSource(QsciScintilla.AcsAll) + if Preferences.getEditor("AutoCompletionEnabled"): + if self.__acHookFunction is None: + self.setAutoCompletionThreshold( + Preferences.getEditor("AutoCompletionThreshold")) + else: + self.setAutoCompletionThreshold(0) + else: + self.setAutoCompletionThreshold(-1) + self.setAutoCompletionSource(QsciScintilla.AcsNone) + + def __setCallTips(self): + """ + Private method to configure the calltips function. + """ + if Preferences.getEditor("CallTipsEnabled"): + self.setCallTipsBackgroundColor( + Preferences.getEditorColour("CallTipsBackground")) + self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible")) + calltipsStyle = Preferences.getEditor("CallTipsStyle") + if calltipsStyle == QsciScintilla.CallTipsNoContext: + self.setCallTipsStyle(QsciScintilla.CallTipsNoContext) + elif calltipsStyle == QsciScintilla.CallTipsNoAutoCompletionContext: + self.setCallTipsStyle(QsciScintilla.CallTipsNoAutoCompletionContext) + else: + self.setCallTipsStyle(QsciScintilla.CallTipsContext) + else: + self.setCallTipsStyle(QsciScintilla.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. + """ + acs = Preferences.getEditor("AutoCompletionSource") + if acs == QsciScintilla.AcsDocument: + self.autoCompleteFromDocument() + elif acs == QsciScintilla.AcsAPIs: + self.autoCompleteFromAPIs() + elif acs == QsciScintilla.AcsAll: + self.autoCompleteFromAll() + else: + QMessageBox.information(None, + self.trUtf8("Autocompletion"), + self.trUtf8("""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: + self.setAutoCompletionThreshold( + Preferences.getEditor("AutoCompletionThreshold")) + autoCompletionSource = Preferences.getEditor("AutoCompletionSource") + if autoCompletionSource == QsciScintilla.AcsDocument: + self.setAutoCompletionSource(QsciScintilla.AcsDocument) + elif autoCompletionSource == QsciScintilla.AcsAPIs: + self.setAutoCompletionSource(QsciScintilla.AcsAPIs) + else: + self.setAutoCompletionSource(QsciScintilla.AcsAll) + else: + self.setAutoCompletionThreshold(-1) + self.setAutoCompletionSource(QsciScintilla.AcsNone) + + 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): + """ + Public slot called to handle the user entering a character. + + @param charNumber value of the character entered (integer) + """ + if self.isListActive(): + char = chr(charNumber) + if self.__isStartChar(char): + self.cancelList() + self.autoComplete(auto = True, context = True) + return + elif char == '(': + self.cancelList() + + if self.callTipsStyle() != QsciScintilla.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() + for wsep in wseps: + if wsep.endswith(ch): + return True + + return False + + def setAutoCompletionHook(self, func): + """ + Public method to set an autocompletion hook. + + @param func Function to be set to handle autocompletion. func + should be a function taking a reference to the editor and + a boolean indicating to complete a context. + """ + if self.autoCompletionThreshold() > 0: + self.setAutoCompletionThreshold(0) + self.__acHookFunction = func + self.connect(self, SIGNAL("SCN_CHARADDED(int)"), self.__charAdded) + + def unsetAutoCompletionHook(self): + """ + Public method to unset a previously installed autocompletion hook. + """ + self.disconnect(self, SIGNAL("SCN_CHARADDED(int)"), self.__charAdded) + self.__acHookFunction = None + if self.autoCompletionThreshold() == 0: + self.setAutoCompletionThreshold( + Preferences.getEditor("AutoCompletionThreshold")) + + def autoCompletionHook(self): + """ + Public method to get the autocompletion hook function. + + @return function set by setAutoCompletionHook() + """ + return self.__acHookFunction + + def autoComplete(self, auto = False, context = True): + """ + Public method to start autocompletion. + + @keyparam auto flag indicating a call from the __charAdded method (boolean) + @keyparam context flag indicating to complete a context (boolean) + """ + if auto and self.autoCompletionThreshold() == -1: + # autocompletion is disabled + return + + if self.__acHookFunction is not None: + self.__acHookFunction(self, context) + elif not auto: + self.autoCompleteQScintilla() + elif self.autoCompletionSource() != QsciScintilla.AcsNone: + self.autoCompleteQScintilla() + + def callTip(self): + """ + Public method to show calltips. + """ + if self.__ctHookFunction is not None: + self.__callTip() + else: + QsciScintillaCompat.callTip(self) + + 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.SendScintilla(QsciScintilla.SCI_CALLTIPCANCEL) + + if not found: + return + + try: + callTips = self.__ctHookFunction(self, pos, commas) + except TypeError: + # for backward compatibility + callTips = self.__ctHookFunction(self, pos) + if len(callTips) == 0: + if Preferences.getEditor("CallTipsScintillaOnFail"): + # try QScintilla calltips + QsciScintillaCompat.callTip(self) + return + + ctshift = 0 + for ct in callTips: + shift = ct.index("(") + if ctshift < shift: + ctshift = shift + + cv = self.callTipsVisible() + if cv > 0: + # this is just a safe guard + ct = "\n".join(callTips[:cv]) + else: + # until here and unindent below + ct = "\n".join(callTips) + + self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW, + self.__adjustedCallTipPosition(ctshift, pos), ct) + if '\n' in ct: + return + + # Highlight the current argument + if commas == 0: + astart = ct.find('(') + else: + astart = ct.find(',') + commas -= 1 + while astart != -1 and commas > 0: + astart = ct.find(',', astart + 1) + commas -= 1 + + if astart == -1: + return + + depth = 0 + for aend in range(astart + 1, len(ct)): + ch = ct[aend] + + if ch == ',' and depth == 0: + break + elif ch == '(': + depth += 1 + elif ch == ')': + if depth == 0: + break + + depth -= 1 + + if astart != aend: + self.SendScintilla(QsciScintilla.SCI_CALLTIPSETHLT, astart, 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 = ct - ctshift + return ct + + def setCallTipHook(self, func): + """ + Public method to set a calltip hook. + + @param func Function to be set to determine 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. + """ + self.__ctHookFunction = func + + def unsetCallTipHook(self): + """ + Public method to unset a calltip hook. + """ + self.__ctHookFunction = None + + def callTipHook(self): + """ + Public method to get the calltip hook function. + + @return function set by setCallTipHook() + """ + return self.__ctHookFunction + + ################################################################# + ## 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): + """ + Private 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.__unifiedMargins: + self.marginMenu.popup(evt.globalPos()) + else: + 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()) + + def __showContextMenu(self): + """ + Private slot handling the aboutToShow signal of the context menu. + """ + self.menuActs["Save"].setEnabled(self.isModified()) + self.menuActs["Undo"].setEnabled(self.isUndoAvailable()) + self.menuActs["Redo"].setEnabled(self.isRedoAvailable()) + self.menuActs["Revert"].setEnabled(self.isModified()) + if not self.miniMenu: + self.menuActs["Cut"].setEnabled(self.hasSelectedText()) + self.menuActs["Copy"].setEnabled(self.hasSelectedText()) + if not self.isResourcesFile: + if self.fileName and \ + (self.isPyFile() or self.isPy3File()): + self.menuActs["Show"].setEnabled(True) + else: + self.menuActs["Show"].setEnabled(False) + if self.fileName and \ + (self.isPyFile() or self.isPy3File() 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()) + self.menuActs["StreamComment"].setEnabled(self.lexer_.canStreamComment()) + self.menuActs["BoxComment"].setEnabled(self.lexer_.canBoxComment()) + else: + self.menuActs["Comment"].setEnabled(False) + self.menuActs["Uncomment"].setEnabled(False) + self.menuActs["StreamComment"].setEnabled(False) + self.menuActs["BoxComment"].setEnabled(False) + + self.menuActs["TypingAidsEnabled"].setEnabled(self.completer is not None) + self.menuActs["TypingAidsEnabled"].setChecked(\ + self.completer is not None and self.completer.isEnabled()) + + 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.emit(SIGNAL("showMenu"), "Main", self.menu, self) + + def __showContextMenuAutocompletion(self): + """ + Private slot called before the autocompletion menu is shown. + """ + self.menuActs["acDynamic"].setEnabled( + self.acAPI or self.__acHookFunction is not None) + self.menuActs["acAPI"].setEnabled(self.acAPI) + self.menuActs["acAPIDocument"].setEnabled(self.acAPI) + self.menuActs["calltip"].setEnabled(self.acAPI) + + self.emit(SIGNAL("showMenu"), "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 + project = e5App().getObject("Project") + if project.isOpen() and project.isProjectSource(self.fileName): + fn = project.getMainScript(True) + if fn is not None: + tfn = Utilities.getTestFileName(fn) + basename = os.path.splitext(fn)[0] + tbasename = os.path.splitext(tfn)[0] + prEnable = prEnable or \ + os.path.isfile("%s.profile" % basename) or \ + os.path.isfile("%s.profile" % tbasename) + coEnable = coEnable or \ + os.path.isfile("%s.coverage" % basename) or \ + os.path.isfile("%s.coverage" % tbasename) + + # now check ourself + fn = self.getFileName() + if fn is not None: + tfn = Utilities.getTestFileName(fn) + basename = os.path.splitext(fn)[0] + tbasename = os.path.splitext(tfn)[0] + prEnable = prEnable or \ + os.path.isfile("%s.profile" % basename) or \ + os.path.isfile("%s.profile" % tbasename) + coEnable = coEnable or \ + os.path.isfile("%s.coverage" % basename) or \ + os.path.isfile("%s.coverage" % tbasename) + + # now check for syntax errors + if self.hasSyntaxErrors(): + coEnable = False + + self.profileMenuAct.setEnabled(prEnable) + self.coverageMenuAct.setEnabled(coEnable) + self.coverageShowAnnotationMenuAct.setEnabled(\ + coEnable and not self.coverageMarkersShown) + self.coverageHideAnnotationMenuAct.setEnabled(\ + self.coverageMarkersShown) + + self.emit(SIGNAL("showMenu"), "Show", self.showMenu, self) + + def __showContextMenuGraphics(self): + """ + Private slot handling the aboutToShow signal of the diagrams context menu. + """ + project = e5App().getObject("Project") + if project.isOpen() and project.isProjectSource(self.fileName): + self.applicationDiagramMenuAct.setEnabled(True) + else: + self.applicationDiagramMenuAct.setEnabled(False) + + self.emit(SIGNAL("showMenu"), "Graphics", self.graphicsMenu, self) + + def __showContextMenuMargin(self): + """ + Private slot handling the aboutToShow signal of the margins context menu. + """ + if self.fileName and \ + (self.isPyFile() or self.isPy3File() or self.isRubyFile()): + self.marginMenuActs["Breakpoint"].setEnabled(True) + self.marginMenuActs["TempBreakpoint"].setEnabled(True) + if self.markersAtLine(self.line) & self.breakpointMask: + self.marginMenuActs["EditBreakpoint"].setEnabled(True) + self.marginMenuActs["EnableBreakpoint"].setEnabled(True) + else: + self.marginMenuActs["EditBreakpoint"].setEnabled(False) + self.marginMenuActs["EnableBreakpoint"].setEnabled(False) + if self.markersAtLine(self.line) & (1 << self.dbreakpoint): + self.marginMenuActs["EnableBreakpoint"].setText(\ + self.trUtf8('Enable breakpoint')) + else: + self.marginMenuActs["EnableBreakpoint"].setText(\ + self.trUtf8('Disable breakpoint')) + if self.breaks: + self.marginMenuActs["NextBreakpoint"].setEnabled(True) + self.marginMenuActs["PreviousBreakpoint"].setEnabled(True) + self.marginMenuActs["ClearBreakpoint"].setEnabled(True) + else: + self.marginMenuActs["NextBreakpoint"].setEnabled(False) + self.marginMenuActs["PreviousBreakpoint"].setEnabled(False) + self.marginMenuActs["ClearBreakpoint"].setEnabled(False) + else: + self.marginMenuActs["Breakpoint"].setEnabled(False) + self.marginMenuActs["TempBreakpoint"].setEnabled(False) + self.marginMenuActs["EditBreakpoint"].setEnabled(False) + self.marginMenuActs["EnableBreakpoint"].setEnabled(False) + self.marginMenuActs["NextBreakpoint"].setEnabled(False) + self.marginMenuActs["PreviousBreakpoint"].setEnabled(False) + self.marginMenuActs["ClearBreakpoint"].setEnabled(False) + + if self.bookmarks: + self.marginMenuActs["NextBookmark"].setEnabled(True) + self.marginMenuActs["PreviousBookmark"].setEnabled(True) + self.marginMenuActs["ClearBookmark"].setEnabled(True) + else: + self.marginMenuActs["NextBookmark"].setEnabled(False) + self.marginMenuActs["PreviousBookmark"].setEnabled(False) + self.marginMenuActs["ClearBookmark"].setEnabled(False) + + if len(self.syntaxerrors): + self.marginMenuActs["GotoSyntaxError"].setEnabled(True) + self.marginMenuActs["ClearSyntaxError"].setEnabled(True) + if self.markersAtLine(self.line) & (1 << self.syntaxerror): + self.marginMenuActs["ShowSyntaxError"].setEnabled(True) + else: + self.marginMenuActs["ShowSyntaxError"].setEnabled(False) + else: + self.marginMenuActs["GotoSyntaxError"].setEnabled(False) + self.marginMenuActs["ClearSyntaxError"].setEnabled(False) + self.marginMenuActs["ShowSyntaxError"].setEnabled(False) + + if len(self.warnings): + self.marginMenuActs["NextWarningMarker"].setEnabled(True) + self.marginMenuActs["PreviousWarningMarker"].setEnabled(True) + self.marginMenuActs["ClearWarnings"].setEnabled(True) + if self.markersAtLine(self.line) & (1 << self.warning): + self.marginMenuActs["ShowWarning"].setEnabled(True) + else: + self.marginMenuActs["ShowWarning"].setEnabled(False) + else: + self.marginMenuActs["NextWarningMarker"].setEnabled(False) + self.marginMenuActs["PreviousWarningMarker"].setEnabled(True) + self.marginMenuActs["ClearWarnings"].setEnabled(False) + self.marginMenuActs["ShowWarning"].setEnabled(False) + + if self.notcoveredMarkers: + self.marginMenuActs["NextCoverageMarker"].setEnabled(True) + self.marginMenuActs["PreviousCoverageMarker"].setEnabled(True) + else: + self.marginMenuActs["NextCoverageMarker"].setEnabled(False) + self.marginMenuActs["PreviousCoverageMarker"].setEnabled(False) + + if self.__hasTaskMarkers: + self.marginMenuActs["PreviousTaskMarker"].setEnabled(True) + self.marginMenuActs["NextTaskMarker"].setEnabled(True) + else: + self.marginMenuActs["PreviousTaskMarker"].setEnabled(False) + self.marginMenuActs["NextTaskMarker"].setEnabled(False) + + self.emit(SIGNAL("showMenu"), "Margin", self.sender(), self) + + def __showContextMenuChecks(self): + """ + Private slot handling the aboutToShow signal of the checks context menu. + """ + self.emit(SIGNAL("showMenu"), "Checks", self.checksMenu, self) + + def __contextSave(self): + """ + Private slot handling the save context menu entry. + """ + ok, newName = self.saveFile() + if ok: + self.vm.setEditorName(self, newName) + + def __contextSaveAs(self): + """ + Private slot handling the save as context menu entry. + """ + ok, newName = self.saveFileAs() + if ok: + self.vm.setEditorName(self, newName) + + 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 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 self.fileName is not None and \ + not self.autosaveManuallyDisabled and \ + not self.isReadOnly() + + def __autoSyntaxCheck(self): + """ + Private method to perform an automatic syntax check of the file. + """ + if Preferences.getEditor("AutoCheckSyntax"): + self.clearSyntaxError() + self.clearFlakesWarnings() + if self.isPy3File(): + syntaxError, _fn, errorline, _code, _error = \ + Utilities.compile(self.fileName, self.text()) + if syntaxError: + self.toggleSyntaxError(int(errorline), True, _error) + else: + if Preferences.getFlakes("IncludeInSyntaxCheck"): + ignoreStarImportWarnings = \ + Preferences.getFlakes("IgnoreStarImportWarnings") + warnings = Checker(self.text(), self.fileName) + warnings.messages.sort(key = lambda a: a.lineno) + for warning in warnings.messages: + if ignoreStarImportWarnings and \ + isinstance(warning, ImportStarUsed): + continue + + _fn, lineno, message = warning.getMessageData() + self.toggleFlakesWarning(lineno, True, message) + + def __showCodeMetrics(self): + """ + Private method to handle the code metrics context menu action. + """ + if not self.checkDirty(): + return + + self.codemetrics = CodeMetricsDialog() + self.codemetrics.show() + self.codemetrics.start(self.fileName) + + def __getCodeCoverageFile(self): + """ + Private method to get the filename of the file containing coverage info. + + @return filename of the coverage file (string) + """ + files = [] + + # first check if the file belongs to a project and there is + # a project coverage file + project = e5App().getObject("Project") + if project.isOpen() and project.isProjectSource(self.fileName): + fn = project.getMainScript(True) + if fn is not None: + tfn = Utilities.getTestFileName(fn) + basename = os.path.splitext(fn)[0] + tbasename = os.path.splitext(tfn)[0] + + f = "%s.coverage" % basename + tf = "%s.coverage" % tbasename + if os.path.isfile(f): + files.append(f) + if os.path.isfile(tf): + files.append(tf) + + # now check, if there are coverage files belonging to ourself + fn = self.getFileName() + if fn is not None: + tfn = Utilities.getTestFileName(fn) + basename = os.path.splitext(fn)[0] + tbasename = os.path.splitext(tfn)[0] + + f = "%s.coverage" % basename + tf = "%s.coverage" % tbasename + if os.path.isfile(f) and not f in files: + files.append(f) + if os.path.isfile(tf) and not tf in files: + files.append(tf) + + if files: + if len(files) > 1: + fn, ok = QInputDialog.getItem(\ + self, + self.trUtf8("Code Coverage"), + self.trUtf8("Please select a coverage file"), + files, + 0, False) + if not ok: + return + else: + fn = files[0] + else: + fn = None + + return fn + + def __showCodeCoverage(self): + """ + Private method to handle the code coverage context menu action. + """ + fn = self.__getCodeCoverageFile() + if fn: + self.codecoverage = PyCoverageDialog() + self.codecoverage.show() + self.codecoverage.start(fn, self.fileName) + + def __codeCoverageShowAnnotations(self): + """ + Private method to handle the show code coverage annotations context menu action. + """ + fn = self.__getCodeCoverageFile() + if fn: + cover = coverage(data_file = fn) + cover.use_cache(True) + 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.emit(SIGNAL('coverageMarkersShown'), True) + self.coverageMarkersShown = True + else: + QMessageBox.information(None, + self.trUtf8("Show Code Coverage Annotations"), + self.trUtf8("""All lines have been covered.""")) + else: + QMessageBox.warning(None, + self.trUtf8("Show Code Coverage Annotations"), + self.trUtf8("""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 = [] + self.emit(SIGNAL('coverageMarkersShown'), False) + self.coverageMarkersShown = False + + def hasCoverageMarkers(self): + """ + Public method to test, if there are coverage markers. + """ + 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 = [] + + # first check if the file belongs to a project and there is + # a project profile file + project = e5App().getObject("Project") + if project.isOpen() and project.isProjectSource(self.fileName): + fn = project.getMainScript(True) + if fn is not None: + tfn = Utilities.getTestFileName(fn) + basename = os.path.splitext(fn)[0] + tbasename = os.path.splitext(tfn)[0] + + f = "%s.profile" % basename + tf = "%s.profile" % tbasename + if os.path.isfile(f): + files.append(f) + if os.path.isfile(tf): + files.append(tf) + + # now check, if there are profile files belonging to ourself + fn = self.getFileName() + if fn is not None: + tfn = Utilities.getTestFileName(fn) + basename = os.path.splitext(fn)[0] + tbasename = os.path.splitext(tfn)[0] + + f = "%s.profile" % basename + tf = "%s.profile" % tbasename + if os.path.isfile(f) and not f in files: + files.append(f) + if os.path.isfile(tf) and not tf in files: + files.append(tf) + + if files: + if len(files) > 1: + fn, ok = QInputDialog.getItem(\ + self, + self.trUtf8("Profile Data"), + self.trUtf8("Please select a profile file"), + files, + 0, False) + if not ok: + return + else: + fn = files[0] + else: + return + + 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, error, msg = ""): + """ + Public method to toggle a syntax error indicator. + + @param line line number of the syntax error + @param error flag indicating if the error marker should be + set or deleted (boolean) + @param msg error message (string) + """ + 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) + if not (markers & (1 << self.syntaxerror)): + handle = self.markerAdd(line - 1, self.syntaxerror) + self.syntaxerrors[handle] = msg + self.emit(SIGNAL('syntaxerrorToggled'), self) + else: + for handle in list(self.syntaxerrors.keys()): + if self.markerLine(handle) == line - 1: + del self.syntaxerrors[handle] + self.markerDeleteHandle(handle) + self.emit(SIGNAL('syntaxerrorToggled'), self) + + 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 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: + self.setCursorPosition(seline, 0) + 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, False) + + 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: + QMessageBox.critical(None, + self.trUtf8("Syntax Error"), + self.syntaxerrors[handle]) + break + else: + QMessageBox.critical(None, + self.trUtf8("Syntax Error"), + self.trUtf8("No syntax error message available.")) + + ############################################################################ + ## Flakes warning handling methods below + ############################################################################ + + def toggleFlakesWarning(self, line, warning, msg = ""): + """ + Public method to toggle a flakes warning indicator. + + @param line line number of the flakes warning + @param erwarningror flag indicating if the warning marker should be + set or deleted (boolean) + @param msg warning message (string) + """ + if line == 0: + line = 1 + # hack to show a warning marker, if line is reported to be 0 + if warning: + # set a new warning marker + markers = self.markersAtLine(line - 1) + if not (markers & (1 << self.warning)): + handle = self.markerAdd(line - 1, self.warning) + self.warnings[handle] = msg + self.emit(SIGNAL('syntaxerrorToggled'), self) + else: + for handle in list(self.warnings.keys()): + if self.markerLine(handle) == line - 1: + del self.warnings[handle] + self.markerDeleteHandle(handle) + self.emit(SIGNAL('syntaxerrorToggled'), self) + + def getFlakesWarnings(self): + """ + Public method to retrieve the flakes warning markers. + + @return sorted list of all lines containing a flakes warning + (list of integer) + """ + fwlist = [] + for handle in list(self.warnings.keys()): + fwlist.append(self.markerLine(handle) + 1) + + fwlist.sort() + return fwlist + + def hasFlakesWarnings(self): + """ + Public method to check for the presence of flakes warnings. + + @return flag indicating the presence of flakes warnings (boolean) + """ + return len(self.warnings) > 0 + + def nextFlakesWarning(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 previousFlakesWarning(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 handle the 'Clear all warnings' context menu action. + """ + for handle in self.warnings: + self.markerDeleteHandle(handle) + self.warnings = {} + self.emit(SIGNAL('syntaxerrorToggled'), self) + + def __showFlakesWarning(self, line = -1): + """ + Private slot to handle the 'Show warning' context menu action. + + @param line line number to show the flakes warning for (integer) + """ + if line == -1: + line = self.line + + for handle in list(self.warnings.keys()): + if self.markerLine(handle) == line: + QMessageBox.warning(None, + self.trUtf8("py3flakes Warning"), + self.warnings[handle]) + break + else: + QMessageBox.warning(None, + self.trUtf8("py3flakes Warning"), + self.trUtf8("No py3flakes warning message available.")) + + ################################################################# + ## 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.trUtf8("Macro Name"), + self.trUtf8("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 = QFileDialog.getOpenFileName( + self, + self.trUtf8("Load macro file"), + configDir, + self.trUtf8("Macro files (*.macro)")) + + if not fname: + return # user aborted + + try: + f = open(fname, "r", encoding = "utf-8") + lines = f.readlines() + f.close() + except IOError: + QMessageBox.critical(self, + self.trUtf8("Error loading macro"), + self.trUtf8("<p>The macro file <b>{0}</b> could not be read.</p>") + .format(fname)) + return + + if len(lines) != 2: + QMessageBox.critical(self, + self.trUtf8("Error loading macro"), + self.trUtf8("<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 = QFileDialog.getSaveFileNameAndFilter(\ + self, + self.trUtf8("Save macro file"), + configDir, + self.trUtf8("Macro files (*.macro)"), + "", + QFileDialog.Options(QFileDialog.DontConfirmOverwrite)) + + if not fname: + return # user aborted + + ext = QFileInfo(fname).suffix() + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fname += ex + if QFileInfo(fname).exists(): + res = QMessageBox.warning(self, + self.trUtf8("Save macro"), + self.trUtf8("<p>The macro file <b>{0}</b> already exists.</p>") + .format(fname), + QMessageBox.StandardButtons(\ + QMessageBox.Abort | \ + QMessageBox.Save), + QMessageBox.Abort) + if res == QMessageBox.Abort or res == QMessageBox.Cancel: + return + fname = Utilities.toNativeSeparators(fname) + + try: + f = open(fname, "w", encoding = "utf-8") + f.write("%s%s" % (name, os.linesep)) + f.write(self.macros[name].save()) + f.close() + except IOError: + QMessageBox.critical(self, + self.trUtf8("Error saving macro"), + self.trUtf8("<p>The macro file <b>{0}</b> could not be written.</p>") + .format(fname)) + return + + def macroRecordingStart(self): + """ + Public method to start macro recording. + """ + if self.recording: + res = QMessageBox.warning(self, + self.trUtf8("Start Macro Recording"), + self.trUtf8("Macro recording is already active. Start new?"), + QMessageBox.StandardButtons(\ + QMessageBox.No | \ + QMessageBox.Yes), + QMessageBox.Yes) + if res == QMessageBox.Yes: + 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.trUtf8("Macro Recording"), + self.trUtf8("Enter name of the macro:"), + QLineEdit.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. + """ + QsciScintillaCompat.undo(self) + self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable()) + self.emit(SIGNAL('redoAvailable'), self.isRedoAvailable()) + + def redo(self): + """ + Public method to redo the last recorded change. + """ + QsciScintillaCompat.redo(self) + self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable()) + self.emit(SIGNAL('redoAvailable'), 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 self.vm.closeEditor(self) + + def closeIt(self): + """ + Public method called by the viewmanager to finally get rid of us. + """ + if Preferences.getEditor("ClearBreaksOnClose"): + self.__menuClearBreakpoints() + + for clone in self.__clones[:]: + self.removeClone(clone) + clone.removeClone(self) + + self.disconnect(self.breakpointModel, + SIGNAL("rowsAboutToBeRemoved(const QModelIndex &, int, int)"), + self.__deleteBreakPoints) + self.disconnect(self.breakpointModel, + SIGNAL("dataAboutToBeChanged(const QModelIndex &, const QModelIndex &)"), + self.__breakPointDataAboutToBeChanged) + self.disconnect(self.breakpointModel, + SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), + self.__changeBreakPoints) + self.disconnect(self.breakpointModel, + SIGNAL("rowsInserted(const QModelIndex &, int, int)"), + self.__addBreakPoints) + + self.disconnect(e5App().getObject("Project"), SIGNAL("projectPropertiesChanged"), + self.__projectPropertiesChanged) + + if self.spell: + self.spell.stopIncrementalCheck() + + QsciScintillaCompat.close(self) + + def keyPressEvent(self, ev): + """ + Re-implemented to handle the user input a key at a time. + + @param ev key event (QKeyEvent) + """ + txt = ev.text() + key = ev.key() + + # See it is text to insert. + if len(txt) and txt >= " ": + QsciScintillaCompat.keyPressEvent(self, 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 (QFocusEvent) + """ + self.recolor() + self.vm.editActGrp.setEnabled(True) + self.vm.editorActGrp.setEnabled(True) + self.vm.copyActGrp.setEnabled(True) + self.vm.viewActGrp.setEnabled(True) + try: + self.setCaretWidth(self.caretWidth) + except AttributeError: + pass + self.__updateReadOnly(False) + if self.vm.editorsCheckFocusInEnabled() and \ + not self.inReopenPrompt and self.fileName and \ + QFileInfo(self.fileName).lastModified().toString() != \ + self.lastModified.toString(): + if Preferences.getEditor("AutoReopen") and not self.isModified(): + self.refresh() + else: + self.inReopenPrompt = True + msg = self.trUtf8(\ + """<p>The file <b>{0}</b> has been changed while it was opened in""" + """ eric5. Reread it?</p>""").format(self.fileName) + default = QMessageBox.No + if self.isModified(): + msg += self.trUtf8(\ + """<br><b>Warning:</b> You will loose""" + """ your changes upon reopening it.""") + default = QMessageBox.Ok + res = QMessageBox.warning(None, + self.trUtf8("File changed"), msg, + QMessageBox.StandardButtons(\ + QMessageBox.Yes | \ + QMessageBox.No), + default) + if res == QMessageBox.Yes: + self.refresh() + else: + # do not prompt for this change again... + self.lastModified = QFileInfo(self.fileName).lastModified() + self.inReopenPrompt = False + + QsciScintillaCompat.focusInEvent(self, event) + + def focusOutEvent(self, event): + """ + Public method called when the editor loses focus. + + @param event the event object (QFocusEvent) + """ + self.vm.editorActGrp.setEnabled(False) + self.setCaretWidth(0) + + QsciScintillaCompat.focusOutEvent(self, 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 (QEvent) + @return flag indicating if the event could be processed (bool) + """ + if evt.type() == QEvent.WindowStateChange and \ + self.fileName is not None: + if self.windowState() == Qt.WindowStates(Qt.WindowMinimized): + cap = os.path.basename(self.fileName) + else: + cap = self.fileName + if self.isReadOnly(): + cap = self.trUtf8("{0} (ro)").format(cap) + self.setWindowTitle(cap) + + QsciScintillaCompat.changeEvent(self, evt) + + def mousePressEvent(self, event): + """ + Protected method to handle the mouse press event. + + @param event the mouse press event (QMouseEvent) + """ + self.vm.eventFilter(self, event) + QsciScintillaCompat.mousePressEvent(self, event) + + 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 is None: + return + readOnly = not QFileInfo(self.fileName).isWritable() + if not bForce and (readOnly == self.isReadOnly()): + return + cap = self.fileName + if readOnly: + cap = self.trUtf8("{0} (ro)".format(cap)) + self.setReadOnly(readOnly) + self.setWindowTitle(cap) + self.emit(SIGNAL('captionChanged'), 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.clearFlakesWarnings() + + # clear breakpoint markers + for handle in list(self.breaks.keys()): + self.markerDeleteHandle(handle) + self.breaks = {} + + # reread the file + try: + self.readFile(self.fileName) + except IOError: + # do not prompt for this change again... + self.lastModified = QDateTime.currentDateTime() + self.setModified(False) + + # 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.emit(SIGNAL('editorSaved'), self.fileName) + self.__autoSyntaxCheck() + + 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: + f = Preferences.getEditorOtherFonts("MonospacedFont") + self.monospacedStyles(f) + else: + if not self.lexer_: + self.clearStyles() + self.__setMarginsDisplay() + self.setFont(Preferences.getEditorOtherFonts("DefaultFont")) + + self.useMonospaced = on + + ################################################################# + ## 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: + QsciScintillaCompat.dragEnterEvent(self, 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: + QsciScintillaCompat.dragMoveEvent(self, 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: + QsciScintillaCompat.dragLeaveEvent(self, 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 QFileInfo(fname).isDir(): + self.vm.openSourceFile(fname) + else: + QMessageBox.information(None, + self.trUtf8("Drop Error"), + self.trUtf8("""<p><b>{0}</b> is not a file.</p>""") + .format(fname)) + event.acceptProposedAction() + else: + QsciScintillaCompat.dropEvent(self, event) + + self.inDragDrop = False + + ################################################################# + ## Support for Qt resources files + ################################################################# + + def __initContextMenuResources(self): + """ + Private method used to setup the Resources context sub menu. + """ + menu = QMenu(self.trUtf8('Resources')) + + menu.addAction(self.trUtf8('Add file...'), + self.__addFileResource) + menu.addAction(self.trUtf8('Add files...'), + self.__addFileResources) + menu.addAction(self.trUtf8('Add aliased file...'), + self.__addFileAliasResource) + menu.addAction(self.trUtf8('Add localized resource...'), + self.__addLocalizedResource) + menu.addSeparator() + menu.addAction(self.trUtf8('Add resource frame'), + self.__addResourceFrame) + + self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuResources) + + return menu + + def __showContextMenuResources(self): + """ + Private slot handling the aboutToShow signal of the resources context menu. + """ + self.emit(SIGNAL("showMenu"), "Resources", self.resourcesMenu, self) + + def __addFileResource(self): + """ + Private method to handle the Add file context menu action. + """ + dirStr = os.path.dirname(self.fileName) + file = QFileDialog.getOpenFileName(\ + self, + self.trUtf8("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 = QFileDialog.getOpenFileNames(\ + self, + self.trUtf8("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 = QFileDialog.getOpenFileName(\ + self, + self.trUtf8("Add aliased file resource"), + dirStr, + "") + if file: + relFile = QDir(dirStr).relativeFilePath(file) + alias, ok = QInputDialog.getText(\ + self, + self.trUtf8("Add aliased file resource"), + self.trUtf8("Alias for file <b>{0}</b>:").format(relFile), + QLineEdit.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.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.UMLClassDiagram import UMLClassDiagram + if not self.checkDirty(): + return + + self.classDiagram = UMLClassDiagram(self.fileName, self, noAttrs = False) + self.classDiagram.show() + + def __showPackageDiagram(self): + """ + Private method to handle the Package Diagram context menu action. + """ + from Graphics.PackageDiagram import PackageDiagram + if not self.checkDirty(): + return + + package = os.path.isdir(self.fileName) and self.fileName \ + or os.path.dirname(self.fileName) + res = QMessageBox.question(None, + self.trUtf8("Package Diagram"), + self.trUtf8("""Include class attributes?"""), + QMessageBox.StandardButtons(\ + QMessageBox.No | \ + QMessageBox.Yes), + QMessageBox.Yes) + self.packageDiagram = PackageDiagram(package, self, + noAttrs = (res == QMessageBox.No)) + self.packageDiagram.show() + + def __showImportsDiagram(self): + """ + Private method to handle the Imports Diagram context menu action. + """ + from Graphics.ImportsDiagram import ImportsDiagram + if not self.checkDirty(): + return + + package = os.path.isdir(self.fileName) and self.fileName \ + or os.path.dirname(self.fileName) + res = QMessageBox.question(None, + self.trUtf8("Imports Diagram"), + self.trUtf8("""Include imports from external modules?"""), + QMessageBox.StandardButtons(\ + QMessageBox.No | \ + QMessageBox.Yes), + QMessageBox.No) + self.importsDiagram = ImportsDiagram(package, self, + showExternalImports = (res == QMessageBox.Yes)) + self.importsDiagram.show() + + def __showApplicationDiagram(self): + """ + Private method to handle the Imports Diagram context menu action. + """ + from Graphics.ApplicationDiagram import ApplicationDiagram + res = QMessageBox.question(None, + self.trUtf8("Application Diagram"), + self.trUtf8("""Include module names?"""), + QMessageBox.StandardButtons(\ + QMessageBox.No | \ + QMessageBox.Yes), + QMessageBox.Yes) + self.applicationDiagram = ApplicationDiagram(e5App().getObject("Project"), + self, noModules = (res == QMessageBox.No)) + self.applicationDiagram.show() + + ####################################################################### + ## 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) + + ####################################################################### + ## Autocompleting 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: + line, index = self.getCursorPosition() + tmplName = self.getWordLeft(line, index) + if tmplName: + if e5App().getObject("TemplateViewer").hasTemplate(tmplName): + self.__applyTemplate(tmplName) + return + else: + templateNames = \ + e5App().getObject("TemplateViewer").getTemplateNames(tmplName) + if len(templateNames) == 1: + self.__applyTemplate(templateNames[0]) + return + elif len(templateNames) > 1: + self.showUserList(TemplateCompletionListID, + ["%s?%d" % (t, self.TemplateImageID) for t in templateNames]) + return + + QsciScintillaCompat.editorCommand(self, cmd) + + def __completionListSelected(self, id, txt): + """ + Private slot to handle the selection from the completion list. + + @param id the ID of the user list (should be 1) (integer) + @param txt the selected text (string) + """ + if id == TemplateCompletionListID: + self.__applyTemplate(txt) + + def __applyTemplate(self, templateName): + """ + Private method to apply a template by name. + + @param templateName name of the template to apply (string) + """ + if e5App().getObject("TemplateViewer").hasTemplate(templateName): + self.extendSelectionWordLeft() + e5App().getObject("TemplateViewer").applyNamedTemplate(templateName) + + ####################################################################### + ## Project related methods + ####################################################################### + + def __projectPropertiesChanged(self): + """ + Private slot to handle changes of the project properties. + """ + project = e5App().getObject("Project") + if self.spell: + pwl, pel = project.getProjectDictionaries() + self.__setSpellingLanguage(project.getProjectSpellLanguage(), + pwl = pwl, pel = pel) + + def addedToProject(self): + """ + Public method to signal, that this editor has been added to a project. + """ + project = e5App().getObject("Project") + if self.spell: + pwl, pel = project.getProjectDictionaries() + self.__setSpellingLanguage(project.getProjectSpellLanguage(), + pwl = pwl, pel = pel) + self.connect(project, SIGNAL("projectPropertiesChanged"), + self.__projectPropertiesChanged) + + ####################################################################### + ## Spellchecking related methods + ####################################################################### + + def __setSpellingLanguage(self, language, pwl = "", pel = ""): + """ + Private slot to set the spell checking language. + + @param language spell checking language to be set (string) + @keyparam pwl name of the personal/project word list (string) + @keyparam 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.connect(e5App().getObject("Project"), SIGNAL("projectPropertiesChanged"), + self.__projectPropertiesChanged) + 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. + """ + project = e5App().getObject("Project") + if self.fileName and \ + project.isOpen() and \ + project.isProjectSource(self.fileName): + pwl, pel = project.getProjectDictionaries() + self.__setSpellingLanguage(project.getProjectSpellLanguage(), + pwl = pwl, pel = pel) + + def setAutoSpellChecking(self): + """ + Public method to set the automatic spell checking. + """ + if Preferences.getEditor("AutoSpellCheckingEnabled"): + self.connect(self, SIGNAL("SCN_CHARADDED(int)"), self.__spellCharAdded) + self.spell.checkDocumentIncrementally() + else: + self.disconnect(self, SIGNAL("SCN_CHARADDED(int)"), 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. + + @param pos position to be checked (integer) + @return flag indicating pos is in a spell check region (boolean) + """ + if self.__spellCheckStringsOnly: + style = self.styleAt(pos) + if self.lexer_ is not None: + return self.lexer_.isCommentStyle(style) or \ + self.lexer_.isStringStyle(style) + return True + + def __spellCharAdded(self, charNumber): + """ + Public 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() + 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. + """ + 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. + """ + 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.png"), + self.trUtf8("Check spelling..."), self.__checkSpellingWord) + self.spellingMenu.addAction(self.trUtf8("Add to dictionary"), + self.__addToSpellingDictionary) + self.spellingMenu.addAction(self.trUtf8("Ignore All"), + self.__ignoreSpellingAlways) + + self.emit(SIGNAL("showMenu"), "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()