eric6/QScintilla/Editor.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7924
8a96736d465e
parent 8004
38d359e2ded7
child 8142
43248bafe9b2
--- a/eric6/QScintilla/Editor.py	Sun Jan 17 13:53:08 2021 +0100
+++ b/eric6/QScintilla/Editor.py	Mon Feb 01 10:38:16 2021 +0100
@@ -4,7 +4,7 @@
 #
 
 """
-Module implementing the editor component of the eric6 IDE.
+Module implementing the editor component of the eric IDE.
 """
 
 import os
@@ -46,7 +46,7 @@
 
 class Editor(QsciScintillaCompat):
     """
-    Class implementing the editor component of the eric6 IDE.
+    Class implementing the editor component of the eric IDE.
     
     @signal modificationStatusChanged(bool, QsciScintillaCompat) emitted when
         the modification status has changed
@@ -157,6 +157,19 @@
         r"""^>>>>>>> .*?$""",
     )
     
+    EncloseChars = {
+        '"': '"',
+        "'": "'",
+        "(": "()",
+        ")": "()",
+        "{": "{}",          # __IGNORE_WARNING_M613__
+        "}": "{}",          # __IGNORE_WARNING_M613__
+        "[": "[]",
+        "]": "[]",
+        "<": "<>",
+        ">": "<>",
+    }
+    
     def __init__(self, dbs, fn="", vm=None,
                  filetype="", editor=None, tv=None):
         """
@@ -243,6 +256,8 @@
         self.__lastEditPosition = None
         self.__annotationLines = 0
         
+        self.__docstringGenerator = None
+        
         # list of clones
         self.__clones = []
         
@@ -810,6 +825,10 @@
                 self.tr('Box Comment'),
                 self.boxCommentLineOrSelection)
             self.menu.addSeparator()
+            self.menuActs["Docstring"] = self.menu.addAction(
+                self.tr("Generate Docstring"),
+                self.__insertDocstring)
+            self.menu.addSeparator()
             self.menu.addAction(
                 self.tr('Select to brace'), self.selectToMatchingBrace)
             self.menu.addAction(self.tr('Select all'), self.__selectAll)
@@ -1446,12 +1465,14 @@
                 self.setLanguage(self.supportedLanguages[language][1])
                 self.checkSyntax()
         
+        self.__docstringGenerator = None
+        
     def __languageChanged(self, language, propagate=True):
         """
         Private slot handling a change of a connected editor's language.
         
         @param language language to be set (string)
-        @keyparam propagate flag indicating to propagate the change (boolean)
+        @param propagate flag indicating to propagate the change (boolean)
         """
         if language == '':
             self.__resetLanguage(propagate=propagate)
@@ -1468,11 +1489,13 @@
                              propagate=propagate)
             self.checkSyntax()
         
+        self.__docstringGenerator = None
+        
     def __resetLanguage(self, propagate=True):
         """
         Private method used to reset the language selection.
         
-        @keyparam propagate flag indicating to propagate the change (boolean)
+        @param propagate flag indicating to propagate the change (boolean)
         """
         if (
             self.lexer_ is not None and
@@ -1494,6 +1517,8 @@
         self.setMonospaced(useMonospaced)
         self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
         
+        self.__docstringGenerator = None
+        
         if not self.inLanguageChanged and propagate:
             self.inLanguageChanged = True
             self.languageChanged.emit(self.apiLanguage)
@@ -1508,8 +1533,8 @@
             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)
+        @param propagate flag indicating to propagate the change (boolean)
+        @param pyname name of the pygments lexer to use (string)
         """
         # clear all warning and syntax error markers
         self.clearSyntaxError()
@@ -1523,6 +1548,8 @@
         self.recolor()
         self.__checkLanguage()
         
+        self.__docstringGenerator = None
+        
         # set the text display
         if initTextDisplay:
             self.__setTextDisplay()
@@ -1589,7 +1616,7 @@
         Private slot to handle a change of the encoding.
         
         @param encoding changed encoding (string)
-        @keyparam propagate flag indicating to propagate the change (boolean)
+        @param propagate flag indicating to propagate the change (boolean)
         """
         self.encoding = encoding
         self.__checkEncoding()
@@ -1703,7 +1730,7 @@
         
         @param filename filename used to determine the associated lexer
             language (string)
-        @keyparam pyname name of the pygments lexer to use (string)
+        @param pyname name of the pygments lexer to use (string)
         """
         if (
             self.lexer_ is not None and
@@ -1825,9 +1852,9 @@
         """
         Public method to retrieve the language of the editor.
         
-        @keyparam normalized flag indicating to normalize some Pygments
+        @param normalized flag indicating to normalize some Pygments
             lexer names (boolean)
