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 |