Implemented the distributed History dialog and moved the Undo/Redo functions to this dialog. server_client_variant

Sun, 17 Sep 2017 17:03:16 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 17 Sep 2017 17:03:16 +0200
branch
server_client_variant
changeset 168
53d76b4fc1ac
parent 167
3c8e875d0326
child 169
fb8a4182f12e

Implemented the distributed History dialog and moved the Undo/Redo functions to this dialog.

PluginRope.e4p file | annotate | diff | comparison | revisions
RefactoringRope/HistoryDialog.py file | annotate | diff | comparison | revisions
RefactoringRope/HistoryDialog.ui file | annotate | diff | comparison | revisions
RefactoringRope/JsonClient.py file | annotate | diff | comparison | revisions
RefactoringRope/JsonServer.py file | annotate | diff | comparison | revisions
RefactoringRope/Refactoring.py file | annotate | diff | comparison | revisions
RefactoringRope/RefactoringClient.py file | annotate | diff | comparison | revisions
--- a/PluginRope.e4p	Sat Sep 16 18:51:19 2017 +0200
+++ b/PluginRope.e4p	Sun Sep 17 17:03:16 2017 +0200
@@ -211,6 +211,7 @@
     <Form>RefactoringRope/ExtractDialog.ui</Form>
     <Form>RefactoringRope/GetterSetterDialog.ui</Form>
     <Form>RefactoringRope/HelpDialog.ui</Form>
+    <Form>RefactoringRope/HistoryDialog.ui</Form>
     <Form>RefactoringRope/InlineArgumentDefaultDialog.ui</Form>
     <Form>RefactoringRope/InlineDialog.ui</Form>
     <Form>RefactoringRope/IntroduceFactoryDialog.ui</Form>
--- a/RefactoringRope/HistoryDialog.py	Sat Sep 16 18:51:19 2017 +0200
+++ b/RefactoringRope/HistoryDialog.py	Sun Sep 17 17:03:16 2017 +0200
@@ -9,127 +9,308 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtCore import Qt, pyqtSlot
-from PyQt5.QtWidgets import QDialogButtonBox, QListWidgetItem, QApplication
+from PyQt5.QtCore import pyqtSlot, Qt, QItemSelectionModel
+from PyQt5.QtGui import QBrush, QColor, QTextCursor
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem, \
+    QAbstractButton
 
 from E5Gui.E5Application import e5App
 from E5Gui import E5MessageBox
 
-from PreviewDialogBase import PreviewDialogBase
+from Ui_HistoryDialog import Ui_HistoryDialog
 
+import Globals
 import Utilities
 
 
-class HistoryDialog(PreviewDialogBase):
+class HistoryDialog(QDialog, Ui_HistoryDialog):
     """
     Class implementing the History dialog.
     """
     ChangeIDRole = Qt.UserRole
     
-    def __init__(self, refactoring, changes, isUndo, parent=None):
+    def __init__(self, refactoring, filename="", parent=None):
         """
         Constructor
         
         @param refactoring reference to the main refactoring object
-            (Refactoring)
-        @param changes list of ChangeSet objects
-            (list of rope.base.change.ChangeSet)
-        @param isUndo flag indicating an undo history dialog (boolean)
-        @param parent reference to the parent widget (QWidget)
+        @type Refactoring
+        @param filename name of the file to show the history for
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
         """
-        PreviewDialogBase.__init__(self, parent)
+        QDialog.__init__(self, parent)
+        self.setupUi(self)
+        self.setWindowFlags(Qt.Window)
+        
+        if Globals.isWindowsPlatform():
+            self.previewEdit.setFontFamily("Lucida Console")
+        else:
+            self.previewEdit.setFontFamily("Monospace")
+        
+        self.formats = {}
+        self.formats[' '] = self.previewEdit.currentCharFormat()
+        charFormat = self.previewEdit.currentCharFormat()
+        charFormat.setBackground(QBrush(QColor(190, 237, 190)))
+        self.formats['+'] = charFormat
+        charFormat = self.previewEdit.currentCharFormat()
+        charFormat.setBackground(QBrush(QColor(237, 190, 190)))
+        self.formats['-'] = charFormat
+        charFormat = self.previewEdit.currentCharFormat()
+        charFormat.setBackground(QBrush(QColor(190, 190, 237)))
+        self.formats['@'] = charFormat
+        charFormat = self.previewEdit.currentCharFormat()
+        charFormat.setBackground(QBrush(QColor(124, 124, 124)))
+        self.formats['?'] = charFormat
+        charFormat = self.previewEdit.currentCharFormat()
+        charFormat.setBackground(QBrush(QColor(190, 190, 190)))
+        self.formats['='] = charFormat
         
         self.__refactoring = refactoring
-        self.__isUndo = isUndo
+        self.__filename = filename
+        
+        if not filename:
+            self.header.setText(self.tr("<b>Project History</b>"))
+        else:
+            self.header.setText(self.tr("<b>File History: {0}</b>").format(
+                filename))
         
