Started to implement a viewer for source code documentation extracted by providers to be implemented by plug-ins (like rope and jedi).

Sat, 14 Oct 2017 20:07:08 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 14 Oct 2017 20:07:08 +0200
changeset 5900
cd90bfdc1247
parent 5899
0516f6548ca6
child 5902
fb4b68592b7c

Started to implement a viewer for source code documentation extracted by providers to be implemented by plug-ins (like rope and jedi).

Preferences/__init__.py file | annotate | diff | comparison | revisions
QScintilla/Editor.py file | annotate | diff | comparison | revisions
README-MacOSX.rst file | annotate | diff | comparison | revisions
UI/CodeDocumentationViewer.py file | annotate | diff | comparison | revisions
UI/CodeDocumentationViewer.ui file | annotate | diff | comparison | revisions
UI/UserInterface.py file | annotate | diff | comparison | revisions
ViewManager/ViewManager.py file | annotate | diff | comparison | revisions
changelog file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
icons/default/codeDocuViewer.png file | annotate | diff | comparison | revisions
--- a/Preferences/__init__.py	Tue Oct 10 19:05:00 2017 +0200
+++ b/Preferences/__init__.py	Sat Oct 14 20:07:08 2017 +0200
@@ -428,6 +428,9 @@
         "CallTipsScintillaOnFail": False,
         # show QScintilla calltips, if plug-in fails
         
+        "ShowInfoOnOpenBracket": True,
+        # TODO: add to editor configuration page
+        
         "AutoCheckSyntax": True,
         "OnlineSyntaxCheck": True,
         "OnlineSyntaxCheckInterval": 5,
--- a/QScintilla/Editor.py	Tue Oct 10 19:05:00 2017 +0200
+++ b/QScintilla/Editor.py	Sat Oct 14 20:07:08 2017 +0200
@@ -261,6 +261,7 @@
         self.cursorPositionChanged.connect(self.__cursorPositionChanged)
         self.modificationAttempted.connect(self.__modificationReadOnly)
         self.userListActivated.connect(self.__completionListSelected)
+        self.SCN_CHARADDED.connect(self.__charAddedPermanent)
         
         # margins layout
         if QSCINTILLA_VERSION() >= 0x020301:
@@ -4971,6 +4972,23 @@
                 bool(self.__ctHookFunctions))
     
     #################################################################
+    ## Methods needed by the code documentation viewer
+    #################################################################
+    
+    def __charAddedPermanent(self, charNumber):
+        """
+        Private slot called to handle the user entering a character.
+        
+        Note: This slot is always connected independent of the auto-completion
+        and calltips handling __charAdded() slot.
+        
+        @param charNumber value of the character entered (integer)
+        """
+        char = chr(charNumber)
+        if char == "(" and Preferences.getEditor("ShowInfoOnOpenBracket"):
+            self.vm.showEditorInfo(self)
+    
+    #################################################################
     ## Methods needed by the context menu
     #################################################################
     
--- a/README-MacOSX.rst	Tue Oct 10 19:05:00 2017 +0200
+++ b/README-MacOSX.rst	Sat Oct 14 20:07:08 2017 +0200
@@ -179,13 +179,13 @@
 
 ::
 
-    port search hunspell-dict
+    port search hunspell
 
 Then install them with a command like this
 
 ::
 
