RefactoringRope/Refactoring.py

branch
server_client_variant
changeset 160
989cd767992b
parent 151
5260100b6700
child 162
55eaaed9d590
equal deleted inserted replaced
159:eb7552dd73c8 160:989cd767992b
22 22
23 import rope 23 import rope
24 import rope.base.libutils 24 import rope.base.libutils
25 import rope.base.exceptions 25 import rope.base.exceptions
26 26
27 from PyQt5.QtCore import QObject 27 from PyQt5.QtCore import pyqtSlot, QProcess
28 from PyQt5.QtWidgets import QMenu, QApplication, QDialog, QAction 28 from PyQt5.QtWidgets import QMenu, QApplication, QDialog, QAction
29 from PyQt5.Qsci import QsciScintilla 29 from PyQt5.Qsci import QsciScintilla
30 from PyQt5.QtNetwork import QTcpServer, QHostAddress
30 31
31 from E5Gui.E5Application import e5App 32 from E5Gui.E5Application import e5App
32 from E5Gui import E5MessageBox 33 from E5Gui import E5MessageBox
33 from E5Gui.E5Action import E5Action 34 from E5Gui.E5Action import E5Action
34 35
35 import Utilities 36 import Utilities
37 import Preferences
36 38
37 39
38 class Refactoring(QObject): 40 # TODO: rename this (and the module) to RefactoringServer once done
41 class Refactoring(QTcpServer):
39 """ 42 """
40 Class implementing the refactoring interface to rope. 43 Class implementing the refactoring interface to rope.
41 """ 44 """
42 def __init__(self, plugin, parent=None): 45 def __init__(self, plugin, parent=None):
43 """ 46 """
44 Constructor 47 Constructor
45 48
46 @param plugin reference to the plugin object 49 @param plugin reference to the plugin object
47 @param parent parent (QObject) 50 @param parent parent (QObject)
48 """ 51 """
49 QObject.__init__(self, parent) 52 super(Refactoring, self).__init__(parent)
50 53
51 self.__plugin = plugin 54 self.__plugin = plugin
52 self.__ui = parent 55 self.__ui = parent
53 self.__e5project = e5App().getObject("Project") 56 self.__e5project = e5App().getObject("Project")
54 self.__projectpath = '' 57 self.__projectpath = ''
56 self.__projectopen = False 59 self.__projectopen = False
57 60
58 self.__mainMenu = None 61 self.__mainMenu = None
59 self.__helpDialog = None 62 self.__helpDialog = None
60 63
64 self.__refactoringProcess = None
65 self.__refactoringConnection = None
66
61 # Rope objects 67 # Rope objects
68 # TODO: move this to RefactoringClient
62 self.__project = None 69 self.__project = None
63 70
71 # TODO: split this between RefactoringClient and this server
64 from FileSystemCommands import E5FileSystemCommands 72 from FileSystemCommands import E5FileSystemCommands
65 self.__fsCommands = E5FileSystemCommands(self.__e5project) 73 self.__fsCommands = E5FileSystemCommands(self.__e5project)
74
75 # setup the network interface
76 networkInterface = Preferences.getDebugger("NetworkInterface")
77 if networkInterface == "all" or '.' in networkInterface:
78 # IPv4
79 self.__hostAddress = '127.0.0.1'
80 else:
81 # IPv6
82 self.__hostAddress = '::1'
83 self.listen(QHostAddress(self.__hostAddress))
84
85 self.newConnection.connect(self.__handleNewConnection)
86
87 port = self.serverPort()
88 ## Note: Need the port if started external in debugger:
89 print('Refactoring server listening on: {0:d}'.format(port))
90 # __IGNORE_WARNING__
66 91
67 def initActions(self): 92 def initActions(self):
68 """ 93 """
69 Public method to define the refactoring actions. 94 Public method to define the refactoring actions.
70 """ 95 """
2209 self.__projectopen = True 2234 self.__projectopen = True
2210 self.__projectpath = self.__e5project.getProjectPath() 2235 self.__projectpath = self.__e5project.getProjectPath()
2211 self.__projectLanguage = self.__e5project.getProjectLanguage() 2236 self.__projectLanguage = self.__e5project.getProjectLanguage()
2212 2237
2213 if self.__projectLanguage.startswith("Python"): 2238 if self.__projectLanguage.startswith("Python"):
2214 import rope.base.project 2239 if self.__projectLanguage == "Python2":
2215 self.__project = rope.base.project.Project( 2240 interpreter = Preferences.getDebugger("PythonInterpreter")
2216 self.__projectpath, fscommands=self.__fsCommands) 2241 elif self.__projectLanguage == "Python3":
2217 for act in self.actions: 2242 interpreter = Preferences.getDebugger("Python3Interpreter")
2218 act.setEnabled(True) 2243 else:
2244 interpreter = ""
2245 if interpreter:
2246 process = self.__startRefactoringClient(interpreter)
2247 if process is None:
2248 self.__ui.appendToStderr(self.tr(
2249 "Project language '{0}' is not supported because"
2250 " the configured interpreter could not be started."
2251 " Refactoring is disabled."
2252 ).format(self.__projectLanguage))
2253 else:
2254 self.__refactoringProcess = process
2255 ## import rope.base.project
2256 ## self.__project = rope.base.project.Project(
2257 ## self.__projectpath, fscommands=self.__fsCommands)
2258 for act in self.actions:
2259 act.setEnabled(True)
2260 else:
2261 self.__ui.appendToStderr(self.tr(
2262 "Project language '{0}' is not supported because no"
2263 " suitable interpreter is configured. Refactoring is"
2264 " disabled."
2265 ).format(self.__projectLanguage))
2219 2266
2220 def projectClosed(self): 2267 def projectClosed(self):
2221 """ 2268 """
2222 Public slot to handle the projectClosed signal. 2269 Public slot to handle the projectClosed signal.
2223 """ 2270 """
2224 for act in self.actions: 2271 for act in self.actions:
2225 act.setEnabled(False) 2272 act.setEnabled(False)
2226 2273
2227 if self.__project is not None: 2274 self.__stopRefactoringClient()
2228 self.__project.close() 2275 ## if self.__project is not None:
2229 self.__project = None 2276 ## self.__project.close()
2277 ## self.__project = None
2230 2278
2231 self.__projectopen = False 2279 self.__projectopen = False
2232 self.__projectpath = '' 2280 self.__projectpath = ''
2233 self.__projectLanguage = "" 2281 self.__projectLanguage = ""
2234 2282
2283 # TODO: delete this or move to client
2235 def getProject(self): 2284 def getProject(self):
2236 """ 2285 """
2237 Public method to get a reference to the rope project object. 2286 Public method to get a reference to the rope project object.
2238 2287
2239 @return reference to the rope project object (RopeProject) 2288 @return reference to the rope project object (RopeProject)
2304 self.__project, filename, oldSource) 2353 self.__project, filename, oldSource)
2305 except RuntimeError: 2354 except RuntimeError:
2306 # this could come from trying to do PyQt4/PyQt5 mixed stuff 2355 # this could come from trying to do PyQt4/PyQt5 mixed stuff
2307 # simply ignore it 2356 # simply ignore it
2308 pass 2357 pass
2358
2359 #######################################################################
2360 ## Methods below handle the network connection
2361 #######################################################################
2362
2363 @pyqtSlot()
2364 def __handleNewConnection(self):
2365 """
2366 Private slot for new incomming connections from the refactoring client.
2367 """
2368 if self.__refactoringConnection is not None:
2369 self.__refactoringConnection.close()
2370 self.__refactoringConnection = None
2371
2372 connection = self.nextPendingConnection()
2373 if not connection.isValid():
2374 return
2375
2376 self.__refactoringConnection = connection
2377 connection.readyRead.connect(self.__receiveJson)
2378 connection.disconnected.connect(self.__handleDisconnect)
2379
2380 self.__sendJson("ping", {})
2381
2382 @pyqtSlot()
2383 def __handleDisconnect(self):
2384 """
2385 Private slot handling a disconnect of the refactoring client.
2386 """
2387 if self.__refactoringConnection is not None:
2388 self.__refactoringConnection.close()
2389
2390 self.__refactoringConnection = None
2391
2392 @pyqtSlot()
2393 def __receiveJson(self):
2394 """
2395 Private slot handling received data from the refactoring client.
2396 """
2397 while self.__refactoringConnection and \
2398 self.__refactoringConnection.canReadLine():
2399 data = self.__refactoringConnection.readLine()
2400 jsonLine = bytes(data).decode()
2401
2402 print("Refactoring Server: ", jsonLine) ##debug
2403
2404 self.__processJson(jsonLine)
2405 continue
2406
2407 def __processJson(self, jsonStr):
2408 """
2409 Private method to process the JSON serialized client data.
2410
2411 @param jsonStr string containing the data structure received
2412 from the refactoring client
2413 @type str
2414 """
2415 import json
2416
2417 try:
2418 clientDict = json.loads(jsonStr.strip())
2419 except (TypeError, ValueError) as err:
2420 E5MessageBox.critical(
2421 None,
2422 self.tr("Refactoring Protocol Error"),
2423 self.tr("""<p>The response received from the refactoring"""
2424 """ client could not be decoded. Please report"""
2425 """ this issue with the received data to the"""
2426 """ eric bugs email address.</p>"""
2427 """<p>Error: {0}</p>"""
2428 """<p>Data:<br/>{0}</p>""").format(
2429 str(err), Utilities.html_encode(jsonStr.strip())),
2430 E5MessageBox.StandardButtons(
2431 E5MessageBox.Ok))
2432 return
2433
2434 method = clientDict["method"]
2435 params = clientDict["params"]
2436
2437 print("Method:", method)
2438 print("Params:", params)
2439
2440 if method == "pong":
2441 pass
2442
2443 elif method == "ClientException":
2444 if params["ExceptionType"] == "ProtocolError":
2445 E5MessageBox.critical(
2446 None,
2447 self.tr("Refactoring Protocol Error"),
2448 self.tr("""<p>The data received from the refactoring"""
2449 """ server could not be decoded. Please report"""
2450 """ this issue with the received data to the"""
2451 """ eric bugs email address.</p>"""
2452 """<p>Error: {0}</p>"""
2453 """<p>Data:<br/>{0}</p>""").format(
2454 params["ExceptionValue"],
2455 Utilities.html_encode(params["ProtocolData"])),
2456 E5MessageBox.StandardButtons(
2457 E5MessageBox.Ok))
2458 else:
2459 E5MessageBox.critical(
2460 None,
2461 self.tr("Refactoring Client Error"),
2462 self.tr("<p>An exception happened in the refactoring"
2463 " client. Please report it to the eric bugs"
2464 " email address.</p>"
2465 "<p>Exception: {0}</p>"
2466 "<p>Value: {1}</p>"
2467 "Traceback: {2}</p>").format(
2468 Utilities.html_encode(params["ExceptionType"]),
2469 params["ExceptionValue"],
2470 params["Traceback"].replace("\r\n", "<br/>")
2471 .replace("\n", "<br/>").replace("\r", "<br/>"),
2472 ),
2473 E5MessageBox.StandardButtons(
2474 E5MessageBox.Ok))
2475 return
2476
2477 def __sendJson(self, command, params):
2478 """
2479 Private method to send a single refactoring command to the client.
2480
2481 @param command command name to be sent
2482 @type str
2483 @param params dictionary of named parameters for the command
2484 @type dict
2485 """
2486 import json
2487
2488 commandDict = {
2489 "jsonrpc": "2.0",
2490 "method": command,
2491 "params": params,
2492 }
2493 cmd = json.dumps(commandDict) + '\n'
2494 if self.__refactoringConnection is not None:
2495 self.__refactoringConnection.write(
2496 cmd.encode('utf8', 'backslashreplace'))
2497
2498 def __startRefactoringClient(self, interpreter):
2499 """
2500 Private method to start the refactoring client.
2501
2502 @param interpreter interpreter to be used for the refactoring client
2503 @type str
2504 @return reference to the refactoring client process
2505 """
2506 if interpreter == "" or not Utilities.isinpath(interpreter):
2507 return None
2508
2509 client = os.path.join(os.path.dirname(__file__),
2510 "RefactoringClient.py")
2511 proc = QProcess()
2512 proc.setProcessChannelMode(QProcess.ForwardedChannels)
2513 args = [client, self.__hostAddress, str(self.serverPort()),
2514 self.__projectpath]
2515 proc.start(interpreter, args)
2516 if not proc.waitForStarted(10000):
2517 proc = None
2518
2519 return proc
2520
2521 def __stopRefactoringClient(self):
2522 """
2523 Private method to stop the refactoring client process.
2524 """
2525 self.__refactoringProcess.close()
2526 self.__refactoringProcess = None
2527
2528 #
2529 # eflag: noqa = M801

eric ide

mercurial