-        if self.__isUndo:
-            self.__actionButton = self.buttonBox.addButton(
-                self.tr("&Undo"),
-                QDialogButtonBox.AcceptRole)
-            self.description.setText(self.tr("Undoable Changes"))
-            title = self.tr("Undo History")
-        else:
-            self.__actionButton = self.buttonBox.addButton(
-                self.tr("&Redo"),
-                QDialogButtonBox.AcceptRole)
-            self.description.setText(self.tr("Redoable Changes"))
-            title = self.tr("Redo History")
-        self.buttonBox.addButton(QDialogButtonBox.Close)
-        self.setWindowTitle(title)
+        self.__undoButton = self.buttonBox.addButton(
+            self.tr("&Undo"), QDialogButtonBox.ActionRole)
+        self.__redoButton = self.buttonBox.addButton(
+            self.tr("&Redo"), QDialogButtonBox.ActionRole)
+        self.__refreshButton = self.buttonBox.addButton(
+            self.tr("Re&fresh"), QDialogButtonBox.ActionRole)
+        self.__clearButton = self.buttonBox.addButton(
+            self.tr("&Clear History"), QDialogButtonBox.ActionRole)
         
         # populate the list
-        self.__changes = {}
-        for change in changes:
-            self.__changes[id(change)] = change
-            itm = QListWidgetItem(str(change), self.changesList)
-            itm.setData(HistoryDialog.ChangeIDRole, id(change))
-        if self.changesList.count() > 0:
-            self.changesList.item(0).setSelected(True)
+        self.__refreshHistories()
+    
+    def __appendText(self, txt, charFormat):
+        """
+        Private method to append text to the end of the preview pane.
+        
+        @param txt text to insert
+        @type str
+        @param charFormat text format to be used
+        @type QTextCharFormat
+        """
+        tc = self.previewEdit.textCursor()
+        tc.movePosition(QTextCursor.End)
+        self.previewEdit.setTextCursor(tc)
+        self.previewEdit.setCurrentCharFormat(charFormat)
+        self.previewEdit.insertPlainText(txt)
     
-    @pyqtSlot(QListWidgetItem, QListWidgetItem)
-    def on_changesList_currentItemChanged(self, current, previous):
+    @pyqtSlot(QAbstractButton)
+    def on_buttonBox_clicked(self, button):
+        """
+        Private slot handling the selection of a dialog button.
+        
+        @param button reference to the button clicked
+        @type QAbstractButton
         """
-        Private slot called when a change is selected.
+        if button == QDialogButtonBox.Close:
+            self.close()
+        elif button == self.__undoButton:
+            self.__undoChanges()
+        elif button == self.__redoButton:
+            self.__redoChanges()
+        elif button == self.__refreshButton:
+            self.__refreshHistories()
+        elif button == self.__clearButton:
+            self.__clearHistory()
+    
+    def __currentItemChanged(self, current):
+        """
+        Private method to request change data of an item.
         
-        @param current reference to the new current item (QListWidgetItem)
-        @param previous reference to the old current item (QListWidgetItem)
+        @param current reference to the item to get change data for
+        @type QListWidgetItem
         """
         if current is None:
             return
         
         self.previewEdit.clear()
         changeId = current.data(HistoryDialog.ChangeIDRole)
-        change = self.__changes[changeId]
-        for line in change.get_description().splitlines(True):
-            try:
-                charFormat = self.formats[line[0]]
-            except (IndexError, KeyError):
-                charFormat = self.formats[' ']
-            self._appendText(line, charFormat)
+        self.__refactoring.sendJson("History", {
+            "Subcommand": "GetChange",
+            "Id": changeId,
+        })
+    
+    @pyqtSlot(QListWidgetItem, QListWidgetItem)
+    def on_redoChangesList_currentItemChanged(self, current, previous):
+        """
+        Private slot handling a change of the current redo change.
+        
+        @param current reference to the new current redo item
+        @type QListWidgetItem
+        @param previous reference to the previous current redo item
+        @type QListWidgetItem
+        """
+        self.__redoButton.setEnabled(current is not None)
+        self.__currentItemChanged(current)
     
-    def accept(self):
+    @pyqtSlot(QListWidgetItem)
+    def on_redoChangesList_itemClicked(self, item):
+        """
+        Private slot handling a click on a redo entry.
+        
+        @param item reference to the clicked item
+        @type QListWidgetItem
         """
-        Public slot to undo the selected set of changes.
+        self.__currentItemChanged(item)
+    
+    @pyqtSlot(QListWidgetItem, QListWidgetItem)
+    def on_undoChangesList_currentItemChanged(self, current, previous):
+        """
+        Private slot handling a change of the current undo change.
+        
+        @param current reference to the new current undo item
+        @type QListWidgetItem
+        @param previous reference to the previous current undo item
+        @type QListWidgetItem
         """
-        changeId = self.changesList.currentItem()\
-            .data(HistoryDialog.ChangeIDRole)
-        change = self.__changes[changeId]
+        self.__undoButton.setEnabled(current is not None)
+        self.__currentItemChanged(current)
+    
+    @pyqtSlot(QListWidgetItem)
+    def on_undoChangesList_itemClicked(self, item):
+        """
+        Private slot handling a click on an undo entry.
         
-        if self.__isUndo:
-            res = E5MessageBox.yesNo(
-                None,
-                self.tr("Undo refactorings"),
-                self.tr("""Shall all refactorings up to <b>{0}</b>"""
-                        """ be undone?""")
-                .format(Utilities.html_encode(str(change))))
-        else:
-            res = E5MessageBox.yesNo(
-                None,
-                self.tr("Redo refactorings"),
-                self.tr("""Shall all refactorings up to <b>{0}</b>"""
-                        """ be redone?""")
-                .format(Utilities.html_encode(str(change))))
+        @param item reference to the clicked item
+        @type QListWidgetItem
+        """
+        self.__currentItemChanged(item)
+    
+    def __undoChanges(self):
+        """
+        Private method to undo the selected set of changes.
+        """
+        currentUndoItem = self.undoChangesList.currentItem()
+        change = currentUndoItem.text()
+        changeId = currentUndoItem.data(HistoryDialog.ChangeIDRole)
+        res = E5MessageBox.yesNo(
+            None,
+            self.tr("Undo refactorings"),
+            self.tr("""Shall all refactorings up to <b>{0}</b>"""
+                    """ be undone?""")
+            .format(Utilities.html_encode(change)))
+        if res:
+            if not self.__refactoring.confirmAllBuffersSaved():
+                return
+            
+            self.__refactoring.sendJson("History", {
+                "Subcommand": "Undo",
+                "Id": changeId,
+            })
+    
+    def __redoChanges(self):
+        """
+        Private method to redo the selected set of changes.
+        """
+        currentRedoItem = self.redoChangesList.currentItem()
+        change = currentRedoItem.text()
+        changeId = currentRedoItem.data(HistoryDialog.ChangeIDRole)
+        res = E5MessageBox.yesNo(
+            None,
+            self.tr("Redo refactorings"),
+            self.tr("""Shall all refactorings up to <b>{0}</b>"""
+                    """ be redone?""")
+            .format(Utilities.html_encode(change)))
         if res:
             if not self.__refactoring.confirmAllBuffersSaved():
                 return
             