-        @keyparam forPygments flag indicating to normalize some lexer
+        @param forPygments flag indicating to normalize some lexer
             names for Pygments (boolean)
         @return language of the editor (string)
         """
@@ -3073,9 +3100,9 @@
         Public slot to read the text from a file.
         
         @param fn filename to read from (string)
-        @keyparam createIt flag indicating the creation of a new file, if the
+        @param createIt flag indicating the creation of a new file, if the
             given one doesn't exist (boolean)
-        @keyparam encoding encoding to be used to read the file (string)
+        @param encoding encoding to be used to read the file (string)
             (Note: this parameter overrides encoding detection)
         """
         self.__loadEditorConfig(fileName=fn)
@@ -3399,7 +3426,7 @@
         Public slot to save a file with a new name.
         
         @param path directory to save the file in (string)
-        @keyparam toProject flag indicating a save to project operation
+        @param toProject flag indicating a save to project operation
             (boolean)
         @return tuple of two values (boolean, string) giving a success
             indicator and the name of the saved file
@@ -3448,7 +3475,7 @@
         
         @param line line number to make visible
         @type int
-        @keyparam expand flag indicating to expand all folds
+        @param expand flag indicating to expand all folds
         @type bool
         """
         self.ensureLineVisible(line - 1)
@@ -3463,7 +3490,7 @@
         
         @param line line number to make visible
         @type int
-        @keyparam expand flag indicating to expand all folds
+        @param expand flag indicating to expand all folds
         @type bool
         """
         self.ensureVisible(line)
@@ -3514,7 +3541,7 @@
         
         @param line number of line to look at (int)
         @param index position to look at (int)
-        @keyparam useWordChars flag indicating to use the wordCharacters
+        @param useWordChars flag indicating to use the wordCharacters
             method (boolean)
         @return tuple with start and end indexes of the word at the position
             (integer, integer)
@@ -3546,7 +3573,7 @@
         @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
+        @param useWordChars flag indicating to use the wordCharacters
             method (boolean)
         @return the word at that position (string)
         """
@@ -4198,12 +4225,12 @@
         
         @param line line number to go to
         @type int
-        @keyparam pos position in line to go to
+        @param pos position in line to go to
         @type int
-        @keyparam firstVisible flag indicating to make the line the first
+        @param firstVisible flag indicating to make the line the first
             visible line
         @type bool
-        @keyparam expand flag indicating to expand all folds
+        @param expand flag indicating to expand all folds
         @type bool
         """
         self.setCursorPosition(line - 1, pos - 1)
@@ -4809,7 +4836,9 @@
             Preferences.getDocuViewer("ShowInfoOnOpenParenthesis")
         ):
             self.vm.showEditorInfo(self)
-            
+        
+        self.__delayedDocstringMenuPopup(self.getCursorPosition())
+        
         if self.isListActive():
             if self.__isStartChar(char):
                 self.cancelList()
@@ -4931,9 +4960,9 @@
         """
         Public method to start auto-completion.
         
-        @keyparam auto flag indicating a call from the __charAdded method
+        @param auto flag indicating a call from the __charAdded method
             (boolean)
-        @keyparam context flag indicating to complete a context (boolean)
+        @param context flag indicating to complete a context (boolean)
         """
         if auto and not Preferences.getEditor("AutoCompletionEnabled"):
             # auto-completion is disabled
@@ -4982,9 +5011,9 @@
         """
         Private method to start auto-completion via plug-ins.
         
-        @keyparam auto flag indicating a call from the __charAdded method
+        @param auto flag indicating a call from the __charAdded method
             (boolean)
-        @keyparam context flag indicating to complete a context
+        @param context flag indicating to complete a context
         @type bool or None
         """
         self.__acCompletions.clear()
@@ -5479,6 +5508,11 @@
         
         self.menuActs["Tools"].setEnabled(not self.toolsMenu.isEmpty())
         
+        cline = self.getCursorPosition()[0]
+        line = self.text(cline)
+        self.menuActs["Docstring"].setEnabled(
+            self.getDocstringGenerator().isFunctionStart(line))
+        
         self.showMenu.emit("Main", self.menu, self)
         
     def __showContextMenuAutocompletion(self):
@@ -6202,7 +6236,7 @@
         @param error flag indicating if the error marker should be
             set or deleted (boolean)
         @param msg error message (string)
-        @keyparam show flag indicating to set the cursor to the error position
+        @param show flag indicating to set the cursor to the error position
             (boolean)
         """
         if line == 0:
@@ -6360,7 +6394,7 @@
         @param warning flag indicating if the warning marker should be
             set or deleted (boolean)
         @param msg warning message (string)
-        @keyparam warningType type of warning message (integer)
+        @param warningType type of warning message (integer)
         """
         if line == 0:
             line = 1