-    sudo port install hunspell-dict-de_DE
+    sudo port install hunspell-de_DE hunspell-en_US_large
 
 replacing the 'de_DE' part with the language code of your desire.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UI/CodeDocumentationViewer.py	Sat Oct 14 20:07:08 2017 +0200
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget to show some source code information provided by
+plug-ins.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSlot, pyqtSignal
+from PyQt5.QtWidgets import QWidget
+
+from .Ui_CodeDocumentationViewer import Ui_CodeDocumentationViewer
+
+import Preferences
+
+
+class CodeDocumentationViewer(QWidget, Ui_CodeDocumentationViewer):
+    """
+    Class implementing a widget to show some source code information provided
+    by plug-ins.
+    """
+    providerAdded = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(CodeDocumentationViewer, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__ui = parent
+        
+        self.__providers = {}
+        self.__selectedProvider = ""
+        self.__disabledProvider = "disabled"
+        
+        self.__shuttingDown = False
+        self.__startingUp = True
+        
+        self.__noDocumentationString = self.tr("No documentation available")
+        self.__disabledString = self.tr(
+            "No source code documentation provider has been registered or"
+            " this function has been disabled.")
+        
+        self.providerComboBox.addItem(self.tr("<disabled>"), "disabled")
+    
+    def finalizeSetup(self):
+        """
+        Public method to finalize the setup of the documentation viewer.
+        """
+        self.__startingUp = False
+        provider = Preferences.Prefs.settings.value(
+            "CodeDocumentationViewer/Provider", "disabled")
+        if provider in self.__providers:
+            index = self.providerComboBox.findData(provider)
+        else:
+            index = 0
+        self.providerComboBox.setCurrentIndex(index)
+    
+    def registerProvider(self, providerName, providerDisplay, provider):
+        """
+        Public method register a source docu provider.
+        
+        @param providerName name of the provider (must be unique)
+        @type str
+        @param providerDisplay visible name of the provider
+        @type str
+        @param provider function to be called to determine source docu
+        @type function
+        @exception KeyError raised if a provider with the given name was
+            already registered
+        """
+        if providerName in self.__providers:
+            raise KeyError(
+                "Provider '{0}' already registered.".format(providerName))
+        
+        self.__providers[providerName] = provider
+        self.providerComboBox.addItem(providerDisplay, providerName)
+    
+    def unregisterProvider(self, providerName):
+        """
+        Public method register a source docu provider.
+        
+        @param providerName name of the provider (must be unique)
+        @type str
+        """
+        if providerName in self.__providers:
+            if providerName == self.__selectedProvider:
+                self.providerComboBox.setCurrentIndex(0)
+            
+            del self.__providers[providerName]
+            index = self.providerComboBox.findData(providerName)
+            self.providerComboBox.removeItem(index)
+    
+    def showInfo(self, editor):
+        """
+        Public method to request code documentation data from a provider.
+        
+        @param editor reference to the editor to request code docu for
+        @type Editor
+        """
+        if self.__selectedProvider != self.__disabledProvider:
+            self.contents.clear()
+            self.__providers[self.__selectedProvider](editor)
+    
+    def documentationReady(self, documentationInfo):
+        """
+        Public method to provide the documentation info to the viewer.
+        
+        If documentationInfo is a dictionary, it should contains these keys
+        and data:
+        
+        name: the name of the inspected object
+        argspec: its argspec
+        note: A phrase describing the type of object (function or method) and
+            the module it belongs to.
+        docstring: its documentation string
+        
+        @param documentationInfo dictionary containing the source docu data
+        @type dict or str
+        """
+        self.__ui.activateCodeDocumentationViewer(switchFocus=False)
+        
+        if not documentationInfo:
+            fullText = self.__noDocumentationString
+        elif isinstance(documentationInfo, str):
+            fullText = documentationInfo
+        elif isinstance(documentationInfo, dict):
+            name = documentationInfo["name"]
+            if name:
+                title = "".join(["=" * len(name), "\n", name, "\n",
+                                 "=" * len(name), "\n\n"])
+            else:
+                title = ""
+
+            if documentationInfo["argspec"]:
+                definition = self.tr("Definition: {0}{1}\n").format(
+                    name, documentationInfo["argspec"])
+            else:
+                definition = ''
+
+            if documentationInfo["note"]:
+                note = self.tr("Info: {0}\n\n----\n\n").format(
+                    documentationInfo["note"])
+            else:
+                note = ""
+
+            fullText = "".join([title, definition, note,
+                                documentationInfo['docstring']])
+        
+        self.contents.setPlainText(fullText)
+    
+    @pyqtSlot(int)
+    def on_providerComboBox_currentIndexChanged(self, index):
+        """
+        Private slot to handle the selection of a provider.
+        
+        @param index index of the selected provider
+        @type int
+        """
+        if not self.__shuttingDown and not self.__startingUp:
+            provider = self.providerComboBox.itemData(index)
+            if provider == self.__disabledProvider:
+                self.documentationReady(self.__disabledString)
+            elif provider in self.__providers:
+                Preferences.Prefs.settings.setValue(
+                    "CodeDocumentationViewer/Provider", provider)
+            self.__selectedProvider = provider
+    
+    def shutdown(self):
+        """
+        Public method to perform shutdown actions.
+        """
+        self.__shuttingDown = True
+        Preferences.Prefs.settings.setValue(
+            "CodeDocumentationViewer/Provider", self.__selectedProvider)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UI/CodeDocumentationViewer.ui	Sat Oct 14 20:07:08 2017 +0200
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CodeDocumentationViewer</class>
+ <widget class="QWidget" name="CodeDocumentationViewer">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>639</width>
+    <height>595</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Code Info Provider:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="providerComboBox">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="toolTip">
+        <string>Select the code info provider</string>
+       </property>
+       <property name="sizeAdjustPolicy">
+        <enum>QComboBox::AdjustToContents</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="objectLineEdit">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTextEdit" name="contents">
+     <property name="tabChangesFocus">
+      <bool>true</bool>
+     </property>
+     <property name="readOnly">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="E5TextEditSearchWidget" name="searchWidget" native="true">
+     <property name="focusPolicy">
+      <enum>Qt::WheelFocus</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5TextEditSearchWidget</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5TextEditSearchWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>providerComboBox</tabstop>
+  <tabstop>objectLineEdit</tabstop>
+  <tabstop>contents</tabstop>
+  <tabstop>searchWidget</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- a/UI/UserInterface.py	Tue Oct 10 19:05:00 2017 +0200
+++ b/UI/UserInterface.py	Sat Oct 14 20:07:08 2017 +0200
@@ -531,6 +531,7 @@
         e5App().registerObject("IRC", self.irc)
         e5App().registerObject("Symbols", self.symbolsViewer)
         e5App().registerObject("Numbers", self.numbersViewer)
+        e5App().registerObject("DocuViewer", self.codeDocumentationViewer)
         
         # list of web addresses serving the versions file
         self.__httpAlternatives = Preferences.getUI("VersionsUrls6")
@@ -643,6 +644,9 @@
         if interval > 0:
             QApplication.setKeyboardInputInterval(interval)
         
+        # finalize the initialization of the code documentation viewer
+        self.codeDocumentationViewer.finalizeSetup()
+        
     def __createLayout(self, debugServer):
         """
         Private method to create the layout of the various windows.
@@ -701,6 +705,8 @@
         """
         from E5Gui.E5ToolBox import E5VerticalToolBox, E5HorizontalToolBox
         
+        logging.debug("Creating Toolboxes Layout...")
+        
         # Create the left toolbox
         self.lToolboxDock = self.__createDockWindow("lToolboxDock")
         self.lToolbox = E5VerticalToolBox(self.lToolboxDock)
@@ -721,6 +727,7 @@
                                self.rToolbox, self.tr("Right Toolbox"))
         
         # Create the project browser