-            from ProgressHandle import ProgressHandle
-            handle = ProgressHandle(change.description, False, self)
-            handle.show()
-            QApplication.processEvents()
-            if self.__isUndo:
-                self.__refactoring.getProject().history.undo(
-                    change, task_handle=handle)
-            else:
-                self.__refactoring.getProject().history.redo(
-                    change, task_handle=handle)
-            handle.reset()
+            self.__refactoring.sendJson("History", {
+                "Subcommand": "Redo",
+                "Id": changeId,
+            })
+    
+    def __refreshHistories(self):
+        """
+        Private method to refresh the undo and redo history lists.
+        """
+        self.__undoButton.setEnabled(False)
+        self.__redoButton.setEnabled(False)
+        self.__refreshButton.setEnabled(False)
+        self.__clearButton.setEnabled(False)
+        
+        self.undoChangesList.clear()
+        self.redoChangesList.clear()
+        self.previewEdit.clear()
+        
+        self.__refactoring.sendJson("History", {
+            "Subcommand": "Get",
+            "Filename": self.__filename
+        })
+    
+    def __clearHistory(self):
+        """
+        Private method to clear the refactoring history.
+        """
+        res = E5MessageBox.yesNo(
+            None,
+            self.tr("Clear History"),
+            self.tr("Do you really want to clear the refactoring history?"))
+        if res:
+            self.sendJson("History", {
+                "Subcommand": "Clear",
+            })
             
-            self.__refactoring.refreshEditors(change)
+            self.historyCleared()
+    
+    def historyCleared(self):
+        """
+        Public method to indicate, that the refactoring history was cleared
+        through the menu.
+        """
+        self.__refreshHistories()
+    
+    def processHistoryCommand(self, data):
+        """
+        Public method to process the data sent by the refactoring client.
+        
+        @param data dictionary containing the history data
+        @type dict
+        """
+        subcommand = data["Subcommand"]
+        if subcommand == "Histories":
+            for change, changeId in data["Undo"]:
+                itm = QListWidgetItem(change, self.undoChangesList)
+                itm.setData(HistoryDialog.ChangeIDRole, changeId)
+            for change, changeId in data["Redo"]:
+                itm = QListWidgetItem(change, self.redoChangesList)
+                itm.setData(HistoryDialog.ChangeIDRole, changeId)
+            if self.undoChangesList.count() > 0:
+                self.undoChangesList.setCurrentItem(
+                    self.undoChangesList.item(0),
+                    QItemSelectionModel.Select)
+            elif self.redoChangesList.count() > 0:
+                self.redoChangesList.setCurrentItem(
+                    self.redoChangesList.item(0),
+                    QItemSelectionModel.Select)
+            
+            self.__refreshButton.setEnabled(True)
+            if self.undoChangesList.count() > 0 or \
+                    self.redoChangesList.count() > 0:
+                self.__clearButton.setEnabled(True)
+        
+        elif subcommand == "ChangeDescription":
+            for line in data["Description"].splitlines(True):
+                try:
+                    charFormat = self.formats[line[0]]
+                except (IndexError, KeyError):
+                    charFormat = self.formats[' ']
+                self.__appendText(line, charFormat)
+        
+        elif subcommand in ["Undo", "Redo"]:
+            self.__refactoring.refreshEditors(data["ChangedFiles"])
             p = e5App().getObject("Project")
             if p.isDirty():
                 p.saveProject()
             