@@ -6935,12 +6969,41 @@
         """
         Protected method to handle the user input a key at a time.
         
-        @param ev key event (QKeyEvent)
-        """
+        @param ev key event
+        @type QKeyEvent
+        """
+        def encloseSelectedText(encString):
+            """
+            Local function to enclose the current selection with some
+            characters.
+            
+            @param encString string to use to enclose the selection
+                (one or two characters)
+            @type str
+            """
+            startChar = encString[0]
+            if len(encString) == 2:
+                endChar = encString[1]
+            else:
+                endChar = startChar
+            
+            sline, sindex, eline, eindex = self.getSelection()
+            replaceText = startChar + self.selectedText() + endChar
+            self.beginUndoAction()
+            self.replaceSelectedText(replaceText)
+            self.endUndoAction()
+            self.setSelection(sline, sindex + 1, eline, eindex + 1)
+        
         txt = ev.text()
         
         # See it is text to insert.
         if len(txt) and txt >= " ":
+            if self.hasSelectedText():
+                if txt in Editor.EncloseChars:
+                    encloseSelectedText(Editor.EncloseChars[txt])
+                    ev.accept()
+                    return
+            
             super(Editor, self).keyPressEvent(ev)
         else:
             ev.ignore()
@@ -6954,7 +7017,8 @@
         assuming, that it is in the vicinity of the old position after the
         reread.
         
-        @param event the event object (QFocusEvent)
+        @param event the event object
+        @type QFocusEvent
         """
         self.recolor()
         self.vm.editActGrp.setEnabled(True)
@@ -6979,7 +7043,7 @@
             else:
                 msg = self.tr(
                     """<p>The file <b>{0}</b> has been changed while it"""
-                    """ was opened in eric6. Reread it?</p>"""
+                    """ was opened in eric. Reread it?</p>"""
                 ).format(self.fileName)
                 yesDefault = True
                 if self.isModified():
@@ -7007,7 +7071,8 @@
         """
         Protected method called when the editor loses focus.
         
-        @param event the event object (QFocusEvent)
+        @param event the event object
+        @type QFocusEvent
         """
         self.vm.editorActGrp.setEnabled(False)
         self.setCaretWidth(0)
@@ -7024,7 +7089,8 @@
         other modes. This is to make the editor windows work nicer
         with the QWorkspace.
         
-        @param evt the event, that was generated (QEvent)
+        @param evt the event, that was generated
+        @type QEvent
         """
         if (
             evt.type() == QEvent.WindowStateChange and
@@ -7044,16 +7110,29 @@
         """
         Protected method to handle the mouse press event.
         
-        @param event the mouse press event (QMouseEvent)
+        @param event the mouse press event
+        @type QMouseEvent
         """
         self.vm.eventFilter(self, event)
         super(Editor, self).mousePressEvent(event)
+    
+    def mouseDoubleClickEvent(self, evt):
+        """
+        Protected method to handle mouse double click events.
+        
+        @param evt reference to the mouse event
+        @type QMouseEvent
+        """
+        super(Editor, self).mouseDoubleClickEvent(evt)
+        
+        self.mouseDoubleClick.emit(evt.pos(), evt.buttons())
         
     def wheelEvent(self, evt):
         """
         Protected method to handle wheel events.
         
-        @param evt reference to the wheel event (QWheelEvent)
+        @param evt reference to the wheel event
+        @type QWheelEvent
         """
         delta = evt.angleDelta().y()
         if evt.modifiers() & Qt.ControlModifier:
@@ -7078,8 +7157,10 @@
         """
         Public method handling events.
         
-        @param evt reference to the event (QEvent)
-        @return flag indicating, if the event was handled (boolean)
+        @param evt reference to the event
+        @type QEvent
+        @return flag indicating, if the event was handled
+        @rtype bool
         """
         if evt.type() == QEvent.Gesture:
             self.gestureEvent(evt)
@@ -7091,7 +7172,8 @@
         """
         Protected method handling gesture events.
         