+        logging.debug("Creating Project Browser...")
         from Project.ProjectBrowser import ProjectBrowser
         self.projectBrowser = ProjectBrowser(
             self.project, None,
@@ -730,6 +737,7 @@
                               self.tr("Project-Viewer"))
 
         # Create the multi project browser
+        logging.debug("Creating Multiproject Browser...")
         from MultiProject.MultiProjectBrowser import MultiProjectBrowser
         self.multiProjectBrowser = MultiProjectBrowser(self.multiProject,
                                                        self.project)
@@ -738,6 +746,7 @@
                               self.tr("Multiproject-Viewer"))
 
         # Create the template viewer part of the user interface
+        logging.debug("Creating Template Viewer...")
         from Templates.TemplateViewer import TemplateViewer
         self.templateViewer = TemplateViewer(None,
                                              self.viewmanager)
@@ -745,7 +754,16 @@
                               UI.PixmapCache.getIcon("templateViewer.png"),
                               self.tr("Template-Viewer"))
 
+        # Create the code documentation viewer
+        logging.debug("Creating Code Documentation Viewer...")
+        from .CodeDocumentationViewer import CodeDocumentationViewer
+        self.codeDocumentationViewer = CodeDocumentationViewer(self)
+        self.rToolbox.addItem(self.codeDocumentationViewer,
+                              UI.PixmapCache.getIcon("codeDocuViewer.png"), 
+                              self.tr("Code Documentation Viewer"))
+        
         # Create the debug viewer maybe without the embedded shell
+        logging.debug("Creating Debug Viewer...")
         from Debugger.DebugViewer import DebugViewer
         self.debugViewer = DebugViewer(
             debugServer, True, self.viewmanager, None,
@@ -756,6 +774,7 @@
                               self.tr("Debug-Viewer"))
 
         # Create the chat part of the user interface