-            PreviewDialogBase.accept(self)
-        else:
-            self.reject()
+            self.raise_()
+            self.__refreshHistories()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RefactoringRope/HistoryDialog.ui	Sun Sep 17 17:03:16 2017 +0200
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HistoryDialog</class>
+ <widget class="QDialog" name="HistoryDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Refactoring History</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <item>
+    <widget class="QLabel" name="header">
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QSplitter" name="splitter_2">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <widget class="QSplitter" name="splitter">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+        <horstretch>1</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <widget class="QWidget" name="">
+       <layout class="QVBoxLayout" name="verticalLayout_2">
+        <item>
+         <widget class="QLabel" name="description">
+          <property name="text">
+           <string>Undo History</string>
+          </property>
+          <property name="wordWrap">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QListWidget" name="undoChangesList">
+          <property name="toolTip">
+           <string>Select a change to preview on the right</string>
+          </property>
+          <property name="alternatingRowColors">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="">
+       <layout class="QVBoxLayout" name="verticalLayout">
+        <item>
+         <widget class="QLabel" name="description_2">
+          <property name="text">
+           <string>Redo History</string>
+          </property>
+          <property name="wordWrap">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QListWidget" name="redoChangesList">
+          <property name="toolTip">
+           <string>Select a change to preview on the right</string>
+          </property>
+          <property name="alternatingRowColors">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </widget>
+     <widget class="QTextEdit" name="previewEdit">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+        <horstretch>2</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="tabChangesFocus">
+       <bool>true</bool>
+      </property>
+      <property name="lineWrapMode">
+       <enum>QTextEdit::NoWrap</enum>
+      </property>
+      <property name="readOnly">
+       <bool>true</bool>
+      </property>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>undoChangesList</tabstop>
+  <tabstop>redoChangesList</tabstop>
+  <tabstop>previewEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>HistoryDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>227</x>
+     <y>578</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>HistoryDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>295</x>
+     <y>584</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- a/RefactoringRope/JsonClient.py	Sat Sep 16 18:51:19 2017 +0200
+++ b/RefactoringRope/JsonClient.py	Sun Sep 17 17:03:16 2017 +0200
@@ -59,7 +59,8 @@
         Private method to receive a JSON encode command and data from the
         server.
         """
-        line = self.__connection.recv(1024 * 1024, socket.MSG_PEEK) # 1M buffer
+        line = self.__connection.recv(1024 * 1024, socket.MSG_PEEK)
+        # 1MB buffer
 
         eol = line.find(b'\n')
 
@@ -139,7 +140,7 @@
     
     def poll(self):
         """
-        Public method to check and receive one message (if available)
+        Public method to check and receive one message (if available).
         """
         try:
             rrdy, wrdy, xrdy = select.select([self.__connection], [], [], 0)
--- a/RefactoringRope/JsonServer.py	Sat Sep 16 18:51:19 2017 +0200
+++ b/RefactoringRope/JsonServer.py	Sun Sep 17 17:03:16 2017 +0200
@@ -11,7 +11,7 @@
 
 import json
 
-from PyQt5.QtCore import pyqtSlot, QProcess, QThread
+from PyQt5.QtCore import pyqtSlot, QProcess
 from PyQt5.QtNetwork import QTcpServer, QHostAddress
 
 from E5Gui import E5MessageBox
@@ -133,7 +133,7 @@
         """
         pass
     
