PageRenderTime 65ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/clion/plugins/python/helpers/pydev/pycompletionserver.py

https://gitlab.com/ZoZworc/install
Python | 402 lines | 335 code | 40 blank | 27 comment | 27 complexity | 8922e82b8e77c50faba3524038e05aa4 MD5 | raw file
  1. '''
  2. Entry-point module to start the code-completion server for PyDev.
  3. @author Fabio Zadrozny
  4. '''
  5. IS_PYTHON3K = 0
  6. try:
  7. import __builtin__
  8. except ImportError:
  9. import builtins as __builtin__ # Python 3.0
  10. IS_PYTHON3K = 1
  11. try:
  12. True
  13. False
  14. except NameError:
  15. # If it's not defined, let's define it now.
  16. setattr(__builtin__, 'True', 1) # Python 3.0 does not accept __builtin__.True = 1 in its syntax
  17. setattr(__builtin__, 'False', 0)
  18. from _pydevd_bundle.pydevd_constants import IS_JYTHON
  19. if IS_JYTHON:
  20. import java.lang # @UnresolvedImport
  21. SERVER_NAME = 'jycompletionserver'
  22. from _pydev_bundle import _pydev_jy_imports_tipper
  23. _pydev_imports_tipper = _pydev_jy_imports_tipper
  24. else:
  25. # it is python
  26. SERVER_NAME = 'pycompletionserver'
  27. from _pydev_bundle import _pydev_imports_tipper
  28. from _pydev_imps._pydev_saved_modules import socket
  29. import sys
  30. if sys.platform == "darwin":
  31. # See: https://sourceforge.net/projects/pydev/forums/forum/293649/topic/3454227
  32. try:
  33. import _CF # Don't fail if it doesn't work -- do it because it must be loaded on the main thread! @UnresolvedImport @UnusedImport
  34. except:
  35. pass
  36. # initial sys.path
  37. _sys_path = []
  38. for p in sys.path:
  39. # changed to be compatible with 1.5
  40. _sys_path.append(p)
  41. # initial sys.modules
  42. _sys_modules = {}
  43. for name, mod in sys.modules.items():
  44. _sys_modules[name] = mod
  45. import traceback
  46. from _pydev_imps._pydev_saved_modules import time
  47. try:
  48. import StringIO
  49. except:
  50. import io as StringIO #Python 3.0
  51. try:
  52. from urllib import quote_plus, unquote_plus
  53. except ImportError:
  54. from urllib.parse import quote_plus, unquote_plus #Python 3.0
  55. INFO1 = 1
  56. INFO2 = 2
  57. WARN = 4
  58. ERROR = 8
  59. DEBUG = INFO1 | ERROR
  60. def dbg(s, prior):
  61. if prior & DEBUG != 0:
  62. sys.stdout.write('%s\n' % (s,))
  63. # f = open('c:/temp/test.txt', 'a')
  64. # print_ >> f, s
  65. # f.close()
  66. from _pydev_bundle import pydev_localhost
  67. HOST = pydev_localhost.get_localhost() # Symbolic name meaning the local host
  68. MSG_KILL_SERVER = '@@KILL_SERVER_END@@'
  69. MSG_COMPLETIONS = '@@COMPLETIONS'
  70. MSG_END = 'END@@'
  71. MSG_INVALID_REQUEST = '@@INVALID_REQUEST'
  72. MSG_JYTHON_INVALID_REQUEST = '@@JYTHON_INVALID_REQUEST'
  73. MSG_CHANGE_DIR = '@@CHANGE_DIR:'
  74. MSG_OK = '@@MSG_OK_END@@'
  75. MSG_IMPORTS = '@@IMPORTS:'
  76. MSG_PYTHONPATH = '@@PYTHONPATH_END@@'
  77. MSG_CHANGE_PYTHONPATH = '@@CHANGE_PYTHONPATH:'
  78. MSG_JEDI = '@@MSG_JEDI:'
  79. MSG_SEARCH = '@@SEARCH'
  80. BUFFER_SIZE = 1024
  81. currDirModule = None
  82. def complete_from_dir(directory):
  83. '''
  84. This is necessary so that we get the imports from the same directory where the file
  85. we are completing is located.
  86. '''
  87. global currDirModule
  88. if currDirModule is not None:
  89. if len(sys.path) > 0 and sys.path[0] == currDirModule:
  90. del sys.path[0]
  91. currDirModule = directory
  92. sys.path.insert(0, directory)
  93. def change_python_path(pythonpath):
  94. '''Changes the pythonpath (clears all the previous pythonpath)
  95. @param pythonpath: string with paths separated by |
  96. '''
  97. split = pythonpath.split('|')
  98. sys.path = []
  99. for path in split:
  100. path = path.strip()
  101. if len(path) > 0:
  102. sys.path.append(path)
  103. class Processor:
  104. def __init__(self):
  105. # nothing to do
  106. return
  107. def remove_invalid_chars(self, msg):
  108. try:
  109. msg = str(msg)
  110. except UnicodeDecodeError:
  111. pass
  112. if msg:
  113. try:
  114. return quote_plus(msg)
  115. except:
  116. sys.stdout.write('error making quote plus in %s\n' % (msg,))
  117. raise
  118. return ' '
  119. def format_completion_message(self, defFile, completionsList):
  120. '''
  121. Format the completions suggestions in the following format:
  122. @@COMPLETIONS(modFile(token,description),(token,description),(token,description))END@@
  123. '''
  124. compMsg = []
  125. compMsg.append('%s' % defFile)
  126. for tup in completionsList:
  127. compMsg.append(',')
  128. compMsg.append('(')
  129. compMsg.append(str(self.remove_invalid_chars(tup[0]))) # token
  130. compMsg.append(',')
  131. compMsg.append(self.remove_invalid_chars(tup[1])) # description
  132. if(len(tup) > 2):
  133. compMsg.append(',')
  134. compMsg.append(self.remove_invalid_chars(tup[2])) # args - only if function.
  135. if(len(tup) > 3):
  136. compMsg.append(',')
  137. compMsg.append(self.remove_invalid_chars(tup[3])) # TYPE
  138. compMsg.append(')')
  139. return '%s(%s)%s' % (MSG_COMPLETIONS, ''.join(compMsg), MSG_END)
  140. class Exit(Exception):
  141. pass
  142. class CompletionServer:
  143. def __init__(self, port):
  144. self.ended = False
  145. self.port = port
  146. self.socket = None # socket to send messages.
  147. self.exit_process_on_kill = True
  148. self.processor = Processor()
  149. def connect_to_server(self):
  150. from _pydev_imps._pydev_saved_modules import socket
  151. self.socket = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  152. try:
  153. s.connect((HOST, self.port))
  154. except:
  155. sys.stderr.write('Error on connect_to_server with parameters: host: %s port: %s\n' % (HOST, self.port))
  156. raise
  157. def get_completions_message(self, defFile, completionsList):
  158. '''
  159. get message with completions.
  160. '''
  161. return self.processor.format_completion_message(defFile, completionsList)
  162. def get_token_and_data(self, data):
  163. '''
  164. When we receive this, we have 'token):data'
  165. '''
  166. token = ''
  167. for c in data:
  168. if c != ')':
  169. token = token + c
  170. else:
  171. break;
  172. return token, data.lstrip(token + '):')
  173. def emulated_sendall(self, msg):
  174. MSGLEN = 1024 * 20
  175. totalsent = 0
  176. while totalsent < MSGLEN:
  177. sent = self.socket.send(msg[totalsent:])
  178. if sent == 0:
  179. return
  180. totalsent = totalsent + sent
  181. def send(self, msg):
  182. if not hasattr(self.socket, 'sendall'):
  183. #Older versions (jython 2.1)
  184. self.emulated_sendall(msg)
  185. else:
  186. if IS_PYTHON3K:
  187. self.socket.sendall(bytearray(msg, 'utf-8'))
  188. else:
  189. self.socket.sendall(msg)
  190. def run(self):
  191. # Echo server program
  192. try:
  193. from _pydev_bundle import _pydev_log
  194. log = _pydev_log.Log()
  195. dbg(SERVER_NAME + ' connecting to java server on %s (%s)' % (HOST, self.port) , INFO1)
  196. # after being connected, create a socket as a client.
  197. self.connect_to_server()
  198. dbg(SERVER_NAME + ' Connected to java server', INFO1)
  199. while not self.ended:
  200. data = ''
  201. while data.find(MSG_END) == -1:
  202. received = self.socket.recv(BUFFER_SIZE)
  203. if len(received) == 0:
  204. raise Exit() # ok, connection ended
  205. if IS_PYTHON3K:
  206. data = data + received.decode('utf-8')
  207. else:
  208. data = data + received
  209. try:
  210. try:
  211. if data.find(MSG_KILL_SERVER) != -1:
  212. dbg(SERVER_NAME + ' kill message received', INFO1)
  213. # break if we received kill message.
  214. self.ended = True
  215. raise Exit()
  216. dbg(SERVER_NAME + ' starting keep alive thread', INFO2)
  217. if data.find(MSG_PYTHONPATH) != -1:
  218. comps = []
  219. for p in _sys_path:
  220. comps.append((p, ' '))
  221. self.send(self.get_completions_message(None, comps))
  222. else:
  223. data = data[:data.rfind(MSG_END)]
  224. if data.startswith(MSG_IMPORTS):
  225. data = data[len(MSG_IMPORTS):]
  226. data = unquote_plus(data)
  227. defFile, comps = _pydev_imports_tipper.generate_tip(data, log)
  228. self.send(self.get_completions_message(defFile, comps))
  229. elif data.startswith(MSG_CHANGE_PYTHONPATH):
  230. data = data[len(MSG_CHANGE_PYTHONPATH):]
  231. data = unquote_plus(data)
  232. change_python_path(data)
  233. self.send(MSG_OK)
  234. elif data.startswith(MSG_JEDI):
  235. data = data[len(MSG_JEDI):]
  236. data = unquote_plus(data)
  237. line, column, encoding, path, source = data.split('|', 4)
  238. try:
  239. import jedi # @UnresolvedImport
  240. except:
  241. self.send(self.get_completions_message(None, [('Error on import jedi', 'Error importing jedi', '')]))
  242. else:
  243. script = jedi.Script(
  244. # Line +1 because it expects lines 1-based (and col 0-based)
  245. source=source,
  246. line=int(line) + 1,
  247. column=int(column),
  248. source_encoding=encoding,
  249. path=path,
  250. )
  251. lst = []
  252. for completion in script.completions():
  253. t = completion.type
  254. if t == 'class':
  255. t = '1'
  256. elif t == 'function':
  257. t = '2'
  258. elif t == 'import':
  259. t = '0'
  260. elif t == 'keyword':
  261. continue # Keywords are already handled in PyDev
  262. elif t == 'statement':
  263. t = '3'
  264. else:
  265. t = '-1'
  266. # gen list(tuple(name, doc, args, type))
  267. lst.append((completion.name, '', '', t))
  268. self.send(self.get_completions_message('empty', lst))
  269. elif data.startswith(MSG_SEARCH):
  270. data = data[len(MSG_SEARCH):]
  271. data = unquote_plus(data)
  272. (f, line, col), foundAs = _pydev_imports_tipper.search_definition(data)
  273. self.send(self.get_completions_message(f, [(line, col, foundAs)]))
  274. elif data.startswith(MSG_CHANGE_DIR):
  275. data = data[len(MSG_CHANGE_DIR):]
  276. data = unquote_plus(data)
  277. complete_from_dir(data)
  278. self.send(MSG_OK)
  279. else:
  280. self.send(MSG_INVALID_REQUEST)
  281. except Exit:
  282. self.send(self.get_completions_message(None, [('Exit:', 'SystemExit', '')]))
  283. raise
  284. except:
  285. dbg(SERVER_NAME + ' exception occurred', ERROR)
  286. s = StringIO.StringIO()
  287. traceback.print_exc(file=s)
  288. err = s.getvalue()
  289. dbg(SERVER_NAME + ' received error: ' + str(err), ERROR)
  290. self.send(self.get_completions_message(None, [('ERROR:', '%s\nLog:%s' % (err, log.get_contents()), '')]))
  291. finally:
  292. log.clear_log()
  293. self.socket.close()
  294. self.ended = True
  295. raise Exit() # connection broken
  296. except Exit:
  297. if self.exit_process_on_kill:
  298. sys.exit(0)
  299. # No need to log SystemExit error
  300. except:
  301. s = StringIO.StringIO()
  302. exc_info = sys.exc_info()
  303. traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], limit=None, file=s)
  304. err = s.getvalue()
  305. dbg(SERVER_NAME + ' received error: ' + str(err), ERROR)
  306. raise
  307. if __name__ == '__main__':
  308. port = int(sys.argv[1]) # this is from where we want to receive messages.
  309. t = CompletionServer(port)
  310. dbg(SERVER_NAME + ' will start', INFO1)
  311. t.run()