blob: 154ad630aca32d1381132a8084e71ab70f240cd4 [file] [log] [blame]
#@PydevCodeAnalysisIgnore
'''
@author Fabio Zadrozny
'''
IS_PYTHON3K = 0
try:
import __builtin__
except ImportError:
import builtins as __builtin__ # Python 3.0
IS_PYTHON3K = 1
try:
True
False
except NameError:
# If it's not defined, let's define it now.
setattr(__builtin__, 'True', 1) # Python 3.0 does not accept __builtin__.True = 1 in its syntax
setattr(__builtin__, 'False', 0)
from pydevd_constants import IS_JYTHON
if IS_JYTHON:
import java.lang
SERVER_NAME = 'jycompletionserver'
import _pydev_jy_imports_tipper # as _pydev_imports_tipper #changed to be backward compatible with 1.5
_pydev_imports_tipper = _pydev_jy_imports_tipper
else:
# it is python
SERVER_NAME = 'pycompletionserver'
import _pydev_imports_tipper
from _pydev_imps import _pydev_socket as socket
import sys
if sys.platform == "darwin":
# See: https://sourceforge.net/projects/pydev/forums/forum/293649/topic/3454227
try:
import _CF # Don't fail if it doesn't work -- do it because it must be loaded on the main thread! @UnresolvedImport @UnusedImport
except:
pass
# initial sys.path
_sys_path = []
for p in sys.path:
# changed to be compatible with 1.5
_sys_path.append(p)
# initial sys.modules
_sys_modules = {}
for name, mod in sys.modules.items():
_sys_modules[name] = mod
import traceback
from _pydev_imps import _pydev_time as time
try:
import StringIO
except:
import io as StringIO #Python 3.0
try:
from urllib import quote_plus, unquote_plus
except ImportError:
from urllib.parse import quote_plus, unquote_plus #Python 3.0
INFO1 = 1
INFO2 = 2
WARN = 4
ERROR = 8
DEBUG = INFO1 | ERROR
def dbg(s, prior):
if prior & DEBUG != 0:
sys.stdout.write('%s\n' % (s,))
# f = open('c:/temp/test.txt', 'a')
# print_ >> f, s
# f.close()
import pydev_localhost
HOST = pydev_localhost.get_localhost() # Symbolic name meaning the local host
MSG_KILL_SERVER = '@@KILL_SERVER_END@@'
MSG_COMPLETIONS = '@@COMPLETIONS'
MSG_END = 'END@@'
MSG_INVALID_REQUEST = '@@INVALID_REQUEST'
MSG_JYTHON_INVALID_REQUEST = '@@JYTHON_INVALID_REQUEST'
MSG_CHANGE_DIR = '@@CHANGE_DIR:'
MSG_OK = '@@MSG_OK_END@@'
MSG_IMPORTS = '@@IMPORTS:'
MSG_PYTHONPATH = '@@PYTHONPATH_END@@'
MSG_CHANGE_PYTHONPATH = '@@CHANGE_PYTHONPATH:'
MSG_JEDI = '@@MSG_JEDI:'
MSG_SEARCH = '@@SEARCH'
BUFFER_SIZE = 1024
currDirModule = None
def CompleteFromDir(directory):
'''
This is necessary so that we get the imports from the same directory where the file
we are completing is located.
'''
global currDirModule
if currDirModule is not None:
if len(sys.path) > 0 and sys.path[0] == currDirModule:
del sys.path[0]
currDirModule = directory
sys.path.insert(0, directory)
def ChangePythonPath(pythonpath):
'''Changes the pythonpath (clears all the previous pythonpath)
@param pythonpath: string with paths separated by |
'''
split = pythonpath.split('|')
sys.path = []
for path in split:
path = path.strip()
if len(path) > 0:
sys.path.append(path)
class Processor:
def __init__(self):
# nothing to do
return
def removeInvalidChars(self, msg):
try:
msg = str(msg)
except UnicodeDecodeError:
pass
if msg:
try:
return quote_plus(msg)
except:
sys.stdout.write('error making quote plus in %s\n' % (msg,))
raise
return ' '
def formatCompletionMessage(self, defFile, completionsList):
'''
Format the completions suggestions in the following format:
@@COMPLETIONS(modFile(token,description),(token,description),(token,description))END@@
'''
compMsg = []
compMsg.append('%s' % defFile)
for tup in completionsList:
compMsg.append(',')
compMsg.append('(')
compMsg.append(str(self.removeInvalidChars(tup[0]))) # token
compMsg.append(',')
compMsg.append(self.removeInvalidChars(tup[1])) # description
if(len(tup) > 2):
compMsg.append(',')
compMsg.append(self.removeInvalidChars(tup[2])) # args - only if function.
if(len(tup) > 3):
compMsg.append(',')
compMsg.append(self.removeInvalidChars(tup[3])) # TYPE
compMsg.append(')')
return '%s(%s)%s' % (MSG_COMPLETIONS, ''.join(compMsg), MSG_END)
class Exit(Exception):
pass
class CompletionServer:
def __init__(self, port):
self.ended = False
self.port = port
self.socket = None # socket to send messages.
self.exit_process_on_kill = True
self.processor = Processor()
def connectToServer(self):
from _pydev_imps import _pydev_socket as socket
self.socket = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((HOST, self.port))
except:
sys.stderr.write('Error on connectToServer with parameters: host: %s port: %s\n' % (HOST, self.port))
raise
def getCompletionsMessage(self, defFile, completionsList):
'''
get message with completions.
'''
return self.processor.formatCompletionMessage(defFile, completionsList)
def getTokenAndData(self, data):
'''
When we receive this, we have 'token):data'
'''
token = ''
for c in data:
if c != ')':
token = token + c
else:
break;
return token, data.lstrip(token + '):')
def emulated_sendall(self, msg):
MSGLEN = 1024 * 20
totalsent = 0
while totalsent < MSGLEN:
sent = self.socket.send(msg[totalsent:])
if sent == 0:
return
totalsent = totalsent + sent
def send(self, msg):
if not hasattr(self.socket, 'sendall'):
#Older versions (jython 2.1)
self.emulated_sendall(msg)
else:
if IS_PYTHON3K:
self.socket.sendall(bytearray(msg, 'utf-8'))
else:
self.socket.sendall(msg)
def run(self):
# Echo server program
try:
import _pydev_log
log = _pydev_log.Log()
dbg(SERVER_NAME + ' connecting to java server on %s (%s)' % (HOST, self.port) , INFO1)
# after being connected, create a socket as a client.
self.connectToServer()
dbg(SERVER_NAME + ' Connected to java server', INFO1)
while not self.ended:
data = ''
while data.find(MSG_END) == -1:
received = self.socket.recv(BUFFER_SIZE)
if len(received) == 0:
raise Exit() # ok, connection ended
if IS_PYTHON3K:
data = data + received.decode('utf-8')
else:
data = data + received
try:
try:
if data.find(MSG_KILL_SERVER) != -1:
dbg(SERVER_NAME + ' kill message received', INFO1)
# break if we received kill message.
self.ended = True
raise Exit()
dbg(SERVER_NAME + ' starting keep alive thread', INFO2)
if data.find(MSG_PYTHONPATH) != -1:
comps = []
for p in _sys_path:
comps.append((p, ' '))
self.send(self.getCompletionsMessage(None, comps))
else:
data = data[:data.rfind(MSG_END)]
if data.startswith(MSG_IMPORTS):
data = data[len(MSG_IMPORTS):]
data = unquote_plus(data)
defFile, comps = _pydev_imports_tipper.GenerateTip(data, log)
self.send(self.getCompletionsMessage(defFile, comps))
elif data.startswith(MSG_CHANGE_PYTHONPATH):
data = data[len(MSG_CHANGE_PYTHONPATH):]
data = unquote_plus(data)
ChangePythonPath(data)
self.send(MSG_OK)
elif data.startswith(MSG_JEDI):
data = data[len(MSG_JEDI):]
data = unquote_plus(data)
line, column, encoding, path, source = data.split('|', 4)
try:
import jedi # @UnresolvedImport
except:
self.send(self.getCompletionsMessage(None, [('Error on import jedi', 'Error importing jedi', '')]))
else:
script = jedi.Script(
# Line +1 because it expects lines 1-based (and col 0-based)
source=source,
line=int(line) + 1,
column=int(column),
source_encoding=encoding,
path=path,
)
lst = []
for completion in script.completions():
t = completion.type
if t == 'class':
t = '1'
elif t == 'function':
t = '2'
elif t == 'import':
t = '0'
elif t == 'keyword':
continue # Keywords are already handled in PyDev
elif t == 'statement':
t = '3'
else:
t = '-1'
# gen list(tuple(name, doc, args, type))
lst.append((completion.name, '', '', t))
self.send(self.getCompletionsMessage('empty', lst))
elif data.startswith(MSG_SEARCH):
data = data[len(MSG_SEARCH):]
data = unquote_plus(data)
(f, line, col), foundAs = _pydev_imports_tipper.Search(data)
self.send(self.getCompletionsMessage(f, [(line, col, foundAs)]))
elif data.startswith(MSG_CHANGE_DIR):
data = data[len(MSG_CHANGE_DIR):]
data = unquote_plus(data)
CompleteFromDir(data)
self.send(MSG_OK)
else:
self.send(MSG_INVALID_REQUEST)
except Exit:
self.send(self.getCompletionsMessage(None, [('Exit:', 'SystemExit', '')]))
raise
except:
dbg(SERVER_NAME + ' exception occurred', ERROR)
s = StringIO.StringIO()
traceback.print_exc(file=s)
err = s.getvalue()
dbg(SERVER_NAME + ' received error: ' + str(err), ERROR)
self.send(self.getCompletionsMessage(None, [('ERROR:', '%s\nLog:%s' % (err, log.GetContents()), '')]))
finally:
log.Clear()
self.socket.close()
self.ended = True
raise Exit() # connection broken
except Exit:
if self.exit_process_on_kill:
sys.exit(0)
# No need to log SystemExit error
except:
s = StringIO.StringIO()
exc_info = sys.exc_info()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], limit=None, file=s)
err = s.getvalue()
dbg(SERVER_NAME + ' received error: ' + str(err), ERROR)
raise
if __name__ == '__main__':
port = int(sys.argv[1]) # this is from where we want to receive messages.
t = CompletionServer(port)
dbg(SERVER_NAME + ' will start', INFO1)
t.run()