--- 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()))