-        @param evt reference to the gesture event (QGestureEvent
+        @param evt reference to the gesture event
+        @type QGestureEvent
         """
         pinch = evt.gesture(Qt.PinchGesture)
         if pinch:
@@ -7113,7 +7195,8 @@
         """
         Protected method handling resize events.
         
-        @param evt reference to the resize event (QResizeEvent)
+        @param evt reference to the resize event
+        @type QResizeEvent
         """
         super(Editor, self).resizeEvent(evt)
         self.__markerMap.calculateGeometry()
@@ -7122,8 +7205,10 @@
         """
         Protected method handling event of the viewport.
         
-        @param evt reference to the event (QEvent)
-        @return flag indiating that the event was handled (boolean)
+        @param evt reference to the event
+        @type QEvent
+        @return flag indiating that the event was handled
+        @rtype bool
         """
         try:
             self.__markerMap.calculateGeometry()
@@ -7666,8 +7751,8 @@
         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)
+        @param pwl name of the personal/project word list (string)
+        @param pel name of the personal/project exclude list (string)
         """
         if self.spell and self.spell.getLanguage() != language:
             self.spell.setLanguage(language, pwl=pwl, pel=pel)
@@ -7962,7 +8047,7 @@
         """
         Public slot to cancel a shared edit session for the editor.
         
-        @keyparam send flag indicating to send the CancelEdit command (boolean)
+        @param send flag indicating to send the CancelEdit command (boolean)
         """
         self.__inSharedEdit = False
         self.__savedText = ""
@@ -8568,13 +8653,94 @@
         
         return None
     
-    def mouseDoubleClickEvent(self, evt):
-        """
-        Protected method to handle mouse double click events.
-        
-        @param evt reference to the mouse event
-        @type QMouseEvent
-        """
-        super(Editor, self).mouseDoubleClickEvent(evt)
-        
-        self.mouseDoubleClick.emit(evt.pos(), evt.buttons())
+    #######################################################################
+    ## Methods implementing the docstring generator interface
+    #######################################################################
+    
+    def getDocstringGenerator(self):
+        """
+        Public method to get a reference to the docstring generator.
+        
+        @return reference to the docstring generator
+        @rtype BaseDocstringGenerator
+        """
+        if self.__docstringGenerator is None:
+            from . import DocstringGenerator
+            self.__docstringGenerator = (
+                DocstringGenerator.getDocstringGenerator(self)
+            )
+        
+        return self.__docstringGenerator
+    
+    def insertDocstring(self):
+        """
+        Public method to generate and insert a docstring for the function under
+        the cursor.
+        
+        Note: This method is called via a keyboard shortcut or through the
+        global 'Edit' menu.
+        """
+        generator = self.getDocstringGenerator()
+        generator.insertDocstringFromShortcut(self.getCursorPosition())
+    
+    @pyqtSlot()
+    def __insertDocstring(self):
+        """
+        Private slot to generate and insert a docstring for the function under
+        the cursor.
+        """
+        generator = self.getDocstringGenerator()
+        generator.insertDocstring(self.getCursorPosition(), fromStart=True)
+    
+    def __delayedDocstringMenuPopup(self, cursorPosition):
+        """
+        Private method to test, if the user might want to insert a docstring.
+        
+        @param cursorPosition current cursor position (line and column)
+        @type tuple of (int, int)
+        """
+        if (
+            Preferences.getEditor("DocstringAutoGenerate") and
+            self.getDocstringGenerator().isDocstringIntro(cursorPosition)
+        ):
+            lineText2Cursor = self.text(cursorPosition[0])[:cursorPosition[1]]
+            
+            QTimer.singleShot(
+                300,
+                lambda: self.__popupDocstringMenu(lineText2Cursor,
+                                                  cursorPosition)
+            )
+    
+    def __popupDocstringMenu(self, lastLineText, lastCursorPosition):
+        """
+        Private slot to pop up a menu asking the user, if a docstring should be
+        inserted.
+        
+        @param lastLineText line contents when the delay timer was started
+        @type str
+        @param lastCursorPosition position of the cursor when the delay timer
+            was started (line and index)
+        @type tuple of (int, int)
+        """
+        cursorPosition = self.getCursorPosition()
+        if lastCursorPosition != cursorPosition:
+            return
+        
+        if self.text(cursorPosition[0])[:cursorPosition[1]] != lastLineText:
+            return
+        
+        generator = self.getDocstringGenerator()
+        if generator.hasFunctionDefinition(cursorPosition):
+            from .DocstringGenerator.BaseDocstringGenerator import (
+                DocstringMenuForEnterOnly
+            )
+            docstringMenu = DocstringMenuForEnterOnly(self)
+            act = docstringMenu.addAction(
+                UI.PixmapCache.getIcon("fileText"),
+                self.tr("Generate Docstring"),
+                lambda: generator.insertDocstring(cursorPosition,
+                                                  fromStart=False)
+            )
+            docstringMenu.setActiveAction(act)
+            docstringMenu.popup(
+                self.mapToGlobal(self.getGlobalCursorPosition()))

eric ide

mercurial