-    def sendJson(self, command, params):
+    def sendJson(self, command, params, flush=False):
         """
         Public method to send a single refactoring command to the client.
         
@@ -141,6 +141,8 @@
         @type str
         @param params dictionary of named parameters for the command
         @type dict
+        @param flush flag indicating to flush the data to the socket
+        @type bool
         """
         commandDict = {
             "jsonrpc": "2.0",
@@ -151,6 +153,8 @@
         
         if self.__connection is not None:
             self.__connection.write(cmd.encode('utf8', 'backslashreplace'))
+            if flush:
+                self.__connection.flush()
     
     def startClient(self, interpreter, clientScript, clientArgs):
         """
@@ -183,12 +187,10 @@
         """
         Public method to stop the client process.
         """
-        self.sendJson("Exit", {})
+        self.sendJson("Exit", {}, flush=True)
         
-        QThread.msleep(200)
+        if self.__connection is not None:
+            self.__connection.waitForDisconnected()
         
         self.__clientProcess.close()
         self.__clientProcess = None
-
-#
-# eflag: noqa = M801
--- a/RefactoringRope/Refactoring.py	Sat Sep 16 18:51:19 2017 +0200
+++ b/RefactoringRope/Refactoring.py	Sun Sep 17 17:03:16 2017 +0200
@@ -60,8 +60,9 @@
         self.__ropeConfig = {}
         
         self.__mainMenu = None
+        self.__progressDialog = None
         self.__helpDialog = None
-        self.__progressDialog = None
+        self.__historyDialog = None
         
         from FileSystemCommands import E5FileSystemCommands
         self.__fsCommands = E5FileSystemCommands(self.__e5project)
@@ -77,6 +78,8 @@
             "FileSystemCommand": self.__fsCommands.processFileSystemCommand,
             
             "ClientException": self.__processClientException,
+            
+            "HistoryResult": self.__processHistoryResult,
         }
     
     def initActions(self):
@@ -489,102 +492,40 @@
         self.actions.append(self.refactoringMethodToMethodObjectAct)
         
         #####################################################
-        ## Undo/Redo actions
+        ## History actions
         #####################################################
         
-        self.refactoringUndoAct = E5Action(
-            self.tr('Undo'),
-            self.tr('&Undo'),
-            0, 0,
-            self, 'refactoring_undo')
-        self.refactoringUndoAct.setStatusTip(self.tr(
-            'Undo the last refactoring'))
-        self.refactoringUndoAct.setWhatsThis(self.tr(
-            """<b>Undo</b>"""
-            """<p>Undo the last refactoring.</p>"""
-        ))
-        self.refactoringUndoAct.triggered.connect(
-            self.__undo)
-        self.actions.append(self.refactoringUndoAct)
-        
-        self.refactoringRedoAct = E5Action(
-            self.tr('Redo'),
-            self.tr('Re&do'),
+        self.refactoringProjectHistoryAct = E5Action(
+            self.tr('Show Project History'),
+            self.tr('Show Project History...'),
             0, 0,
-            self, 'refactoring_redo')
-        self.refactoringRedoAct.setStatusTip(self.tr(
-            'Redo the last refactoring'))
-        self.refactoringRedoAct.setWhatsThis(self.tr(
-            """<b>Redo</b>"""
-            """<p>Redo the last refactoring.</p>"""
-        ))
-        self.refactoringRedoAct.triggered.connect(
-            self.__redo)
-        self.actions.append(self.refactoringRedoAct)
-        
-        self.refactoringUndoHistoryAct = E5Action(
-            self.tr('Show Project Undo History'),
-            self.tr('Show Project Undo History'),
-            0, 0,
-            self, 'refactoring_show_project_undo_history')
-        self.refactoringUndoHistoryAct.setStatusTip(self.tr(
-            'Show the undo history of the project'))
-        self.refactoringUndoHistoryAct.setWhatsThis(self.tr(
-            """<b>Show Project Undo History</b>"""
-            """<p>Opens a dialog to show the undo history list of"""
+            self, 'refactoring_show_project_history')
+        self.refactoringProjectHistoryAct.setStatusTip(self.tr(
+            'Show the refactoring history of the project'))
+        self.refactoringProjectHistoryAct.setWhatsThis(self.tr(
+            """<b>Show Project History</b>"""
+            """<p>This opens a dialog to show the refactoring history of"""
             """ the project.</p>"""
         ))
-        self.refactoringUndoHistoryAct.triggered.connect(
-            self.__showProjectUndoHistory)
-        self.actions.append(self.refactoringUndoHistoryAct)
+        self.refactoringProjectHistoryAct.triggered.connect(
+            self.__showProjectHistory)
+        self.actions.append(self.refactoringProjectHistoryAct)
         
-        self.refactoringRedoHistoryAct = E5Action(
-            self.tr('Show Project Redo History'),
-            self.tr('Show Project Redo History'),
+        self.refactoringFileHistoryAct = E5Action(
+            self.tr('Show Current File History'),
+            self.tr('Show Current File History...'),
             0, 0,
-            self, 'refactoring_show_project_redo_history')
-        self.refactoringRedoHistoryAct.setStatusTip(self.tr(
-            'Show the redo history of the project'))
-        self.refactoringRedoHistoryAct.setWhatsThis(self.tr(
-            """<b>Show Project Redo History</b>"""
-            """<p>Opens a dialog to show the redo history list of"""
-            """ the project.</p>"""
-        ))
-        self.refactoringRedoHistoryAct.triggered.connect(
-            self.__showProjectRedoHistory)
-        self.actions.append(self.refactoringRedoHistoryAct)
-        
-        self.refactoringUndoFileHistoryAct = E5Action(
-            self.tr('Show Current File Undo History'),
-            self.tr('Show Current File Undo History'),
-            0, 0,
-            self, 'refactoring_show_file_undo_history')
-        self.refactoringUndoFileHistoryAct.setStatusTip(self.tr(
-            'Show the undo history of the current file'))
-        self.refactoringUndoFileHistoryAct.setWhatsThis(self.tr(
-            """<b>Show Current File Undo History</b>"""
-            """<p>Opens a dialog to show the undo history list of"""
+            self, 'refactoring_show_file_history')
+        self.refactoringFileHistoryAct.setStatusTip(self.tr(
+            'Show the refactoring history of the current file'))
+        self.refactoringFileHistoryAct.setWhatsThis(self.tr(
+            """<b>Show Current File History</b>"""
+            """<p>This opens a dialog to show the refactoring history of"""
             """ the current file.</p>"""
         ))
-        self.refactoringUndoFileHistoryAct.triggered.connect(
-            self.__showFileUndoHistory)
-        self.actions.append(self.refactoringUndoFileHistoryAct)
-        
-        self.refactoringRedoFileHistoryAct = E5Action(
-            self.tr('Show Current File Redo History'),
-            self.tr('Show Current File Redo History'),
-            0, 0,
-            self, 'refactoring_show_file_redo_history')
-        self.refactoringRedoFileHistoryAct.setStatusTip(self.tr(
-            'Show the redo history of the current file'))
-        self.refactoringRedoFileHistoryAct.setWhatsThis(self.tr(
-            """<b>Show Current File Redo History</b>"""
-            """<p>Opens a dialog to show the redo history list of"""
-            """ the current file.</p>"""
-        ))
-        self.refactoringRedoFileHistoryAct.triggered.connect(
-            self.__showFileRedoHistory)
-        self.actions.append(self.refactoringRedoFileHistoryAct)
+        self.refactoringFileHistoryAct.triggered.connect(
+            self.__showFileHistory)
+        self.actions.append(self.refactoringFileHistoryAct)
         
         self.refactoringClearHistoryAct = E5Action(
             self.tr('Clear History'),
@@ -745,7 +686,6 @@
         smenu.addAction(self.queryImplementationsAct)
         
         smenu = menu.addMenu(self.tr("&Refactoring"))
-        smenu.aboutToShow.connect(self.__showRefactoringMenu)
         smenu.addAction(self.refactoringRenameAct)
         smenu.addAction(self.refactoringRenameLocalAct)
         smenu.addAction(self.refactoringChangeOccurrencesAct)
@@ -784,17 +724,11 @@
         imenu.addAction(self.refactoringImportsHandleLongAct)
         
         smenu.addSeparator()
-        smenu.addAction(self.refactoringUndoAct)
-        smenu.addAction(self.refactoringRedoAct)
-        smenu.addSeparator()
         
         hmenu = smenu.addMenu(self.tr("History"))
         hmenu.aboutToShow.connect(self.__showRefactoringHistoryMenu)
-        hmenu.addAction(self.refactoringUndoHistoryAct)
-        hmenu.addAction(self.refactoringUndoFileHistoryAct)
-        hmenu.addSeparator()
-        hmenu.addAction(self.refactoringRedoHistoryAct)
-        hmenu.addAction(self.refactoringRedoFileHistoryAct)
+        hmenu.addAction(self.refactoringProjectHistoryAct)
+        hmenu.addAction(self.refactoringFileHistoryAct)
         hmenu.addSeparator()
         hmenu.addAction(self.refactoringClearHistoryAct)
         
@@ -828,103 +762,18 @@
                     self.__ropeConfig["RopeVersion"],
                     self.__ropeConfig["RopeCopyright"])))
     
-    def __canUndo(self):
-        """
-        Private slot to check, if there are changes to be undone.
-        
-        @return flag indicating, that undoable changes are available (boolean)
-        """
-        return False
-        return self.__project is not None and \
-            len(self.__project.history.undo_list) > 0
-    
-    def __canRedo(self):
-        """
-        Private slot to check, if there are changes to be redone.
-        
-        @return flag indicating, that redoable changes are available (boolean)
-        """
-        return False
-        return self.__project is not None and \
-            len(self.__project.history.redo_list) > 0
-    
-    def __getFileUndoList(self, resource):
-        """
-        Private slot to get a list of undoable changes.
-        
-        @param resource file resource to filter against
-            (rope.base.resources.File)
-        @return list of change objects (list of rope.base.change.Change)
-        """
-        undoList = []
-        for change in self.__project.history.undo_list:
-            if resource in change.get_changed_resources():
-                undoList.append(change)
-        return undoList
-    
-    def __getFileRedoList(self, resource):
-        """
-        Private slot to get a list of redoable changes.
-        
-        @param resource file resource to filter against
-            (rope.base.resources.File)
-        @return list of change objects (list of rope.base.change.Change)
-        """
-        redoList = []
-        for change in self.__project.history.redo_list:
-            if resource in change.get_changed_resources():
-                redoList.append(change)
-        return redoList
-    
-    def __canUndoFile(self, resource):
-        """
-        Private slot to check, if there are undoable changes for a resource.
-        
-        @param resource file resource to check against
-            (rope.base.resources.File)
-        @return flag indicating, that undoable changes are available (boolean)
-        """
-        return self.__canUndo() and len(self.__getFileUndoList(resource)) > 0
-    
-    def __canRedoFile(self, resource):
-        """
-        Private slot to check, if there are redoable changes for a resource.
-        
-        @param resource file resource to check against
-            (rope.base.resources.File)
-        @return flag indicating, that redoable changes are available (boolean)
-        """
-        return self.__canRedo() and len(self.__getFileRedoList(resource)) > 0
-    
-    def __showRefactoringMenu(self):
-        """
-        Private slot called before the refactoring menu is shown.
-        """
-        self.refactoringUndoAct.setEnabled(self.__canUndo())
-        self.refactoringRedoAct.setEnabled(self.__canRedo())
-    
     def __showRefactoringHistoryMenu(self):
         """
         Private slot called before the refactoring history menu is shown.
         """
         aw = e5App().getObject("ViewManager").activeWindow()
-        resource = None
-        if aw is not None and self.__project is not None:
-            filename = aw.getFileName()
-            if filename is not None:
-                resource = rope.base.libutils.path_to_resource(
-                    self.__project, filename)
+        enable = aw is not None and bool(aw.getFileName())
         
-        self.refactoringUndoHistoryAct.setEnabled(self.__canUndo())
-        self.refactoringUndoFileHistoryAct.setEnabled(
-            resource is not None and self.__canUndoFile(resource))
-        self.refactoringRedoHistoryAct.setEnabled(self.__canRedo())
-        self.refactoringRedoFileHistoryAct.setEnabled(
-            resource is not None and self.__canRedoFile(resource))
+        self.refactoringFileHistoryAct.setEnabled(enable)
     
-    def __handleRopeError(self, result):
+    def handleRopeError(self, result):
         """
-        Private method to handle a rope error.
+        Public method to handle a rope error.
         
         @param result dictionary containing the error information
         @type dict
@@ -1779,122 +1628,39 @@
         self.dlg.show()
     
     #####################################################
-    ## Undo/Redo refactorings
+    ## Refactoring History
     #####################################################
     
-    def __undo(self):
-        """
-        Private slot to undo the last refactoring.
+    def __showProjectHistory(self):
         """
-        title = self.tr("Undo refactoring")
-        history = self.__project.history
-        res = E5MessageBox.yesNo(
-            None,
-            title,
-            self.tr("""Shall the refactoring <b>{0}</b> be undone?""")
-            .format(Utilities.html_encode(
-                history.undo_list[-1].description)))
-        if res:
-            if not self.confirmAllBuffersSaved():
-                return
-            
-            from ProgressHandle import ProgressHandle
-            changes = history.undo_list[-1]
-            handle = ProgressHandle(self.tr("Undo"), False, self.__ui)
-            handle.show()
-            QApplication.processEvents()
-            try:
-                history.undo(task_handle=handle)
-            except Exception as err:
-                self.handleRopeError(err, title, handle)
-            handle.reset()
-            self.refreshEditors(changes)
-            if self.__e5project.isDirty():
-                self.__e5project.saveProject()
-    
-    def __redo(self):
-        """
-        Private slot to redo the last refactoring.
+        Private method to show the project refactoring history.
         """
-        title = self.tr("Redo refactoring")
-        history = self.__project.history
-        res = E5MessageBox.yesNo(
-            None,
-            title,
-            self.tr("""Shall the refactoring <b>{0}</b> be redone?""")
-            .format(Utilities.html_encode(
-                history.redo_list[-1].description)))
-        if res:
-            if not self.confirmAllBuffersSaved():
-                return
-            
-            from ProgressHandle import ProgressHandle
-            changes = history.redo_list[-1]
-            handle = ProgressHandle(self.tr("Redo"), False, self.__ui)
-            handle.show()
-            QApplication.processEvents()
-            try:
-                history.redo(task_handle=handle)
-            except Exception as err:
-                self.handleRopeError(err, title, handle)
-            handle.reset()
-            self.refreshEditors(changes)
-            if self.__e5project.isDirty():
-                self.__e5project.saveProject()
+        if self.__historyDialog is not None:
+            self.__historyDialog.close()
+        
+        from HistoryDialog import HistoryDialog
+        self.__historyDialog = HistoryDialog(self, parent=self.__ui)
+        self.__historyDialog.finished.connect(self.__historyDialogClosed)
+        self.__historyDialog.show()
     
-    def __showProjectUndoHistory(self):
-        """
-        Private method to show list of changes available for an undo operation.
-        """
-        from HistoryDialog import HistoryDialog
-        undoList = list(reversed(self.__project.history.undo_list))
-        self.dlg = HistoryDialog(self, undoList, True, self.__ui)
-        self.dlg.show()
-    
-    def __showProjectRedoHistory(self):
+    def __showFileHistory(self):
         """
-        Private method to show list of changes available for a redo operation.
-        """
-        from HistoryDialog import HistoryDialog
-        redoList = self.__project.history.redo_list
-        self.dlg = HistoryDialog(self, redoList, False, self.__ui)
-        self.dlg.show()
-    
-    def __showFileUndoHistory(self):
-        """
-        Private method to show list of changes related to the current file
-        available for an undo operation.
+        Private method to show the refactoring history of the current file.
         """
         aw = e5App().getObject("ViewManager").activeWindow()
         
         if aw is None:
             return
         
-        from HistoryDialog import HistoryDialog
-        filename = aw.getFileName()
-        resource = rope.base.libutils.path_to_resource(
-            self.__project, filename)
-        undoList = list(reversed(self.__getFileUndoList(resource)))
-        self.dlg = HistoryDialog(self, undoList, True, self.__ui)
-        self.dlg.show()
-    
-    def __showFileRedoHistory(self):
-        """
-        Private method to show list of changes related to the current file
-        available for a redo operation.
-        """
-        aw = e5App().getObject("ViewManager").activeWindow()
-        
-        if aw is None:
-            return
+        if self.__historyDialog is not None:
+            self.__historyDialog.close()
         
         from HistoryDialog import HistoryDialog
         filename = aw.getFileName()
-        resource = rope.base.libutils.path_to_resource(
-            self.__project, filename)
-        redoList = self.__getFileRedoList(resource)
-        self.dlg = HistoryDialog(self, redoList, False, self.__ui)
-        self.dlg.show()
+        if filename:
+            self.__historyDialog = HistoryDialog(
+                self, filename=filename, parent=self.__ui)
+            self.__historyDialog.show()
     
     def __clearHistory(self):
         """
@@ -1903,10 +1669,31 @@
         res = E5MessageBox.yesNo(
             None,
             self.tr("Clear History"),
-            self.tr("""Do you really want to clear the undo"""
-                    """ and redo history?"""))
+            self.tr("Do you really want to clear the refactoring history?"))
         if res:
-            self.__project.history.clear()
+            self.sendJson("History", {
+                "Subcommand": "Clear",
+            })
+            
+            if self.__historyDialog is not None:
+                self.__historyDialog.historyCleared()
+    
+    def __processHistoryResult(self, result):
+        """
+        Private method to process the history data sent by the refactoring
+        client.
+        
+        @param result dictionary containing the history data
+        @type dict
+        """
+        if self.handleRopeError(result) and self.__historyDialog is not None:
+            self.__historyDialog.processHistoryCommand(result)
+    
+    def __historyDialogClosed(self):
+        """
+        Private slot handling the closing of the history dialog.
+        """
+        self.__historyDialog = None
     
     #####################################################
     ## Find actions including mouse click handler
@@ -1944,7 +1731,7 @@
         @param result dictionary containing the result data
         @type dict
         """
-        if self.__handleRopeError(result):
+        if self.handleRopeError(result):
             title = result["Title"]
             if result["EntriesCount"] > 0:
                 from MatchesDialog import MatchesDialog
@@ -2015,7 +1802,7 @@
         @type dict
         """
         if result["Subcommand"] == "Query":
-            if self.__handleRopeError(result):
+            if self.handleRopeError(result):
                 title = result["Title"]
                 if "Location" in result:
                     location = result["Location"]
@@ -2077,7 +1864,7 @@
         @param result dictionary containing the result data
         @type dict
         """
-        if self.__handleRopeError(result):
+        if self.handleRopeError(result):
             title = result["Title"]
             if result["EntriesCount"] > 0:
                 from MatchesDialog import MatchesDialog
@@ -2182,7 +1969,7 @@
         @param result dictionary containing the result data
         @type dict
         """
-        if self.__handleRopeError(result):
+        if self.handleRopeError(result):
             title = result["Title"]
             
             E5MessageBox.information(
@@ -2333,22 +2120,22 @@
             act.setEnabled(False)
         self.__mainMenu.menuAction().setEnabled(False)
         
-        self.stopClient()
+        if self.__helpDialog is not None:
+            self.__helpDialog.close()
+            self.__helpDialog = None
+        if self.__historyDialog is not None:
+            self.__historyDialog.close()
+            self.__historyDialog = None
+        
+        self.sendJson("CloseProject", {}, flush=True)
         
         self.__projectopen = False
         self.__projectpath = ''
         self.__projectLanguage = ""
         self.__ropeConfig = {}
+        
+        self.stopClient()
     
-    # TODO: delete this or move to client
-##    def getProject(self):
-##        """
-##        Public method to get a reference to the rope project object.
-##        
-##        @return reference to the rope project object (RopeProject)
-##        """
-##        return self.__project
-##    
     def confirmBufferIsSaved(self, editor):
         """
         Public method to check, if an editor has unsaved changes.
@@ -2372,25 +2159,18 @@
         self.sendJson("Validate", {})
         return res
     
-    # TODO: port this
-    def refreshEditors(self, changes):
+    def refreshEditors(self, changedFiles):
         """
         Public method to refresh modified editors.
         
-        @param changes reference to the ChangeSet object
-            (rope.base.change.ChangeSet)
+        @param changedFiles list of changed files
+        @type list of str
         """
         vm = e5App().getObject("ViewManager")
-        
-        changedFiles = []
-#        for resource in changes.get_changed_resources():
-#            if not resource.is_folder():
-#                changedFiles.append(resource.real_path)
-        
         openFiles = [Utilities.normcasepath(f) for f in vm.getOpenFilenames()]
         
-        for file in changedFiles:
-            normfile = Utilities.normcasepath(file)
+        for fileName in changedFiles:
+            normfile = Utilities.normcasepath(fileName)
             if normfile in openFiles:
                 editor = vm.getEditor(normfile)[1]
                 editor.refresh()
--- a/RefactoringRope/RefactoringClient.py	Sat Sep 16 18:51:19 2017 +0200
+++ b/RefactoringRope/RefactoringClient.py	Sun Sep 17 17:03:16 2017 +0200
@@ -46,6 +46,7 @@
         
         self.__methodMapping = {
             "AbortAction": self.__abortAction,
+            "CloseProject": self.__closeProject,
             "Validate": self.__validate,
             "QueryReferences": self.__queryReferences,
             "QueryDefinition": self.__queryDefinition,
@@ -54,6 +55,7 @@
             "ConfigChanged": self.__configChanged,
             "PerformSoa": self.__performSOA,
             "ReportChanged": self.__reportChanged,
+            "History": self.__processHistory,
         }
         
         from FileSystemCommands import RefactoringClientFileSystemCommands
@@ -64,6 +66,8 @@
             self.__projectpath, fscommands=self.__fsCommands)
         
         self.__progressHandle = None
+        
+        self.__changes = {}     # dict storing the retrieved changes
     
     def handleCall(self, method, params):
         """
@@ -123,6 +127,16 @@
         """
         self.__project.validate(self.__project.root)
     
+    def __closeProject(self, params):
+        """
+        Private slot to validate the project.
+        
+        @param params dictionary containing the method parameters sent by
+            the server
+        @type dict
+        """
+        self.__project.close()
+    
     def __getConfig(self, params):
         """
         Private method to send some configuration data to the server.
@@ -322,6 +336,80 @@
         except Exception:
             # simply ignore it
             pass
+    
+    def __processHistory(self, params):
+        """
+        Private method to process the various history related requests.
+        
+        @param params dictionary containing the method parameters sent by
+            the server
+        @type dict
+        """
+        subcommand = params["Subcommand"]
+        if subcommand == "Get":
+            self.__changes = {}
+            if params["Filename"]:
+                # file history
+                resource = rope.base.libutils.path_to_resource(
+                    self.__project, params["Filename"])
+                undoList = []
+                for change in reversed(self.__project.history.undo_list):
+                    if resource in change.get_changed_resources():
+                        undoList.append(change)
+                redoList = []
+                for change in self.__project.history.redo_list:
+                    if resource in change.get_changed_resources():
+                        redoList.append(change)
+            else:
+                # project history
+                undoList = list(reversed(self.__project.history.undo_list))
+                redoList = self.__project.history.redo_list
+            
+            result = {"Subcommand": "Histories"}
+            result["Undo"] = []
+            for change in undoList:
+                self.__changes[id(change)] = change
+                result["Undo"].append([str(change), id(change)])
+            result["Redo"] = []
+            for change in redoList:
+                self.__changes[id(change)] = change
+                result["Redo"].append([str(change), id(change)])
+            
+            self.sendJson("HistoryResult", result)
+        
+        elif subcommand == "GetChange":
+            result = {
+                "Subcommand": "ChangeDescription",
+                "Description": self.__changes[params["Id"]].get_description()
+            }
+            
+            self.sendJson("HistoryResult", result)
+        
+        elif subcommand in ["Undo", "Redo"]:
+            change = self.__changes[params["Id"]]
+            from ProgressHandle import ProgressHandle
+            self.__progressHandle = ProgressHandle(self, change.description,
+                                                   False)
+            if subcommand == "Undo":
+                self.__project.history.undo(
+                    change, task_handle=self.__progressHandle)
+            else:
+                self.__project.history.redo(
+                    change, task_handle=self.__progressHandle)
+            self.__progressHandle.reset()
+            self.__progressHandle = None
+            
+            result = {
+                "Subcommand": subcommand,
+                "ChangedFiles": [
+                    res.real_path for res in change.get_changed_resources()
+                ],
+            }
+            
+            self.sendJson("HistoryResult", result)
+        
+        elif subcommand == "Clear":
+            self.__project.history.clear()
 
 if __name__ == '__main__':
     if len(sys.argv) != 4:

eric ide

mercurial