+        logging.debug("Creating Chat Widget...")
         from Cooperation.ChatWidget import ChatWidget
         self.cooperation = ChatWidget(self)
         self.rToolbox.addItem(self.cooperation,
@@ -763,6 +782,7 @@
                               self.tr("Cooperation"))
         
         # Create the IRC part of the user interface
+        logging.debug("Creating IRC Widget...")
         from Network.IRC.IrcWidget import IrcWidget
         self.irc = IrcWidget(self)
         self.rToolbox.addItem(self.irc,
@@ -770,6 +790,7 @@
                               self.tr("IRC"))
         
         # Create the task viewer part of the user interface
+        logging.debug("Creating Task Viewer...")
         from Tasks.TaskViewer import TaskViewer
         self.taskViewer = TaskViewer(None, self.project)
         self.hToolbox.addItem(self.taskViewer,
@@ -777,6 +798,7 @@
                               self.tr("Task-Viewer"))
 
         # Create the log viewer part of the user interface
+        logging.debug("Creating Log Viewer...")
         from .LogView import LogViewer
         self.logViewer = LogViewer(self)
         self.hToolbox.addItem(self.logViewer,
@@ -787,6 +809,7 @@
             self.shell = self.debugViewer.shell
         else:
             # Create the shell
+            logging.debug("Creating Shell...")
             from QScintilla.Shell import ShellAssembly
             self.shellAssembly = \
                 ShellAssembly(debugServer, self.viewmanager, True)
@@ -797,6 +820,7 @@
 
         if self.embeddedFileBrowser == 0:   # separate window
             # Create the file browser
+            logging.debug("Creating File Browser...")
             from .Browser import Browser
             self.browser = Browser()
             self.lToolbox.addItem(self.browser,
@@ -808,6 +832,7 @@
             self.browser = self.projectBrowser.fileBrowser
         
         # Create the symbols viewer
+        logging.debug("Creating Symbols Viewer...")
         from .SymbolsWidget import SymbolsWidget
         self.symbolsViewer = SymbolsWidget()
         self.lToolbox.addItem(self.symbolsViewer,
@@ -815,6 +840,7 @@
                               self.tr("Symbols"))
         
         # Create the numbers viewer
+        logging.debug("Creating Numbers Viewer...")
         from .NumbersWidget import NumbersWidget
         self.numbersViewer = NumbersWidget()
         self.hToolbox.addItem(self.numbersViewer,
@@ -831,6 +857,8 @@
         """
         from E5Gui.E5SideBar import E5SideBar
         
+        logging.debug("Creating Sidebars Layout...")
+        
         delay = Preferences.getUI("SidebarDelay")
         # Create the left sidebar
         self.leftSidebar = E5SideBar(E5SideBar.West, delay)
@@ -872,6 +900,15 @@
             UI.PixmapCache.getIcon("templateViewer.png"),
             self.tr("Template-Viewer"))
 
+        # Create the code documentation viewer
+        logging.debug("Creating Code Documentation Viewer...")
+        from .CodeDocumentationViewer import CodeDocumentationViewer
+        self.codeDocumentationViewer = CodeDocumentationViewer(self)
+        self.rightSidebar.addTab(
+            self.codeDocumentationViewer,
+            UI.PixmapCache.getIcon("codeDocuViewer.png"), 
+            self.tr("Code Documentation Viewer"))
+        
         # Create the debug viewer maybe without the embedded shell
         logging.debug("Creating Debug Viewer...")
         from Debugger.DebugViewer import DebugViewer
@@ -4165,6 +4202,24 @@
         if aw is not None:
             aw.setFocus(Qt.ActiveWindowFocusReason)
     
+    def activateCodeDocumentationViewer(self, switchFocus=True):
+        """
+        Public slot to handle the activation of the Code Documentation Viewer.
+        
+        @param switchFocus flag indicating to transfer the input focus
+        @type bool
+        """
+        if self.layoutType == "Toolboxes":
+            self.rToolboxDock.show()
+            self.rToolbox.setCurrentWidget(self.codeDocumentationViewer)
+        elif self.layoutType == "Sidebars":
+            self.rightSidebar.show()
+            self.rightSidebar.setCurrentWidget(self.codeDocumentationViewer)
+        else:
+            self.codeDocumentationViewer.show()
+        if switchFocus:
+            self.codeDocumentationViewer.setFocus(Qt.ActiveWindowFocusReason)
+    
     def __toggleWindow(self, w):
         """
         Private method to toggle a workspace editor window.
@@ -6267,6 +6322,8 @@
         if sessionCreated and not self.__disableCrashSession:
             self.__deleteCrashSession()
         
+        self.codeDocumentationViewer.shutdown()
+        
         self.__previewer.shutdown()
         
         self.shell.closeShell()
@@ -6798,3 +6855,16 @@
         Private slot handling the automatic connection of the IRC client.
         """
         self.__activateIRC()
+    
+    ###############################################
+    ## Support for Code Documentation Viewer  below
+    ###############################################
+    
+    def documentationViewer(self):
+        """
+        Public method to provide a reference to the code documentation viewer.
+        
+        @return reference to the code documentation viewer
+        @rtype CodeDocumentationViewer
+        """
+        return self.codeDocumentationViewer
--- a/ViewManager/ViewManager.py	Tue Oct 10 19:05:00 2017 +0200
+++ b/ViewManager/ViewManager.py	Sat Oct 14 20:07:08 2017 +0200
@@ -84,6 +84,7 @@
         super(QuickSearchLineEdit, self).focusInEvent(evt)   # pass it on
 
 
+# TODO: add an action to show code info for the object under the cursor (Ctrl-Alt-I)
 class ViewManager(QWidget):
     """
     Base class inherited by all specific viewmanager classes.
@@ -6782,6 +6783,16 @@
         if editor:
             editor.sortLines()
     
+    def showEditorInfo(self, editor):
+        """
+        Public method to show some information for a given editor.
+        
+        @param editor editor to show information text for
+        @type Editor
+        """
+        # TODO: implement CodeDocumentationViewer
+        self.ui.documentationViewer().showInfo(editor)
+    
     ##################################################################
     ## Below are protected utility methods
     ##################################################################
--- a/changelog	Tue Oct 10 19:05:00 2017 +0200
+++ b/changelog	Sat Oct 14 20:07:08 2017 +0200
@@ -6,6 +6,8 @@
   -- added capability to enter an interpreter in the Start... dialog
 - Editor
   -- added support for auto-completion lists being provided asynchronously
+  -- added a viewer for source code documentation extracted by providers to
+     be implemented by plug-ins (like rope and jedi)
 
 Version 17.10:
 - bug fixes
--- a/eric6.e4p	Tue Oct 10 19:05:00 2017 +0200
+++ b/eric6.e4p	Sat Oct 14 20:07:08 2017 +0200
@@ -1221,6 +1221,7 @@
     <Source>UI/BrowserModel.py</Source>
     <Source>UI/BrowserSortFilterProxyModel.py</Source>
     <Source>UI/ClearPrivateDataDialog.py</Source>
+    <Source>UI/CodeDocumentationViewer.py</Source>
     <Source>UI/CompareDialog.py</Source>
     <Source>UI/Config.py</Source>
     <Source>UI/DeleteFilesConfirmationDialog.py</Source>
@@ -1898,6 +1899,7 @@
     <Form>Tasks/TaskPropertiesDialog.ui</Form>
     <Form>Templates/TemplatePropertiesDialog.ui</Form>
     <Form>Templates/TemplateSingleVariableDialog.ui</Form>
+    <Form>UI/CodeDocumentationViewer.ui</Form>
     <Form>UI/AuthenticationDialog.ui</Form>
     <Form>UI/ClearPrivateDataDialog.ui</Form>
     <Form>UI/CompareDialog.ui</Form>
@@ -2015,14 +2017,14 @@
   <Interfaces/>
   <Others>
     <Other>.hgignore</Other>
-    <Other>APIs/Python/zope-2.10.7.api</Other>
-    <Other>APIs/Python/zope-2.11.2.api</Other>
-    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/Python3/PyQt4.bas</Other>
     <Other>APIs/Python3/PyQt5.bas</Other>
     <Other>APIs/Python3/QScintilla2.bas</Other>
     <Other>APIs/Python3/eric6.api</Other>
     <Other>APIs/Python3/eric6.bas</Other>
+    <Other>APIs/Python/zope-2.10.7.api</Other>
+    <Other>APIs/Python/zope-2.11.2.api</Other>
+    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/QSS/qss.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>
Binary file icons/default/codeDocuViewer.png has changed

eric ide

mercurial