blob: fc4332f75f0df9e2fed60957843a52283c44933b [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001#@PydevCodeAnalysisIgnore
2'''
3@author Fabio Zadrozny
4'''
5IS_PYTHON3K = 0
6try:
7 import __builtin__
8except ImportError:
9 import builtins as __builtin__ # Python 3.0
10 IS_PYTHON3K = 1
11
12try:
13 True
14 False
15except NameError:
16 #If it's not defined, let's define it now.
17 setattr(__builtin__, 'True', 1) #Python 3.0 does not accept __builtin__.True = 1 in its syntax
18 setattr(__builtin__, 'False', 0)
19
20import pydevd_constants
21
22try:
23 from java.lang import Thread
24 IS_JYTHON = True
25 SERVER_NAME = 'jycompletionserver'
26 import jyimportsTipper #as importsTipper #changed to be backward compatible with 1.5
27 importsTipper = jyimportsTipper
28
29except ImportError:
30 #it is python
31 IS_JYTHON = False
32 SERVER_NAME = 'pycompletionserver'
33 if pydevd_constants.USE_LIB_COPY:
34 from _pydev_threading import Thread
35 else:
36 from threading import Thread
37 import importsTipper
38
39
40if pydevd_constants.USE_LIB_COPY:
41 import _pydev_socket as socket
42else:
43 import socket
44
45import sys
46if sys.platform == "darwin":
47 #See: https://sourceforge.net/projects/pydev/forums/forum/293649/topic/3454227
48 try:
49 import _CF #Don't fail if it doesn't work.
50 except:
51 pass
52
53
54#initial sys.path
55_sys_path = []
56for p in sys.path:
57 #changed to be compatible with 1.5
58 _sys_path.append(p)
59
60#initial sys.modules
61_sys_modules = {}
62for name, mod in sys.modules.items():
63 _sys_modules[name] = mod
64
65
66import traceback
67
68if pydevd_constants.USE_LIB_COPY:
69 import _pydev_time as time
70else:
71 import time
72
73try:
74 import StringIO
75except:
76 import io as StringIO #Python 3.0
77
78try:
79 from urllib import quote_plus, unquote_plus
80except ImportError:
81 from urllib.parse import quote_plus, unquote_plus #Python 3.0
82
83INFO1 = 1
84INFO2 = 2
85WARN = 4
86ERROR = 8
87
88DEBUG = INFO1 | ERROR
89
90def dbg(s, prior):
91 if prior & DEBUG != 0:
92 sys.stdout.write('%s\n' % (s,))
93# f = open('c:/temp/test.txt', 'a')
94# print_ >> f, s
95# f.close()
96
97import pydev_localhost
98HOST = pydev_localhost.get_localhost() # Symbolic name meaning the local host
99
100MSG_KILL_SERVER = '@@KILL_SERVER_END@@'
101MSG_COMPLETIONS = '@@COMPLETIONS'
102MSG_END = 'END@@'
103MSG_INVALID_REQUEST = '@@INVALID_REQUEST'
104MSG_JYTHON_INVALID_REQUEST = '@@JYTHON_INVALID_REQUEST'
105MSG_CHANGE_DIR = '@@CHANGE_DIR:'
106MSG_OK = '@@MSG_OK_END@@'
107MSG_BIKE = '@@BIKE'
108MSG_PROCESSING = '@@PROCESSING_END@@'
109MSG_PROCESSING_PROGRESS = '@@PROCESSING:%sEND@@'
110MSG_IMPORTS = '@@IMPORTS:'
111MSG_PYTHONPATH = '@@PYTHONPATH_END@@'
112MSG_CHANGE_PYTHONPATH = '@@CHANGE_PYTHONPATH:'
113MSG_SEARCH = '@@SEARCH'
114
115BUFFER_SIZE = 1024
116
117
118
119currDirModule = None
120
121def CompleteFromDir(dir):
122 '''
123 This is necessary so that we get the imports from the same dir where the file
124 we are completing is located.
125 '''
126 global currDirModule
127 if currDirModule is not None:
128 del sys.path[currDirModule]
129
130 sys.path.insert(0, dir)
131
132
133def ChangePythonPath(pythonpath):
134 '''Changes the pythonpath (clears all the previous pythonpath)
135
136 @param pythonpath: string with paths separated by |
137 '''
138
139 split = pythonpath.split('|')
140 sys.path = []
141 for path in split:
142 path = path.strip()
143 if len(path) > 0:
144 sys.path.append(path)
145
146class KeepAliveThread(Thread):
147 def __init__(self, socket):
148 Thread.__init__(self)
149 self.socket = socket
150 self.processMsgFunc = None
151 self.lastMsg = None
152
153 def run(self):
154 time.sleep(0.1)
155
156 def send(s, msg):
157 if IS_PYTHON3K:
158 s.send(bytearray(msg, 'utf-8'))
159 else:
160 s.send(msg)
161
162 while self.lastMsg == None:
163
164 if self.processMsgFunc != None:
165 s = MSG_PROCESSING_PROGRESS % quote_plus(self.processMsgFunc())
166 sent = send(self.socket, s)
167 else:
168 sent = send(self.socket, MSG_PROCESSING)
169 if sent == 0:
170 sys.exit(0) #connection broken
171 time.sleep(0.1)
172
173 sent = send(self.socket, self.lastMsg)
174 if sent == 0:
175 sys.exit(0) #connection broken
176
177class Processor:
178
179 def __init__(self):
180 # nothing to do
181 return
182
183 def removeInvalidChars(self, msg):
184 try:
185 msg = str(msg)
186 except UnicodeDecodeError:
187 pass
188
189 if msg:
190 try:
191 return quote_plus(msg)
192 except:
193 sys.stdout.write('error making quote plus in %s\n' % (msg,))
194 raise
195 return ' '
196
197 def formatCompletionMessage(self, defFile, completionsList):
198 '''
199 Format the completions suggestions in the following format:
200 @@COMPLETIONS(modFile(token,description),(token,description),(token,description))END@@
201 '''
202 compMsg = []
203 compMsg.append('%s' % defFile)
204 for tup in completionsList:
205 compMsg.append(',')
206
207 compMsg.append('(')
208 compMsg.append(str(self.removeInvalidChars(tup[0]))) #token
209 compMsg.append(',')
210 compMsg.append(self.removeInvalidChars(tup[1])) #description
211
212 if(len(tup) > 2):
213 compMsg.append(',')
214 compMsg.append(self.removeInvalidChars(tup[2])) #args - only if function.
215
216 if(len(tup) > 3):
217 compMsg.append(',')
218 compMsg.append(self.removeInvalidChars(tup[3])) #TYPE
219
220 compMsg.append(')')
221
222 return '%s(%s)%s' % (MSG_COMPLETIONS, ''.join(compMsg), MSG_END)
223
224
225class T(Thread):
226
227 def __init__(self, thisP, serverP):
228 Thread.__init__(self)
229 self.thisPort = thisP
230 self.serverPort = serverP
231 self.socket = None #socket to send messages.
232 self.processor = Processor()
233
234
235 def connectToServer(self):
236 self.socket = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
237 try:
238 s.connect((HOST, self.serverPort))
239 except:
240 sys.stderr.write('Error on connectToServer with parameters: host: %s port: %s\n' % (HOST, self.serverPort))
241 raise
242
243 def getCompletionsMessage(self, defFile, completionsList):
244 '''
245 get message with completions.
246 '''
247 return self.processor.formatCompletionMessage(defFile, completionsList)
248
249 def getTokenAndData(self, data):
250 '''
251 When we receive this, we have 'token):data'
252 '''
253 token = ''
254 for c in data:
255 if c != ')':
256 token = token + c
257 else:
258 break;
259
260 return token, data.lstrip(token + '):')
261
262
263 def run(self):
264 # Echo server program
265 try:
266 import _pydev_log
267 log = _pydev_log.Log()
268
269 dbg(SERVER_NAME + ' creating socket' , INFO1)
270 try:
271 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
272 s.bind((HOST, self.thisPort))
273 except:
274 sys.stderr.write('Error connecting with parameters: host: %s port: %s\n' % (HOST, self.serverPort))
275 raise
276 s.listen(1) #socket to receive messages.
277
278
279 #we stay here until we are connected.
280 #we only accept 1 client.
281 #the exit message for the server is @@KILL_SERVER_END@@
282 dbg(SERVER_NAME + ' waiting for connection' , INFO1)
283 conn, addr = s.accept()
284 time.sleep(0.5) #wait a little before connecting to JAVA server
285
286 dbg(SERVER_NAME + ' waiting to java client' , INFO1)
287 #after being connected, create a socket as a client.
288 self.connectToServer()
289
290 dbg(SERVER_NAME + ' Connected by ' + str(addr), INFO1)
291
292
293 while 1:
294 data = ''
295 returnMsg = ''
296 keepAliveThread = KeepAliveThread(self.socket)
297
298 while data.find(MSG_END) == -1:
299 received = conn.recv(BUFFER_SIZE)
300 if len(received) == 0:
301 sys.exit(0) #ok, connection ended
302 if IS_PYTHON3K:
303 data = data + received.decode('utf-8')
304 else:
305 data = data + received
306
307 try:
308 try:
309 if data.find(MSG_KILL_SERVER) != -1:
310 dbg(SERVER_NAME + ' kill message received', INFO1)
311 #break if we received kill message.
312 self.ended = True
313 sys.exit(0)
314
315 dbg(SERVER_NAME + ' starting keep alive thread', INFO2)
316 keepAliveThread.start()
317
318 if data.find(MSG_PYTHONPATH) != -1:
319 comps = []
320 for p in _sys_path:
321 comps.append((p, ' '))
322 returnMsg = self.getCompletionsMessage(None, comps)
323
324 else:
325 data = data[:data.rfind(MSG_END)]
326
327 if data.startswith(MSG_IMPORTS):
328 data = data.replace(MSG_IMPORTS, '')
329 data = unquote_plus(data)
330 defFile, comps = importsTipper.GenerateTip(data, log)
331 returnMsg = self.getCompletionsMessage(defFile, comps)
332
333 elif data.startswith(MSG_CHANGE_PYTHONPATH):
334 data = data.replace(MSG_CHANGE_PYTHONPATH, '')
335 data = unquote_plus(data)
336 ChangePythonPath(data)
337 returnMsg = MSG_OK
338
339 elif data.startswith(MSG_SEARCH):
340 data = data.replace(MSG_SEARCH, '')
341 data = unquote_plus(data)
342 (f, line, col), foundAs = importsTipper.Search(data)
343 returnMsg = self.getCompletionsMessage(f, [(line, col, foundAs)])
344
345 elif data.startswith(MSG_CHANGE_DIR):
346 data = data.replace(MSG_CHANGE_DIR, '')
347 data = unquote_plus(data)
348 CompleteFromDir(data)
349 returnMsg = MSG_OK
350
351 elif data.startswith(MSG_BIKE):
352 returnMsg = MSG_INVALID_REQUEST #No longer supported.
353
354 else:
355 returnMsg = MSG_INVALID_REQUEST
356 except SystemExit:
357 returnMsg = self.getCompletionsMessage(None, [('Exit:', 'SystemExit', '')])
358 keepAliveThread.lastMsg = returnMsg
359 raise
360 except:
361 dbg(SERVER_NAME + ' exception occurred', ERROR)
362 s = StringIO.StringIO()
363 traceback.print_exc(file=s)
364
365 err = s.getvalue()
366 dbg(SERVER_NAME + ' received error: ' + str(err), ERROR)
367 returnMsg = self.getCompletionsMessage(None, [('ERROR:', '%s\nLog:%s' % (err, log.GetContents()), '')])
368
369
370 finally:
371 log.Clear()
372 keepAliveThread.lastMsg = returnMsg
373
374 conn.close()
375 self.ended = True
376 sys.exit(0) #connection broken
377
378
379 except SystemExit:
380 raise
381 #No need to log SystemExit error
382 except:
383 s = StringIO.StringIO()
384 exc_info = sys.exc_info()
385
386 traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], limit=None, file=s)
387 err = s.getvalue()
388 dbg(SERVER_NAME + ' received error: ' + str(err), ERROR)
389 raise
390
391if __name__ == '__main__':
392
393 thisPort = int(sys.argv[1]) #this is from where we want to receive messages.
394 serverPort = int(sys.argv[2])#this is where we want to write messages.
395
396 t = T(thisPort, serverPort)
397 dbg(SERVER_NAME + ' will start', INFO1)
398 t.start()
399 time.sleep(5)
400 t.join()