mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'master' of git@git.labs.intellij.net:idea/ultimate
This commit is contained in:
@@ -1085,5 +1085,6 @@ if __name__ == "__main__":
|
||||
outfile.close()
|
||||
except:
|
||||
sys.stderr.write("Failed to process " + name + " while " + action + "\n")
|
||||
raise
|
||||
# Please don't add a 'raise' here. If we fail to process one module, it's not a sufficient reason
|
||||
# to cancel processing the remaining ones.
|
||||
continue
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,954 @@
|
||||
#IMPORTANT: pydevd_constants must be the 1st thing defined because it'll keep a reference to the original sys._getframe
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
|
||||
from pydevd_comm import CMD_CHANGE_VARIABLE, \
|
||||
CMD_EVALUATE_EXPRESSION, \
|
||||
CMD_EXEC_EXPRESSION, \
|
||||
CMD_GET_FRAME, \
|
||||
CMD_GET_VARIABLE, \
|
||||
CMD_LIST_THREADS, \
|
||||
CMD_REMOVE_BREAK, \
|
||||
CMD_RUN, \
|
||||
CMD_SET_BREAK, \
|
||||
CMD_STEP_INTO, \
|
||||
CMD_STEP_OVER, \
|
||||
CMD_STEP_RETURN, \
|
||||
CMD_THREAD_CREATE, \
|
||||
CMD_THREAD_KILL, \
|
||||
CMD_THREAD_RUN, \
|
||||
CMD_THREAD_SUSPEND, \
|
||||
CMD_RUN_TO_LINE, \
|
||||
CMD_RELOAD_CODE, \
|
||||
CMD_VERSION, \
|
||||
GetGlobalDebugger, \
|
||||
InternalChangeVariable, \
|
||||
InternalEvaluateExpression, \
|
||||
InternalGetFrame, \
|
||||
InternalGetVariable, \
|
||||
InternalTerminateThread, \
|
||||
NetCommand, \
|
||||
NetCommandFactory, \
|
||||
PyDBDaemonThread, \
|
||||
PydevQueue, \
|
||||
ReaderThread, \
|
||||
SetGlobalDebugger, \
|
||||
WriterThread, \
|
||||
PydevdFindThreadById, \
|
||||
PydevdLog, \
|
||||
StartClient, \
|
||||
StartServer
|
||||
|
||||
import pydevd_file_utils
|
||||
import traceback
|
||||
import pydevd_vm_type
|
||||
import pydevd_tracing
|
||||
import pydevd_io
|
||||
import pydevd_additional_thread_info
|
||||
import time
|
||||
|
||||
DONT_TRACE = {
|
||||
#commonly used things from the stdlib that we don't want to trace
|
||||
'threading.py':1,
|
||||
'Queue.py':1,
|
||||
'socket.py':1,
|
||||
|
||||
#things from pydev that we don't want to trace
|
||||
'pydevd_additional_thread_info.py':1,
|
||||
'pydevd_comm.py':1,
|
||||
'pydevd_constants.py':1,
|
||||
'pydevd_file_utils.py':1,
|
||||
'pydevd_frame.py':1,
|
||||
'pydevd_io.py':1 ,
|
||||
'pydevd_resolver.py':1 ,
|
||||
'pydevd_tracing.py':1 ,
|
||||
'pydevd_vars.py':1,
|
||||
'pydevd_vm_type.py':1,
|
||||
'pydevd.py':1 ,
|
||||
'pydevd_psyco_stub.py':1
|
||||
}
|
||||
|
||||
if IS_PY3K:
|
||||
#if we try to trace io.py it seems it can get halted (see http://bugs.python.org/issue4716)
|
||||
DONT_TRACE['io.py'] = 1
|
||||
|
||||
|
||||
connected = False
|
||||
bufferStdOutToServer = False
|
||||
bufferStdErrToServer = False
|
||||
|
||||
PyDBUseLocks = True
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# PyDBCommandThread
|
||||
#=======================================================================================================================
|
||||
class PyDBCommandThread(PyDBDaemonThread):
|
||||
|
||||
def __init__(self, pyDb):
|
||||
PyDBDaemonThread.__init__(self)
|
||||
self.pyDb = pyDb
|
||||
self.setName('pydevd.CommandThread')
|
||||
|
||||
def OnRun(self):
|
||||
time.sleep(5) #this one will only start later on (because otherwise we may not have any non-daemon threads
|
||||
|
||||
run_traced = True
|
||||
|
||||
if pydevd_vm_type.GetVmType() == pydevd_vm_type.PydevdVmType.JYTHON and sys.hexversion <= 0x020201f0:
|
||||
#don't run untraced threads if we're in jython 2.2.1 or lower
|
||||
#jython bug: if we start a thread and another thread changes the tracing facility
|
||||
#it affects other threads (it's not set only for the thread but globally)
|
||||
#Bug: http://sourceforge.net/tracker/index.php?func=detail&aid=1870039&group_id=12867&atid=112867
|
||||
run_traced = False
|
||||
|
||||
if run_traced:
|
||||
pydevd_tracing.SetTrace(None) # no debugging on this thread
|
||||
|
||||
try:
|
||||
while not self.killReceived:
|
||||
try:
|
||||
self.pyDb.processInternalCommands()
|
||||
except:
|
||||
PydevdLog(0, 'Finishing debug communication...(2)')
|
||||
time.sleep(0.5)
|
||||
except:
|
||||
pass
|
||||
#only got this error in interpreter shutdown
|
||||
#PydevdLog(0, 'Finishing debug communication...(3)')
|
||||
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# PyDB
|
||||
#=======================================================================================================================
|
||||
class PyDB:
|
||||
""" Main debugging class
|
||||
Lots of stuff going on here:
|
||||
|
||||
PyDB starts two threads on startup that connect to remote debugger (RDB)
|
||||
The threads continuously read & write commands to RDB.
|
||||
PyDB communicates with these threads through command queues.
|
||||
Every RDB command is processed by calling processNetCommand.
|
||||
Every PyDB net command is sent to the net by posting NetCommand to WriterThread queue
|
||||
|
||||
Some commands need to be executed on the right thread (suspend/resume & friends)
|
||||
These are placed on the internal command queue.
|
||||
"""
|
||||
|
||||
RUNNING_THREAD_IDS = {} #this is a dict of thread ids pointing to thread ids. Whenever a command
|
||||
#is passed to the java end that acknowledges that a thread was created,
|
||||
#the thread id should be passed here -- and if at some time we do not find
|
||||
#that thread alive anymore, we must remove it from this list and make
|
||||
#the java side know that the thread was killed.
|
||||
|
||||
def __init__(self):
|
||||
SetGlobalDebugger(self)
|
||||
pydevd_tracing.ReplaceSysSetTraceFunc()
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
self.quitting = None
|
||||
self.cmdFactory = NetCommandFactory()
|
||||
self.cmdQueue = {} # the hash of Queues. Key is thread id, value is thread
|
||||
self.breakpoints = {}
|
||||
self.readyToRun = False
|
||||
self.lock = threading.RLock()
|
||||
self.internalQueueLock = threading.Lock()
|
||||
self.finishDebuggingSession = False
|
||||
|
||||
def acquire(self):
|
||||
if PyDBUseLocks:
|
||||
self.lock.acquire()
|
||||
return True
|
||||
|
||||
def release(self):
|
||||
if PyDBUseLocks:
|
||||
self.lock.release()
|
||||
return True
|
||||
|
||||
def initializeNetwork(self, sock):
|
||||
try:
|
||||
sock.settimeout(None) # infinite, no timeouts from now on - jython does not have it
|
||||
except:
|
||||
pass
|
||||
self.writer = WriterThread(sock)
|
||||
self.reader = ReaderThread(sock)
|
||||
self.writer.start()
|
||||
self.reader.start()
|
||||
|
||||
time.sleep(0.1) # give threads time to start
|
||||
|
||||
def connect(self, host, port):
|
||||
if host:
|
||||
s = StartClient(host, port)
|
||||
else:
|
||||
s = StartServer(port)
|
||||
|
||||
self.initializeNetwork(s)
|
||||
|
||||
|
||||
def getInternalQueue(self, thread_id):
|
||||
""" returns intenal command queue for a given thread.
|
||||
if new queue is created, notify the RDB about it """
|
||||
try:
|
||||
return self.cmdQueue[thread_id]
|
||||
except KeyError:
|
||||
self.internalQueueLock.acquire()
|
||||
try:
|
||||
self.cmdQueue[thread_id] = PydevQueue.Queue()
|
||||
all_threads = threading.enumerate()
|
||||
cmd = None
|
||||
for t in all_threads:
|
||||
if GetThreadId(t) == thread_id:
|
||||
if not hasattr(t, 'additionalInfo'):
|
||||
#see http://sourceforge.net/tracker/index.php?func=detail&aid=1955428&group_id=85796&atid=577329
|
||||
#Let's create the additional info right away!
|
||||
t.additionalInfo = pydevd_additional_thread_info.PyDBAdditionalThreadInfo()
|
||||
|
||||
self.RUNNING_THREAD_IDS[thread_id] = t
|
||||
cmd = self.cmdFactory.makeThreadCreatedMessage(t)
|
||||
break
|
||||
|
||||
if cmd:
|
||||
PydevdLog(2, "found a new thread ", str(thread_id))
|
||||
self.writer.addCommand(cmd)
|
||||
else:
|
||||
PydevdLog(0, "could not find thread by id to register")
|
||||
finally:
|
||||
self.internalQueueLock.release()
|
||||
|
||||
return self.cmdQueue[thread_id]
|
||||
|
||||
|
||||
def postInternalCommand(self, int_cmd, thread_id):
|
||||
""" if thread_id is *, post to all """
|
||||
if thread_id == "*":
|
||||
for k in self.cmdQueue.keys():
|
||||
self.cmdQueue[k].put(int_cmd)
|
||||
|
||||
else:
|
||||
queue = self.getInternalQueue(thread_id)
|
||||
queue.put(int_cmd)
|
||||
|
||||
def checkOutput(self, out, outCtx):
|
||||
'''Checks the output to see if we have to send some buffered output to the debug server
|
||||
|
||||
@param out: sys.stdout or sys.stderr
|
||||
@param outCtx: the context indicating: 1=stdout and 2=stderr (to know the colors to write it)
|
||||
'''
|
||||
|
||||
try:
|
||||
v = out.getvalue()
|
||||
if v:
|
||||
self.cmdFactory.makeIoMessage(v, outCtx, self)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def processInternalCommands(self):
|
||||
'''This function processes internal commands
|
||||
'''
|
||||
|
||||
self.acquire()
|
||||
try:
|
||||
if bufferStdOutToServer:
|
||||
self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
|
||||
|
||||
if bufferStdErrToServer:
|
||||
self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
|
||||
|
||||
currThreadId = GetThreadId(threading.currentThread())
|
||||
threads = threading.enumerate()
|
||||
foundNonPyDBDaemonThread = False
|
||||
foundThreads = {}
|
||||
|
||||
for t in threads:
|
||||
tId = GetThreadId(t)
|
||||
if t.isAlive():
|
||||
foundThreads[tId] = tId
|
||||
|
||||
if not isinstance(t, PyDBDaemonThread):
|
||||
foundNonPyDBDaemonThread = True
|
||||
queue = self.getInternalQueue(GetThreadId(t))
|
||||
cmdsToReadd = [] #some commands must be processed by the thread itself... if that's the case,
|
||||
#we will re-add the commands to the queue after executing.
|
||||
try:
|
||||
while True:
|
||||
int_cmd = queue.get(False)
|
||||
if int_cmd.canBeExecutedBy(currThreadId):
|
||||
PydevdLog(2, "processing internal command ", str(int_cmd))
|
||||
int_cmd.doIt(self)
|
||||
else:
|
||||
PydevdLog(2, "NOT processing internal command ", str(int_cmd))
|
||||
cmdsToReadd.append(int_cmd)
|
||||
|
||||
except PydevQueue.Empty:
|
||||
for int_cmd in cmdsToReadd:
|
||||
queue.put(int_cmd)
|
||||
# this is how we exit
|
||||
|
||||
if not foundNonPyDBDaemonThread:
|
||||
self.finishDebuggingSession = True
|
||||
for t in threads:
|
||||
if hasattr(t, 'doKill'):
|
||||
t.doKill()
|
||||
|
||||
for tId in self.RUNNING_THREAD_IDS.keys():
|
||||
try:
|
||||
if not DictContains(foundThreads, tId):
|
||||
self.processThreadNotAlive(tId)
|
||||
except:
|
||||
sys.stderr.write('Error iterating through %s (%s) - %s\n' % (foundThreads, foundThreads.__class__, dir(foundThreads)))
|
||||
raise
|
||||
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
def processNetCommand(self, cmd_id, seq, text):
|
||||
'''Processes a command received from the Java side
|
||||
|
||||
@param cmd_id: the id of the command
|
||||
@param seq: the sequence of the command
|
||||
@param text: the text received in the command
|
||||
|
||||
@note: this method is run as a big switch... after doing some tests, it's not clear whether changing it for
|
||||
a dict id --> function call will have better performance result. A simple test with xrange(10000000) showed
|
||||
that the gains from having a fast access to what should be executed are lost because of the function call in
|
||||
a way that if we had 10 elements in the switch the if..elif are better -- but growing the number of choices
|
||||
makes the solution with the dispatch look better -- so, if this gets more than 20-25 choices at some time,
|
||||
it may be worth refactoring it (actually, reordering the ifs so that the ones used mostly come before
|
||||
probably will give better performance).
|
||||
'''
|
||||
|
||||
self.acquire()
|
||||
try:
|
||||
try:
|
||||
cmd = None
|
||||
if cmd_id == CMD_RUN:
|
||||
self.readyToRun = True
|
||||
|
||||
elif cmd_id == CMD_VERSION:
|
||||
# response is version number
|
||||
cmd = self.cmdFactory.makeVersionMessage(seq)
|
||||
|
||||
elif cmd_id == CMD_LIST_THREADS:
|
||||
# response is a list of threads
|
||||
cmd = self.cmdFactory.makeListThreadsMessage(seq)
|
||||
|
||||
elif cmd_id == CMD_THREAD_KILL:
|
||||
int_cmd = InternalTerminateThread(text)
|
||||
self.postInternalCommand(int_cmd, text)
|
||||
|
||||
elif cmd_id == CMD_THREAD_SUSPEND:
|
||||
t = PydevdFindThreadById(text)
|
||||
if t:
|
||||
additionalInfo = None
|
||||
try:
|
||||
additionalInfo = t.additionalInfo
|
||||
except AttributeError:
|
||||
pass #that's ok, no info currently set
|
||||
|
||||
if additionalInfo is not None:
|
||||
for frame in additionalInfo.IterFrames():
|
||||
frame.f_trace = self.trace_dispatch
|
||||
SetTraceForParents(frame, self.trace_dispatch)
|
||||
del frame
|
||||
|
||||
self.setSuspend(t, CMD_THREAD_SUSPEND)
|
||||
|
||||
elif cmd_id == CMD_THREAD_RUN:
|
||||
t = PydevdFindThreadById(text)
|
||||
if t:
|
||||
t.additionalInfo.pydev_step_cmd = None
|
||||
t.additionalInfo.pydev_step_stop = None
|
||||
t.additionalInfo.pydev_state = STATE_RUN
|
||||
|
||||
elif cmd_id == CMD_STEP_INTO or cmd_id == CMD_STEP_OVER or cmd_id == CMD_STEP_RETURN:
|
||||
#we received some command to make a single step
|
||||
t = PydevdFindThreadById(text)
|
||||
if t:
|
||||
t.additionalInfo.pydev_step_cmd = cmd_id
|
||||
t.additionalInfo.pydev_state = STATE_RUN
|
||||
|
||||
elif cmd_id == CMD_RUN_TO_LINE:
|
||||
#we received some command to make a single step
|
||||
thread_id, line, func_name = text.split('\t', 2)
|
||||
t = PydevdFindThreadById(thread_id)
|
||||
if t:
|
||||
t.additionalInfo.pydev_step_cmd = cmd_id
|
||||
t.additionalInfo.pydev_next_line = int(line)
|
||||
t.additionalInfo.pydev_func_name = func_name
|
||||
t.additionalInfo.pydev_state = STATE_RUN
|
||||
|
||||
|
||||
elif cmd_id == CMD_RELOAD_CODE:
|
||||
#we received some command to make a reload of a module
|
||||
module_name = text.strip()
|
||||
from pydevd_reload import xreload
|
||||
if not DictContains(sys.modules, module_name):
|
||||
if '.' in module_name:
|
||||
new_module_name = module_name.split('.')[-1]
|
||||
if DictContains(sys.modules, new_module_name):
|
||||
module_name = new_module_name
|
||||
|
||||
if not DictContains(sys.modules, module_name):
|
||||
sys.stderr.write('pydev debugger: Unable to find module to reload: "'+module_name+'".\n')
|
||||
sys.stderr.write('pydev debugger: This usually means you are trying to reload the __main__ module (which cannot be reloaded).\n')
|
||||
|
||||
else:
|
||||
sys.stderr.write('pydev debugger: Reloading: '+module_name+'\n')
|
||||
xreload(sys.modules[module_name])
|
||||
|
||||
|
||||
elif cmd_id == CMD_CHANGE_VARIABLE:
|
||||
#the text is: thread\tstackframe\tFRAME|GLOBAL\tattribute_to_change\tvalue_to_change
|
||||
try:
|
||||
thread_id, frame_id, scope, attr_and_value = text.split('\t', 3)
|
||||
|
||||
tab_index = attr_and_value.rindex('\t')
|
||||
attr = attr_and_value[0:tab_index].replace('\t', '.')
|
||||
value = attr_and_value[tab_index + 1:]
|
||||
int_cmd = InternalChangeVariable(seq, thread_id, frame_id, scope, attr, value)
|
||||
self.postInternalCommand(int_cmd, thread_id)
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
elif cmd_id == CMD_GET_VARIABLE:
|
||||
#we received some command to get a variable
|
||||
#the text is: thread_id\tframe_id\tFRAME|GLOBAL\tattributes*
|
||||
try:
|
||||
thread_id, frame_id, scopeattrs = text.split('\t', 2)
|
||||
|
||||
if scopeattrs.find('\t') != -1: # there are attibutes beyond scope
|
||||
scope, attrs = scopeattrs.split('\t', 1)
|
||||
else:
|
||||
scope, attrs = (scopeattrs, None)
|
||||
|
||||
int_cmd = InternalGetVariable(seq, thread_id, frame_id, scope, attrs)
|
||||
self.postInternalCommand(int_cmd, thread_id)
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
elif cmd_id == CMD_GET_FRAME:
|
||||
thread_id, frame_id, scope = text.split('\t', 2)
|
||||
|
||||
int_cmd = InternalGetFrame(seq, thread_id, frame_id)
|
||||
self.postInternalCommand(int_cmd, thread_id)
|
||||
|
||||
elif cmd_id == CMD_SET_BREAK:
|
||||
#func name: 'None': match anything. Empty: match global, specified: only method context.
|
||||
|
||||
#command to add some breakpoint.
|
||||
# text is file\tline. Add to breakpoints dictionary
|
||||
file, line, condition = text.split('\t', 2)
|
||||
if condition.startswith('**FUNC**'):
|
||||
func_name, condition = condition.split('\t', 1)
|
||||
|
||||
#We must restore new lines and tabs as done in
|
||||
#AbstractDebugTarget.breakpointAdded
|
||||
condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\
|
||||
replace("@_@TAB_CHAR@_@", '\t').strip()
|
||||
|
||||
func_name = func_name[8:]
|
||||
else:
|
||||
func_name = 'None' #Match anything if not specified.
|
||||
|
||||
|
||||
file = pydevd_file_utils.NormFileToServer(file)
|
||||
|
||||
if not os.path.exists(file):
|
||||
sys.stderr.write('pydev debugger: warning: trying to add breakpoint'\
|
||||
' to file that does not exist: %s (will have no effect)\n' % (file,))
|
||||
|
||||
line = int(line)
|
||||
|
||||
if DEBUG_TRACE_BREAKPOINTS > 0:
|
||||
sys.stderr.write('Added breakpoint:%s - line:%s - func_name:%s\n' % (file, line, func_name))
|
||||
|
||||
if DictContains(self.breakpoints, file):
|
||||
breakDict = self.breakpoints[file]
|
||||
else:
|
||||
breakDict = {}
|
||||
|
||||
if len(condition) <= 0 or condition == None or condition == "None":
|
||||
breakDict[line] = (True, None, func_name)
|
||||
else:
|
||||
breakDict[line] = (True, condition, func_name)
|
||||
|
||||
|
||||
self.breakpoints[file] = breakDict
|
||||
|
||||
#and enable the tracing for existing threads (because there may be frames being executed that
|
||||
#are currently untraced).
|
||||
threads = threading.enumerate()
|
||||
for t in threads:
|
||||
if not t.getName().startswith('pydevd.'):
|
||||
#TODO: optimize so that we only actually add that tracing if it's in
|
||||
#the new breakpoint context.
|
||||
additionalInfo = None
|
||||
try:
|
||||
additionalInfo = t.additionalInfo
|
||||
except AttributeError:
|
||||
pass #that's ok, no info currently set
|
||||
|
||||
if additionalInfo is not None:
|
||||
for frame in additionalInfo.IterFrames():
|
||||
frame.f_trace = self.trace_dispatch
|
||||
SetTraceForParents(frame, self.trace_dispatch)
|
||||
del frame
|
||||
|
||||
elif cmd_id == CMD_REMOVE_BREAK:
|
||||
#command to remove some breakpoint
|
||||
#text is file\tline. Remove from breakpoints dictionary
|
||||
file, line = text.split('\t', 1)
|
||||
file = pydevd_file_utils.NormFileToServer(file)
|
||||
try:
|
||||
line = int(line)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
else:
|
||||
try:
|
||||
del self.breakpoints[file][line] #remove the breakpoint in that line
|
||||
if DEBUG_TRACE_BREAKPOINTS > 0:
|
||||
sys.stderr.write('Removed breakpoint:%s\n' % (file,))
|
||||
except KeyError:
|
||||
#ok, it's not there...
|
||||
if DEBUG_TRACE_BREAKPOINTS > 0:
|
||||
#Sometimes, when adding a breakpoint, it adds a remove command before (don't really know why)
|
||||
sys.stderr.write("breakpoint not found: %s - %s\n" % (file, line))
|
||||
|
||||
elif cmd_id == CMD_EVALUATE_EXPRESSION or cmd_id == CMD_EXEC_EXPRESSION:
|
||||
#command to evaluate the given expression
|
||||
#text is: thread\tstackframe\tLOCAL\texpression
|
||||
thread_id, frame_id, scope, expression = text.split('\t', 3)
|
||||
int_cmd = InternalEvaluateExpression(seq, thread_id, frame_id, expression,
|
||||
cmd_id == CMD_EXEC_EXPRESSION)
|
||||
self.postInternalCommand(int_cmd, thread_id)
|
||||
|
||||
|
||||
else:
|
||||
#I have no idea what this is all about
|
||||
cmd = self.cmdFactory.makeErrorMessage(seq, "unexpected command " + str(cmd_id))
|
||||
|
||||
if cmd is not None:
|
||||
self.writer.addCommand(cmd)
|
||||
del cmd
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
cmd = self.cmdFactory.makeErrorMessage(seq,
|
||||
"Unexpected exception in processNetCommand.\nInitial params: %s" % ((cmd_id, seq, text),))
|
||||
|
||||
self.writer.addCommand(cmd)
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
def processThreadNotAlive(self, threadId):
|
||||
""" if thread is not alive, cancel trace_dispatch processing """
|
||||
thread = self.RUNNING_THREAD_IDS.get(threadId, None)
|
||||
if thread is None:
|
||||
return
|
||||
|
||||
del self.RUNNING_THREAD_IDS[threadId]
|
||||
wasNotified = thread.additionalInfo.pydev_notify_kill
|
||||
|
||||
if not wasNotified:
|
||||
cmd = self.cmdFactory.makeThreadKilledMessage(threadId)
|
||||
self.writer.addCommand(cmd)
|
||||
thread.additionalInfo.pydev_notify_kill = True
|
||||
|
||||
def setSuspend(self, thread, stop_reason):
|
||||
thread.additionalInfo.pydev_state = STATE_SUSPEND
|
||||
thread.stop_reason = stop_reason
|
||||
|
||||
def doWaitSuspend(self, thread, frame, event, arg): #@UnusedVariable
|
||||
""" busy waits until the thread state changes to RUN
|
||||
it expects thread's state as attributes of the thread.
|
||||
Upon running, processes any outstanding Stepping commands.
|
||||
"""
|
||||
self.processInternalCommands()
|
||||
cmd = self.cmdFactory.makeThreadSuspendMessage(GetThreadId(thread), frame, thread.stop_reason)
|
||||
self.writer.addCommand(cmd)
|
||||
|
||||
info = thread.additionalInfo
|
||||
while info.pydev_state == STATE_SUSPEND and not self.finishDebuggingSession:
|
||||
self.processInternalCommands()
|
||||
time.sleep(0.2)
|
||||
|
||||
#process any stepping instructions
|
||||
if info.pydev_step_cmd == CMD_STEP_INTO:
|
||||
info.pydev_step_stop = None
|
||||
|
||||
elif info.pydev_step_cmd == CMD_STEP_OVER:
|
||||
info.pydev_step_stop = frame
|
||||
if frame.f_trace is None:
|
||||
frame.f_trace = self.trace_dispatch
|
||||
SetTraceForParents(frame, self.trace_dispatch)
|
||||
|
||||
elif info.pydev_step_cmd == CMD_RUN_TO_LINE:
|
||||
if frame.f_trace is None:
|
||||
frame.f_trace = self.trace_dispatch
|
||||
SetTraceForParents(frame, self.trace_dispatch)
|
||||
|
||||
if event == 'line':
|
||||
#If we're already in the correct context, we have to stop it now, because we can act only on
|
||||
#line events -- if a return was the next statement it wouldn't work (so, we have this code
|
||||
#repeated at pydevd_frame).
|
||||
stop = False
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
#global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>'):
|
||||
curr_func_name = ''
|
||||
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
line = info.pydev_next_line
|
||||
if frame.f_lineno == line:
|
||||
stop = True
|
||||
else:
|
||||
if frame.f_trace is None:
|
||||
frame.f_trace = self.trace_dispatch
|
||||
frame.f_lineno = line
|
||||
frame.f_trace = None
|
||||
stop = True
|
||||
if stop:
|
||||
info.pydev_state = STATE_SUSPEND
|
||||
self.doWaitSuspend(thread, frame, event, arg)
|
||||
return
|
||||
|
||||
|
||||
elif info.pydev_step_cmd == CMD_STEP_RETURN:
|
||||
back_frame = frame.f_back
|
||||
if back_frame is not None:
|
||||
#steps back to the same frame (in a return call it will stop in the 'back frame' for the user)
|
||||
info.pydev_step_stop = frame
|
||||
if frame.f_trace is None:
|
||||
frame.f_trace = self.trace_dispatch
|
||||
SetTraceForParents(frame, self.trace_dispatch)
|
||||
else:
|
||||
#No back frame?!? -- this happens in jython when we have some frame created from an awt event
|
||||
#(the previous frame would be the awt event, but this doesn't make part of 'jython', only 'java')
|
||||
#so, if we're doing a step return in this situation, it's the same as just making it run
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_step_cmd = None
|
||||
info.pydev_state = STATE_RUN
|
||||
|
||||
del frame
|
||||
cmd = self.cmdFactory.makeThreadRunMessage(GetThreadId(thread), info.pydev_step_cmd)
|
||||
self.writer.addCommand(cmd)
|
||||
|
||||
|
||||
|
||||
def trace_dispatch(self, frame, event, arg):
|
||||
''' This is the callback used when we enter some context in the debugger.
|
||||
|
||||
We also decorate the thread we are in with info about the debugging.
|
||||
The attributes added are:
|
||||
pydev_state
|
||||
pydev_step_stop
|
||||
pydev_step_cmd
|
||||
pydev_notify_kill
|
||||
'''
|
||||
try:
|
||||
if self.finishDebuggingSession:
|
||||
#that was not working very well because jython gave some socket errors
|
||||
threads = threading.enumerate()
|
||||
for t in threads:
|
||||
if hasattr(t, 'doKill'):
|
||||
t.doKill()
|
||||
return None
|
||||
|
||||
filename, base = pydevd_file_utils.GetFilenameAndBase(frame)
|
||||
|
||||
if DictContains(DONT_TRACE, base): #we don't want to debug threading or anything related to pydevd
|
||||
return None
|
||||
|
||||
#print('trace_dispatch', base, frame.f_lineno, event, frame.f_code.co_name)
|
||||
try:
|
||||
#this shouldn't give an exception, but it could happen... (python bug)
|
||||
#see http://mail.python.org/pipermail/python-bugs-list/2007-June/038796.html
|
||||
#and related bug: http://bugs.python.org/issue1733757
|
||||
t = threading.currentThread()
|
||||
except:
|
||||
frame.f_trace = self.trace_dispatch
|
||||
return self.trace_dispatch
|
||||
|
||||
# if thread is not alive, cancel trace_dispatch processing
|
||||
if not t.isAlive():
|
||||
self.processThreadNotAlive(GetThreadId(t))
|
||||
return None # suspend tracing
|
||||
|
||||
try:
|
||||
additionalInfo = t.additionalInfo
|
||||
except AttributeError:
|
||||
t.additionalInfo = additionalInfo = pydevd_additional_thread_info.PyDBAdditionalThreadInfo()
|
||||
|
||||
#always keep a reference to the topmost frame so that we're able to start tracing it (if it was untraced)
|
||||
#that's needed when a breakpoint is added in a current frame for a currently untraced context.
|
||||
|
||||
#each new frame...
|
||||
dbFrame = additionalInfo.CreateDbFrame(self, filename, additionalInfo, t, frame)
|
||||
return dbFrame.trace_dispatch(frame, event, arg)
|
||||
|
||||
except SystemExit:
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
#Log it
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
if USE_PSYCO_OPTIMIZATION:
|
||||
try:
|
||||
import psyco
|
||||
trace_dispatch = psyco.proxy(trace_dispatch)
|
||||
processNetCommand = psyco.proxy(processNetCommand)
|
||||
processInternalCommands = psyco.proxy(processInternalCommands)
|
||||
doWaitSuspend = psyco.proxy(doWaitSuspend)
|
||||
getInternalQueue = psyco.proxy(getInternalQueue)
|
||||
except ImportError:
|
||||
if hasattr(sys, 'exc_clear'): #jython does not have it
|
||||
sys.exc_clear() #don't keep the traceback (let's keep it clear for when we go to the point of executing client code)
|
||||
|
||||
if not sys.platform.startswith("java") and not sys.platform.startswith("cli"):
|
||||
sys.stderr.write("pydev debugger: warning: psyco not available for speedups (the debugger will still work correctly, but a bit slower)\n")
|
||||
|
||||
|
||||
def run(self, file, globals=None, locals=None):
|
||||
|
||||
if globals is None:
|
||||
#patch provided by: Scott Schlesier - when script is run, it does not
|
||||
#use globals from pydevd:
|
||||
#This will prevent the pydevd script from contaminating the namespace for the script to be debugged
|
||||
|
||||
#pretend pydevd is not the main module, and
|
||||
#convince the file to be debugged that it was loaded as main
|
||||
sys.modules['pydevd'] = sys.modules['__main__']
|
||||
sys.modules['pydevd'].__name__ = 'pydevd'
|
||||
|
||||
from imp import new_module
|
||||
m = new_module('__main__')
|
||||
sys.modules['__main__'] = m
|
||||
m.__file__ = file
|
||||
globals = m.__dict__
|
||||
|
||||
if locals is None:
|
||||
locals = globals
|
||||
|
||||
#Predefined (writable) attributes: __name__ is the module's name;
|
||||
#__doc__ is the module's documentation string, or None if unavailable;
|
||||
#__file__ is the pathname of the file from which the module was loaded,
|
||||
#if it was loaded from a file. The __file__ attribute is not present for
|
||||
#C modules that are statically linked into the interpreter; for extension modules
|
||||
#loaded dynamically from a shared library, it is the pathname of the shared library file.
|
||||
|
||||
|
||||
#I think this is an ugly hack, bug it works (seems to) for the bug that says that sys.path should be the same in
|
||||
#debug and run.
|
||||
if m.__file__.startswith(sys.path[0]):
|
||||
#print >> sys.stderr, 'Deleting: ', sys.path[0]
|
||||
del sys.path[0]
|
||||
|
||||
#now, the local directory has to be added to the pythonpath
|
||||
#sys.path.insert(0, os.getcwd())
|
||||
#Changed: it's not the local directory, but the directory of the file launched
|
||||
#The file being run ust be in the pythonpath (even if it was not before)
|
||||
sys.path.insert(0, os.path.split(file)[0])
|
||||
|
||||
# for completness, we'll register the pydevd.reader & pydevd.writer threads
|
||||
net = NetCommand(str(CMD_THREAD_CREATE), 0, '<xml><thread name="pydevd.reader" id="-1"/></xml>')
|
||||
self.writer.addCommand(net)
|
||||
net = NetCommand(str(CMD_THREAD_CREATE), 0, '<xml><thread name="pydevd.writer" id="-1"/></xml>')
|
||||
self.writer.addCommand(net)
|
||||
|
||||
pydevd_tracing.SetTrace(self.trace_dispatch)
|
||||
try:
|
||||
#not available in jython!
|
||||
threading.settrace(self.trace_dispatch) # for all future threads
|
||||
except:
|
||||
pass
|
||||
|
||||
while not self.readyToRun:
|
||||
time.sleep(0.1) # busy wait until we receive run command
|
||||
|
||||
PyDBCommandThread(debugger).start()
|
||||
|
||||
if not IS_PY3K:
|
||||
execfile(file, globals, locals) #execute the script
|
||||
else:
|
||||
#We need to compile before so that the module name is correct
|
||||
obj = compile(open(file).read()+"\n", file, 'exec')
|
||||
exec(obj, globals, locals) #execute the script
|
||||
|
||||
|
||||
def processCommandLine(argv):
|
||||
""" parses the arguments.
|
||||
removes our arguments from the command line """
|
||||
retVal = {}
|
||||
retVal['client'] = ''
|
||||
retVal['server'] = False
|
||||
retVal['port'] = 0
|
||||
retVal['file'] = ''
|
||||
i = 0
|
||||
del argv[0]
|
||||
while (i < len(argv)):
|
||||
if (argv[i] == '--port'):
|
||||
del argv[i]
|
||||
retVal['port'] = int(argv[i])
|
||||
del argv[i]
|
||||
elif (argv[i] == '--vm_type'):
|
||||
del argv[i]
|
||||
retVal['vm_type'] = argv[i]
|
||||
del argv[i]
|
||||
elif (argv[i] == '--client'):
|
||||
del argv[i]
|
||||
retVal['client'] = argv[i]
|
||||
del argv[i]
|
||||
elif (argv[i] == '--server'):
|
||||
del argv[i]
|
||||
retVal['server'] = True
|
||||
elif (argv[i] == '--file'):
|
||||
del argv[i]
|
||||
retVal['file'] = argv[i];
|
||||
i = len(argv) # pop out, file is our last argument
|
||||
elif (argv[i] == '--DEBUG_RECORD_SOCKET_READS'):
|
||||
del argv[i]
|
||||
retVal['DEBUG_RECORD_SOCKET_READS'] = True
|
||||
else:
|
||||
raise ValueError("unexpected option " + argv[i])
|
||||
return retVal
|
||||
|
||||
def usage(doExit=0):
|
||||
sys.stdout.write('Usage:\n')
|
||||
sys.stdout.write('pydevd.py --port=N [(--client hostname) | --server] --file executable [file_options]\n')
|
||||
if doExit:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
def SetTraceForParents(frame, dispatch_func):
|
||||
frame = frame.f_back
|
||||
while frame:
|
||||
if frame.f_trace is None:
|
||||
frame.f_trace = dispatch_func
|
||||
|
||||
frame = frame.f_back
|
||||
del frame
|
||||
|
||||
def settrace(host='localhost', stdoutToServer=False, stderrToServer=False, port=5678, suspend=True):
|
||||
'''Sets the tracing function with the pydev debug function and initializes needed facilities.
|
||||
|
||||
@param host: the user may specify another host, if the debug server is not in the same machine
|
||||
@param stdoutToServer: when this is true, the stdout is passed to the debug server
|
||||
@param stderrToServer: when this is true, the stderr is passed to the debug server
|
||||
so that they are printed in its console and not in this process console.
|
||||
@param port: specifies which port to use for communicating with the server (note that the server must be started
|
||||
in the same port). @note: currently it's hard-coded at 5678 in the client
|
||||
@param suspend: whether a breakpoint should be emulated as soon as this function is called.
|
||||
'''
|
||||
|
||||
global connected
|
||||
global bufferStdOutToServer
|
||||
global bufferStdErrToServer
|
||||
|
||||
if not connected :
|
||||
connected = True
|
||||
bufferStdOutToServer = stdoutToServer
|
||||
bufferStdErrToServer = stderrToServer
|
||||
|
||||
pydevd_vm_type.SetupType()
|
||||
|
||||
debugger = PyDB()
|
||||
debugger.connect(host, port)
|
||||
|
||||
net = NetCommand(str(CMD_THREAD_CREATE), 0, '<xml><thread name="pydevd.reader" id="-1"/></xml>')
|
||||
debugger.writer.addCommand(net)
|
||||
net = NetCommand(str(CMD_THREAD_CREATE), 0, '<xml><thread name="pydevd.writer" id="-1"/></xml>')
|
||||
debugger.writer.addCommand(net)
|
||||
|
||||
if bufferStdOutToServer:
|
||||
sys.stdoutBuf = pydevd_io.IOBuf()
|
||||
sys.stdout = pydevd_io.IORedirector(sys.stdout, sys.stdoutBuf) #@UndefinedVariable
|
||||
|
||||
if bufferStdErrToServer:
|
||||
sys.stderrBuf = pydevd_io.IOBuf()
|
||||
sys.stderr = pydevd_io.IORedirector(sys.stderr, sys.stderrBuf) #@UndefinedVariable
|
||||
|
||||
SetTraceForParents(GetFrame(), debugger.trace_dispatch)
|
||||
|
||||
t = threading.currentThread()
|
||||
try:
|
||||
additionalInfo = t.additionalInfo
|
||||
except AttributeError:
|
||||
additionalInfo = pydevd_additional_thread_info.PyDBAdditionalThreadInfo()
|
||||
t.additionalInfo = additionalInfo
|
||||
|
||||
while not debugger.readyToRun:
|
||||
time.sleep(0.1) # busy wait until we receive run command
|
||||
|
||||
if suspend:
|
||||
debugger.setSuspend(t, CMD_SET_BREAK)
|
||||
|
||||
#that's right, debug only threads that pass through this function
|
||||
#(so, we just call sys.settrace and not threading.settrace)
|
||||
#note that we do that through pydevd_tracing.SetTrace so that the tracing
|
||||
#is not warned to the user!
|
||||
pydevd_tracing.SetTrace(debugger.trace_dispatch)
|
||||
PyDBCommandThread(debugger).start()
|
||||
|
||||
else:
|
||||
#ok, we're already in debug mode, with all set, so, let's just set the break
|
||||
debugger = GetGlobalDebugger()
|
||||
|
||||
SetTraceForParents(GetFrame(), debugger.trace_dispatch)
|
||||
|
||||
t = threading.currentThread()
|
||||
try:
|
||||
additionalInfo = t.additionalInfo
|
||||
except AttributeError:
|
||||
additionalInfo = pydevd_additional_thread_info.PyDBAdditionalThreadInfo()
|
||||
t.additionalInfo = additionalInfo
|
||||
|
||||
pydevd_tracing.SetTrace(debugger.trace_dispatch)
|
||||
if suspend:
|
||||
debugger.setSuspend(t, CMD_SET_BREAK)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stderr.write("pydev debugger: starting\n")
|
||||
# parse the command line. --file is our last argument that is required
|
||||
try:
|
||||
setup = processCommandLine(sys.argv)
|
||||
except ValueError:
|
||||
traceback.print_exc()
|
||||
usage(1)
|
||||
|
||||
#as to get here all our imports are already resolved, the psyco module can be
|
||||
#changed and we'll still get the speedups in the debugger, as those functions
|
||||
#are already compiled at this time.
|
||||
try:
|
||||
import psyco
|
||||
except ImportError:
|
||||
if hasattr(sys, 'exc_clear'): #jython does not have it
|
||||
sys.exc_clear() #don't keep the traceback -- clients don't want to see it
|
||||
pass #that's ok, no need to mock psyco if it's not available anyways
|
||||
else:
|
||||
#if it's available, let's change it for a stub (pydev already made use of it)
|
||||
import pydevd_psyco_stub
|
||||
sys.modules['psyco'] = pydevd_psyco_stub
|
||||
|
||||
|
||||
PydevdLog(2, "Executing file ", setup['file'])
|
||||
PydevdLog(2, "arguments:", str(sys.argv))
|
||||
|
||||
pydevd_vm_type.SetupType(setup.get('vm_type', None))
|
||||
|
||||
DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = setup.get('DEBUG_RECORD_SOCKET_READS', False)
|
||||
|
||||
debugger = PyDB()
|
||||
debugger.connect(setup['client'], setup['port'])
|
||||
debugger.run(setup['file'], None, None)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import sys
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
import threading
|
||||
from pydevd_frame import PyDBFrame
|
||||
import weakref
|
||||
|
||||
#=======================================================================================================================
|
||||
# AbstractPyDBAdditionalThreadInfo
|
||||
#=======================================================================================================================
|
||||
class AbstractPyDBAdditionalThreadInfo:
|
||||
def __init__(self):
|
||||
self.pydev_state = STATE_RUN
|
||||
self.pydev_step_stop = None
|
||||
self.pydev_step_cmd = None
|
||||
self.pydev_notify_kill = False
|
||||
|
||||
|
||||
def IterFrames(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def CreateDbFrame(self, mainDebugger, filename, base, additionalInfo, t, frame):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __str__(self):
|
||||
return 'State:%s Stop:%s Cmd: %s Kill:%s' % (self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# PyDBAdditionalThreadInfoWithCurrentFramesSupport
|
||||
#=======================================================================================================================
|
||||
class PyDBAdditionalThreadInfoWithCurrentFramesSupport(AbstractPyDBAdditionalThreadInfo):
|
||||
|
||||
def IterFrames(self):
|
||||
#sys._current_frames(): dictionary with thread id -> topmost frame
|
||||
return sys._current_frames().values() #return a copy... don't know if it's changed if we did get an iterator
|
||||
|
||||
#just create the db frame directly
|
||||
CreateDbFrame = PyDBFrame
|
||||
|
||||
#=======================================================================================================================
|
||||
# PyDBAdditionalThreadInfoWithoutCurrentFramesSupport
|
||||
#=======================================================================================================================
|
||||
class PyDBAdditionalThreadInfoWithoutCurrentFramesSupport(AbstractPyDBAdditionalThreadInfo):
|
||||
|
||||
def __init__(self):
|
||||
AbstractPyDBAdditionalThreadInfo.__init__(self)
|
||||
#That's where the last frame entered is kept. That's needed so that we're able to
|
||||
#trace contexts that were previously untraced and are currently active. So, the bad thing
|
||||
#is that the frame may be kept alive longer than it would if we go up on the frame stack,
|
||||
#and is only disposed when some other frame is removed.
|
||||
#A better way would be if we could get the topmost frame for each thread, but that's
|
||||
#not possible (until python 2.5 -- which is the PyDBAdditionalThreadInfoWithCurrentFramesSupport version)
|
||||
#Or if the user compiled threadframe (from http://www.majid.info/mylos/stories/2004/06/10/threadframe.html)
|
||||
|
||||
#NOT RLock!! (could deadlock if it was)
|
||||
self.lock = threading.Lock()
|
||||
self._acquire_lock = self.lock.acquire
|
||||
self._release_lock = self.lock.release
|
||||
|
||||
#collection with the refs
|
||||
d = {}
|
||||
self.pydev_existing_frames = d
|
||||
try:
|
||||
self._iter_frames = d.iterkeys
|
||||
except AttributeError:
|
||||
self._iter_frames = d.keys
|
||||
|
||||
|
||||
def _OnDbFrameCollected(self, ref):
|
||||
'''
|
||||
Callback to be called when a given reference is garbage-collected.
|
||||
'''
|
||||
self._acquire_lock()
|
||||
try:
|
||||
del self.pydev_existing_frames[ref]
|
||||
finally:
|
||||
self._release_lock()
|
||||
|
||||
|
||||
def _AddDbFrame(self, db_frame):
|
||||
self._acquire_lock()
|
||||
try:
|
||||
#create the db frame with a callback to remove it from the dict when it's garbage-collected
|
||||
#(could be a set, but that's not available on all versions we want to target).
|
||||
r = weakref.ref(db_frame, self._OnDbFrameCollected)
|
||||
self.pydev_existing_frames[r] = r
|
||||
finally:
|
||||
self._release_lock()
|
||||
|
||||
|
||||
def CreateDbFrame(self, mainDebugger, filename, additionalInfo, t, frame):
|
||||
#the frame must be cached as a weak-ref (we return the actual db frame -- which will be kept
|
||||
#alive until its trace_dispatch method is not referenced anymore).
|
||||
#that's a large workaround because:
|
||||
#1. we can't have weak-references to python frame object
|
||||
#2. only from 2.5 onwards we have _current_frames support from the interpreter
|
||||
db_frame = PyDBFrame(mainDebugger, filename, additionalInfo, t, frame)
|
||||
db_frame.frame = frame
|
||||
self._AddDbFrame(db_frame)
|
||||
return db_frame
|
||||
|
||||
|
||||
def IterFrames(self):
|
||||
#We cannot use yield (because of the lock)
|
||||
self._acquire_lock()
|
||||
try:
|
||||
ret = []
|
||||
|
||||
for weak_db_frame in self._iter_frames():
|
||||
try:
|
||||
ret.append(weak_db_frame().frame)
|
||||
except AttributeError:
|
||||
pass #ok, garbage-collected already
|
||||
return ret
|
||||
finally:
|
||||
self._release_lock()
|
||||
|
||||
def __str__(self):
|
||||
return 'State:%s Stop:%s Cmd: %s Kill:%s Frames:%s' % (self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill, len(self.IterFrames()))
|
||||
|
||||
#=======================================================================================================================
|
||||
# NOW, WE HAVE TO DEFINE WHICH THREAD INFO TO USE
|
||||
# (whether we have to keep references to the frames or not)
|
||||
# from version 2.5 onwards, we can use sys._current_frames to get a dict with the threads
|
||||
# and frames, but to support other versions, we can't rely on that.
|
||||
#=======================================================================================================================
|
||||
if hasattr(sys, '_current_frames'):
|
||||
PyDBAdditionalThreadInfo = PyDBAdditionalThreadInfoWithCurrentFramesSupport
|
||||
else:
|
||||
try:
|
||||
import threadframe
|
||||
sys._current_frames = threadframe.dict
|
||||
assert sys._current_frames is threadframe.dict #Just check if it was correctly set
|
||||
PyDBAdditionalThreadInfo = PyDBAdditionalThreadInfoWithCurrentFramesSupport
|
||||
except:
|
||||
#If all fails, let's use the support without frames
|
||||
PyDBAdditionalThreadInfo = PyDBAdditionalThreadInfoWithoutCurrentFramesSupport
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
''' pydevd - a debugging daemon
|
||||
This is the daemon you launch for python remote debugging.
|
||||
|
||||
Protocol:
|
||||
each command has a format:
|
||||
id\tsequence-num\ttext
|
||||
id: protocol command number
|
||||
sequence-num: each request has a sequence number. Sequence numbers
|
||||
originating at the debugger are odd, sequence numbers originating
|
||||
at the daemon are even. Every response uses the same sequence number
|
||||
as the request.
|
||||
payload: it is protocol dependent. When response is a complex structure, it
|
||||
is returned as XML. Each attribute value is urlencoded, and then the whole
|
||||
payload is urlencoded again to prevent stray characters corrupting protocol/xml encodings
|
||||
|
||||
Commands:
|
||||
|
||||
NUMBER NAME FROM* ARGUMENTS RESPONSE NOTE
|
||||
100 series: program execution
|
||||
101 RUN JAVA - -
|
||||
102 LIST_THREADS JAVA RETURN with XML listing of all threads
|
||||
103 THREAD_CREATE PYDB - XML with thread information
|
||||
104 THREAD_KILL JAVA id (or * to exit) kills the thread
|
||||
PYDB id nofies JAVA that thread was killed
|
||||
105 THREAD_SUSPEND JAVA XML of the stack, suspends the thread
|
||||
reason for suspension
|
||||
PYDB id notifies JAVA that thread was suspended
|
||||
|
||||
106 CMD_THREAD_RUN JAVA id resume the thread
|
||||
PYDB id \t reason notifies JAVA that thread was resumed
|
||||
|
||||
107 STEP_INTO JAVA thread_id
|
||||
108 STEP_OVER JAVA thread_id
|
||||
109 STEP_RETURN JAVA thread_id
|
||||
|
||||
110 GET_VARIABLE JAVA thread_id \t frame_id \t GET_VARIABLE with XML of var content
|
||||
FRAME|GLOBAL \t attributes*
|
||||
|
||||
111 SET_BREAK JAVA file/line of the breakpoint
|
||||
112 REMOVE_BREAK JAVA file/line of the return
|
||||
113 CMD_EVALUATE_EXPRESSION JAVA expression result of evaluating the expression
|
||||
114 CMD_GET_FRAME JAVA request for frame contents
|
||||
115 CMD_EXEC_EXPRESSION JAVA
|
||||
116 CMD_WRITE_TO_CONSOLE PYDB
|
||||
|
||||
500 series diagnostics/ok
|
||||
901 VERSION either Version string (1.0) Currently just used at startup
|
||||
902 RETURN either Depends on caller -
|
||||
|
||||
900 series: errors
|
||||
501 ERROR either - This is reserved for unexpected errors.
|
||||
|
||||
* JAVA - remote debugger, the java end
|
||||
* PYDB - pydevd, the python end
|
||||
'''
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
|
||||
import time
|
||||
import threading
|
||||
try:
|
||||
import Queue as PydevQueue
|
||||
except ImportError:
|
||||
import queue as PydevQueue
|
||||
from socket import socket
|
||||
from socket import AF_INET, SOCK_STREAM
|
||||
try:
|
||||
from urllib import quote
|
||||
except:
|
||||
from urllib.parse import quote
|
||||
import pydevd_vars
|
||||
import pydevd_tracing
|
||||
import pydevd_vm_type
|
||||
import pydevd_file_utils
|
||||
import traceback
|
||||
|
||||
from pydevd_tracing import GetExceptionTracebackStr
|
||||
|
||||
|
||||
CMD_RUN = 101
|
||||
CMD_LIST_THREADS = 102
|
||||
CMD_THREAD_CREATE = 103
|
||||
CMD_THREAD_KILL = 104
|
||||
CMD_THREAD_SUSPEND = 105
|
||||
CMD_THREAD_RUN = 106
|
||||
CMD_STEP_INTO = 107
|
||||
CMD_STEP_OVER = 108
|
||||
CMD_STEP_RETURN = 109
|
||||
CMD_GET_VARIABLE = 110
|
||||
CMD_SET_BREAK = 111
|
||||
CMD_REMOVE_BREAK = 112
|
||||
CMD_EVALUATE_EXPRESSION = 113
|
||||
CMD_GET_FRAME = 114
|
||||
CMD_EXEC_EXPRESSION = 115
|
||||
CMD_WRITE_TO_CONSOLE = 116
|
||||
CMD_CHANGE_VARIABLE = 117
|
||||
CMD_RUN_TO_LINE = 118
|
||||
CMD_RELOAD_CODE = 119
|
||||
CMD_VERSION = 501
|
||||
CMD_RETURN = 502
|
||||
CMD_ERROR = 901
|
||||
|
||||
ID_TO_MEANING = {
|
||||
'101':'CMD_RUN',
|
||||
'102':'CMD_LIST_THREADS',
|
||||
'103':'CMD_THREAD_CREATE',
|
||||
'104':'CMD_THREAD_KILL',
|
||||
'105':'CMD_THREAD_SUSPEND',
|
||||
'106':'CMD_THREAD_RUN',
|
||||
'107':'CMD_STEP_INTO',
|
||||
'108':'CMD_STEP_OVER',
|
||||
'109':'CMD_STEP_RETURN',
|
||||
'110':'CMD_GET_VARIABLE',
|
||||
'111':'CMD_SET_BREAK',
|
||||
'112':'CMD_REMOVE_BREAK',
|
||||
'113':'CMD_EVALUATE_EXPRESSION',
|
||||
'114':'CMD_GET_FRAME',
|
||||
'115':'CMD_EXEC_EXPRESSION',
|
||||
'116':'CMD_WRITE_TO_CONSOLE',
|
||||
'117':'CMD_CHANGE_VARIABLE',
|
||||
'118':'CMD_RUN_TO_LINE',
|
||||
'119':'CMD_RELOAD_CODE',
|
||||
'501':'CMD_VERSION',
|
||||
'502':'CMD_RETURN',
|
||||
'901':'CMD_ERROR',
|
||||
}
|
||||
|
||||
MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive)
|
||||
#this number can be changed if there's need to do so
|
||||
|
||||
VERSION_STRING = "1.1"
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------------------------------- UTILITIES
|
||||
|
||||
#=======================================================================================================================
|
||||
# PydevdLog
|
||||
#=======================================================================================================================
|
||||
def PydevdLog(level, *args):
|
||||
""" levels are:
|
||||
0 most serious warnings/errors
|
||||
1 warnings/significant events
|
||||
2 informational trace
|
||||
"""
|
||||
if level <= DEBUG_TRACE_LEVEL:
|
||||
#yes, we can have errors printing if the console of the program has been finished (and we're still trying to print something)
|
||||
try:
|
||||
sys.stderr.write('%s\n' % (args,))
|
||||
except:
|
||||
pass
|
||||
|
||||
#=======================================================================================================================
|
||||
# GlobalDebuggerHolder
|
||||
#=======================================================================================================================
|
||||
class GlobalDebuggerHolder:
|
||||
'''
|
||||
Holder for the global debugger.
|
||||
'''
|
||||
globalDbg = None
|
||||
|
||||
#=======================================================================================================================
|
||||
# GetGlobalDebugger
|
||||
#=======================================================================================================================
|
||||
def GetGlobalDebugger():
|
||||
return GlobalDebuggerHolder.globalDbg
|
||||
|
||||
#=======================================================================================================================
|
||||
# SetGlobalDebugger
|
||||
#=======================================================================================================================
|
||||
def SetGlobalDebugger(dbg):
|
||||
GlobalDebuggerHolder.globalDbg = dbg
|
||||
|
||||
|
||||
#------------------------------------------------------------------- ACTUAL COMM
|
||||
|
||||
#=======================================================================================================================
|
||||
# PyDBDaemonThread
|
||||
#=======================================================================================================================
|
||||
class PyDBDaemonThread(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
self.setDaemon(True)
|
||||
self.killReceived = False
|
||||
|
||||
def run(self):
|
||||
if sys.platform.startswith("java"):
|
||||
import org.python.core as PyCore #@UnresolvedImport
|
||||
ss = PyCore.PySystemState()
|
||||
# Note: Py.setSystemState() affects only the current thread.
|
||||
PyCore.Py.setSystemState(ss)
|
||||
|
||||
self.OnRun()
|
||||
|
||||
def OnRun(self):
|
||||
raise NotImplementedError('Should be reimplemented by: %s' % self.__class__)
|
||||
|
||||
def doKill(self):
|
||||
#that was not working very well because jython gave some socket errors
|
||||
self.killReceived = True
|
||||
|
||||
#=======================================================================================================================
|
||||
# ReaderThread
|
||||
#=======================================================================================================================
|
||||
class ReaderThread(PyDBDaemonThread):
|
||||
""" reader thread reads and dispatches commands in an infinite loop """
|
||||
|
||||
def __init__(self, sock):
|
||||
PyDBDaemonThread.__init__(self)
|
||||
self.sock = sock
|
||||
self.setName("pydevd.Reader")
|
||||
|
||||
|
||||
def doKill(self):
|
||||
#We must close the socket so that it doesn't stay halted there.
|
||||
self.killReceived = True
|
||||
try:
|
||||
self.sock.close()
|
||||
except:
|
||||
#just ignore that
|
||||
pass
|
||||
|
||||
|
||||
def OnRun(self):
|
||||
pydevd_tracing.SetTrace(None) # no debugging on this thread
|
||||
buffer = ""
|
||||
try:
|
||||
|
||||
while not self.killReceived:
|
||||
try:
|
||||
r = self.sock.recv(1024)
|
||||
except:
|
||||
GlobalDebuggerHolder.globalDbg.finishDebuggingSession = True
|
||||
break #Finished communication.
|
||||
if IS_PY3K:
|
||||
r = r.decode('utf-8')
|
||||
|
||||
buffer += r
|
||||
if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS:
|
||||
sys.stdout.write('received >>%s<<\n' % (buffer,))
|
||||
|
||||
if len(buffer) == 0:
|
||||
GlobalDebuggerHolder.globalDbg.finishDebuggingSession = True
|
||||
break
|
||||
while buffer.find('\n') != -1:
|
||||
command, buffer = buffer.split('\n', 1)
|
||||
PydevdLog(1, "received command ", command)
|
||||
args = command.split('\t', 2)
|
||||
GlobalDebuggerHolder.globalDbg.processNetCommand(int(args[0]), int(args[1]), args[2])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
GlobalDebuggerHolder.globalDbg.finishDebuggingSession = True
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------------- SOCKET UTILITIES - WRITER
|
||||
#=======================================================================================================================
|
||||
# WriterThread
|
||||
#=======================================================================================================================
|
||||
class WriterThread(PyDBDaemonThread):
|
||||
""" writer thread writes out the commands in an infinite loop """
|
||||
def __init__(self, sock):
|
||||
PyDBDaemonThread.__init__(self)
|
||||
self.sock = sock
|
||||
self.setName("pydevd.Writer")
|
||||
self.cmdQueue = PydevQueue.Queue()
|
||||
if pydevd_vm_type.GetVmType() == 'python':
|
||||
self.timeout = 0
|
||||
else:
|
||||
self.timeout = 0.1
|
||||
|
||||
def addCommand(self, cmd):
|
||||
""" cmd is NetCommand """
|
||||
self.cmdQueue.put(cmd)
|
||||
|
||||
def OnRun(self):
|
||||
""" just loop and write responses """
|
||||
|
||||
pydevd_tracing.SetTrace(None) # no debugging on this thread
|
||||
try:
|
||||
while not self.killReceived:
|
||||
try:
|
||||
cmd = self.cmdQueue.get(1)
|
||||
except:
|
||||
#PydevdLog(0, 'Finishing debug communication...(1)')
|
||||
#when liberating the thread here, we could have errors because we were shutting down
|
||||
#but the thread was still not liberated
|
||||
return
|
||||
out = cmd.getOutgoing()
|
||||
if DEBUG_TRACE_LEVEL >= 1:
|
||||
out_message = 'sending cmd: '
|
||||
out_message += ID_TO_MEANING.get(out[:3], 'UNKNOWN')
|
||||
out_message += ' '
|
||||
out_message += out
|
||||
try:
|
||||
sys.stderr.write('%s\n' % (out_message,))
|
||||
except:
|
||||
pass
|
||||
|
||||
if IS_PY3K:
|
||||
out = bytearray(out, 'utf-8')
|
||||
self.sock.send(out) #TODO: this does not guarantee that all message are sent (and jython does not have a send all)
|
||||
if time is None:
|
||||
break #interpreter shutdown
|
||||
time.sleep(self.timeout)
|
||||
except Exception:
|
||||
GlobalDebuggerHolder.globalDbg.finishDebuggingSession = True
|
||||
if DEBUG_TRACE_LEVEL >= 0:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
|
||||
|
||||
#--------------------------------------------------- CREATING THE SOCKET THREADS
|
||||
|
||||
#=======================================================================================================================
|
||||
# StartServer
|
||||
#=======================================================================================================================
|
||||
def StartServer(port):
|
||||
""" binds to a port, waits for the debugger to connect """
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.bind(('', port))
|
||||
s.listen(1)
|
||||
newSock, _addr = s.accept()
|
||||
return newSock
|
||||
|
||||
#=======================================================================================================================
|
||||
# StartClient
|
||||
#=======================================================================================================================
|
||||
def StartClient(host, port):
|
||||
""" connects to a host/port """
|
||||
PydevdLog(1, "Connecting to ", host, ":", str(port))
|
||||
try:
|
||||
s = socket(AF_INET, SOCK_STREAM);
|
||||
|
||||
s.connect((host, port))
|
||||
PydevdLog(1, "Connected.")
|
||||
return s
|
||||
except:
|
||||
sys.stderr.write("server timed out after 10 seconds, could not connect to %s: %s\n" % (host, port))
|
||||
sys.stderr.write("Exiting. Bye!\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------------ MANY COMMUNICATION STUFF
|
||||
|
||||
#=======================================================================================================================
|
||||
# NetCommand
|
||||
#=======================================================================================================================
|
||||
class NetCommand:
|
||||
""" Commands received/sent over the network.
|
||||
|
||||
Command can represent command received from the debugger,
|
||||
or one to be sent by daemon.
|
||||
"""
|
||||
next_seq = 0 # sequence numbers
|
||||
|
||||
def __init__(self, id, seq, text):
|
||||
""" smart handling of paramaters
|
||||
if sequence is 0, new sequence will be generated
|
||||
if text has carriage returns they'll be replaced"""
|
||||
self.id = id
|
||||
if (seq == 0): seq = self.getNextSeq()
|
||||
self.seq = seq
|
||||
self.text = text
|
||||
self.outgoing = self.makeMessage(id, seq, text)
|
||||
|
||||
def getNextSeq(self):
|
||||
""" returns next sequence number """
|
||||
NetCommand.next_seq += 2
|
||||
return NetCommand.next_seq
|
||||
|
||||
def getOutgoing(self):
|
||||
""" returns the outgoing message"""
|
||||
return self.outgoing
|
||||
|
||||
def makeMessage(self, cmd, seq, payload):
|
||||
encoded = quote(str(payload), '/<>_=" \t')
|
||||
return str(cmd) + '\t' + str(seq) + '\t' + encoded + "\n"
|
||||
|
||||
#=======================================================================================================================
|
||||
# NetCommandFactory
|
||||
#=======================================================================================================================
|
||||
class NetCommandFactory:
|
||||
|
||||
def __init_(self):
|
||||
self.next_seq = 0
|
||||
|
||||
def threadToXML(self, thread):
|
||||
""" thread information as XML """
|
||||
name = pydevd_vars.makeValidXmlValue(thread.getName())
|
||||
cmdText = '<thread name="%s" id="%s" />' % (quote(name), GetThreadId(thread))
|
||||
return cmdText
|
||||
|
||||
def makeErrorMessage(self, seq, text):
|
||||
cmd = NetCommand(CMD_ERROR, seq, text)
|
||||
if DEBUG_TRACE_LEVEL > 2:
|
||||
sys.stderr.write("Error: %s" % (text,))
|
||||
return cmd;
|
||||
|
||||
def makeThreadCreatedMessage(self, thread):
|
||||
cmdText = "<xml>" + self.threadToXML(thread) + "</xml>"
|
||||
return NetCommand(CMD_THREAD_CREATE, 0, cmdText)
|
||||
|
||||
def makeListThreadsMessage(self, seq):
|
||||
""" returns thread listing as XML """
|
||||
try:
|
||||
t = threading.enumerate()
|
||||
cmdText = "<xml>"
|
||||
for i in t:
|
||||
if t.isAlive():
|
||||
cmdText += self.threadToXML(i)
|
||||
cmdText += "</xml>"
|
||||
return NetCommand(CMD_RETURN, seq, cmdText)
|
||||
except:
|
||||
return self.makeErrorMessage(seq, GetExceptionTracebackStr())
|
||||
|
||||
def makeIoMessage(self, v, ctx, dbg=None):
|
||||
'''
|
||||
@param v: the message to pass to the debug server
|
||||
@param ctx: 1 for stdio 2 for stderr
|
||||
@param dbg: If not none, add to the writer
|
||||
'''
|
||||
|
||||
try:
|
||||
if len(v) > MAX_IO_MSG_SIZE:
|
||||
v = v[0:MAX_IO_MSG_SIZE]
|
||||
v += '...'
|
||||
|
||||
v = pydevd_vars.makeValidXmlValue(quote(v, '/>_= \t'))
|
||||
net = NetCommand(str(CMD_WRITE_TO_CONSOLE), 0, '<xml><io s="%s" ctx="%s"/></xml>' % (v, ctx))
|
||||
if dbg:
|
||||
dbg.writer.addCommand(net)
|
||||
except:
|
||||
return self.makeErrorMessage(0, GetExceptionTracebackStr())
|
||||
|
||||
def makeVersionMessage(self, seq):
|
||||
try:
|
||||
return NetCommand(CMD_VERSION, seq, VERSION_STRING)
|
||||
except:
|
||||
return self.makeErrorMessage(seq, GetExceptionTracebackStr())
|
||||
|
||||
def makeThreadKilledMessage(self, id):
|
||||
try:
|
||||
return NetCommand(CMD_THREAD_KILL, 0, str(id))
|
||||
except:
|
||||
return self.makeErrorMessage(0, GetExceptionTracebackStr())
|
||||
|
||||
def makeThreadSuspendMessage(self, thread_id, frame, stop_reason):
|
||||
|
||||
""" <xml>
|
||||
<thread id="id" stop_reason="reason">
|
||||
<frame id="id" name="functionName " file="file" line="line">
|
||||
<var variable stuffff....
|
||||
</frame>
|
||||
</thread>
|
||||
"""
|
||||
try:
|
||||
cmdTextList = ["<xml>"]
|
||||
cmdTextList.append('<thread id="%s" stop_reason="%s">' % (thread_id, stop_reason))
|
||||
|
||||
curFrame = frame
|
||||
while curFrame:
|
||||
#print cmdText
|
||||
myId = str(id(curFrame))
|
||||
#print "id is ", myId
|
||||
|
||||
if curFrame.f_code is None:
|
||||
break #Iron Python sometimes does not have it!
|
||||
|
||||
myName = curFrame.f_code.co_name #method name (if in method) or ? if global
|
||||
if myName is None:
|
||||
break #Iron Python sometimes does not have it!
|
||||
|
||||
#print "name is ", myName
|
||||
|
||||
myFile = pydevd_file_utils.NormFileToClient(curFrame.f_code.co_filename)
|
||||
#print "file is ", myFile
|
||||
#myFile = inspect.getsourcefile(curFrame) or inspect.getfile(frame)
|
||||
|
||||
myLine = str(curFrame.f_lineno)
|
||||
#print "line is ", myLine
|
||||
|
||||
#the variables are all gotten 'on-demand'
|
||||
#variables = pydevd_vars.frameVarsToXML(curFrame)
|
||||
|
||||
variables = ''
|
||||
cmdTextList.append('<frame id="%s" name="%s" ' % (myId , pydevd_vars.makeValidXmlValue(myName)))
|
||||
cmdTextList.append('file="%s" line="%s">"' % (quote(myFile, '/>_= \t'), myLine))
|
||||
cmdTextList.append(variables)
|
||||
cmdTextList.append("</frame>")
|
||||
curFrame = curFrame.f_back
|
||||
|
||||
cmdTextList.append("</thread></xml>")
|
||||
cmdText = ''.join(cmdTextList)
|
||||
return NetCommand(CMD_THREAD_SUSPEND, 0, cmdText)
|
||||
except:
|
||||
return self.makeErrorMessage(0, GetExceptionTracebackStr())
|
||||
|
||||
def makeThreadRunMessage(self, id, reason):
|
||||
try:
|
||||
return NetCommand(CMD_THREAD_RUN, 0, str(id) + "\t" + str(reason))
|
||||
except:
|
||||
return self.makeErrorMessage(0, GetExceptionTracebackStr())
|
||||
|
||||
def makeGetVariableMessage(self, seq, payload):
|
||||
try:
|
||||
return NetCommand(CMD_GET_VARIABLE, seq, payload)
|
||||
except Exception:
|
||||
return self.makeErrorMessage(seq, GetExceptionTracebackStr())
|
||||
|
||||
def makeGetFrameMessage(self, seq, payload):
|
||||
try:
|
||||
return NetCommand(CMD_GET_FRAME, seq, payload)
|
||||
except Exception:
|
||||
return self.makeErrorMessage(seq, GetExceptionTracebackStr())
|
||||
|
||||
|
||||
def makeEvaluateExpressionMessage(self, seq, payload):
|
||||
try:
|
||||
return NetCommand(CMD_EVALUATE_EXPRESSION, seq, payload)
|
||||
except Exception:
|
||||
return self.makeErrorMessage(seq, GetExceptionTracebackStr())
|
||||
|
||||
INTERNAL_TERMINATE_THREAD = 1
|
||||
INTERNAL_SUSPEND_THREAD = 2
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalThreadCommand
|
||||
#=======================================================================================================================
|
||||
class InternalThreadCommand:
|
||||
""" internal commands are generated/executed by the debugger.
|
||||
|
||||
The reason for their existence is that some commands have to be executed
|
||||
on specific threads. These are the InternalThreadCommands that get
|
||||
get posted to PyDB.cmdQueue.
|
||||
"""
|
||||
|
||||
def canBeExecutedBy(self, thread_id):
|
||||
'''By default, it must be in the same thread to be executed
|
||||
'''
|
||||
return self.thread_id == thread_id
|
||||
|
||||
def doIt(self, dbg):
|
||||
raise NotImplementedError("you have to override doIt")
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalTerminateThread
|
||||
#=======================================================================================================================
|
||||
class InternalTerminateThread(InternalThreadCommand):
|
||||
def __init__(self, thread_id):
|
||||
self.thread_id = thread_id
|
||||
|
||||
def doIt(self, dbg):
|
||||
PydevdLog(1, "killing ", str(self.thread_id))
|
||||
cmd = dbg.cmdFactory.makeThreadKilledMessage(self.thread_id)
|
||||
dbg.writer.addCommand(cmd)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalGetVariable
|
||||
#=======================================================================================================================
|
||||
class InternalGetVariable(InternalThreadCommand):
|
||||
""" gets the value of a variable """
|
||||
def __init__(self, seq, thread_id, frame_id, scope, attrs):
|
||||
self.sequence = seq
|
||||
self.thread_id = thread_id
|
||||
self.frame_id = frame_id
|
||||
self.scope = scope
|
||||
self.attributes = attrs
|
||||
|
||||
def doIt(self, dbg):
|
||||
""" Converts request into python variable """
|
||||
try:
|
||||
xml = "<xml>"
|
||||
valDict = pydevd_vars.resolveCompoundVariable(self.thread_id, self.frame_id, self.scope, self.attributes)
|
||||
keys = valDict.keys()
|
||||
if hasattr(keys, 'sort'):
|
||||
keys.sort() #Python 3.0 does not have it
|
||||
else:
|
||||
keys = sorted(keys, key=str) #Jython 2.1 does not have it (and all must be compared as strings).
|
||||
for k in keys:
|
||||
xml += pydevd_vars.varToXML(valDict[k], str(k))
|
||||
|
||||
xml += "</xml>"
|
||||
cmd = dbg.cmdFactory.makeGetVariableMessage(self.sequence, xml)
|
||||
dbg.writer.addCommand(cmd)
|
||||
except Exception:
|
||||
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error resolving variables " + GetExceptionTracebackStr())
|
||||
dbg.writer.addCommand(cmd)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalChangeVariable
|
||||
#=======================================================================================================================
|
||||
class InternalChangeVariable(InternalThreadCommand):
|
||||
""" changes the value of a variable """
|
||||
def __init__(self, seq, thread_id, frame_id, scope, attr, expression):
|
||||
self.sequence = seq
|
||||
self.thread_id = thread_id
|
||||
self.frame_id = frame_id
|
||||
self.scope = scope
|
||||
self.attr = attr
|
||||
self.expression = expression
|
||||
|
||||
def doIt(self, dbg):
|
||||
""" Converts request into python variable """
|
||||
try:
|
||||
pydevd_vars.changeAttrExpression(self.thread_id, self.frame_id, self.attr, self.expression)
|
||||
except Exception:
|
||||
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error changing variable attr:%s expression:%s traceback:%s" % (self.attr, self.expression, GetExceptionTracebackStr()))
|
||||
dbg.writer.addCommand(cmd)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalGetFrame
|
||||
#=======================================================================================================================
|
||||
class InternalGetFrame(InternalThreadCommand):
|
||||
""" gets the value of a variable """
|
||||
def __init__(self, seq, thread_id, frame_id):
|
||||
self.sequence = seq
|
||||
self.thread_id = thread_id
|
||||
self.frame_id = frame_id
|
||||
|
||||
def doIt(self, dbg):
|
||||
""" Converts request into python variable """
|
||||
try:
|
||||
try:
|
||||
xml = "<xml>"
|
||||
frame = pydevd_vars.findFrame(self.thread_id, self.frame_id)
|
||||
xml += pydevd_vars.frameVarsToXML(frame)
|
||||
del frame
|
||||
xml += "</xml>"
|
||||
cmd = dbg.cmdFactory.makeGetFrameMessage(self.sequence, xml)
|
||||
dbg.writer.addCommand(cmd)
|
||||
except pydevd_vars.FrameNotFoundError:
|
||||
#pydevd_vars.dumpFrames(self.thread_id)
|
||||
#don't print this error: frame not found: means that the client is not synchronized (but that's ok)
|
||||
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Frame not found: %s from thread: %s" % (self.frame_id, self.thread_id))
|
||||
dbg.writer.addCommand(cmd)
|
||||
except:
|
||||
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error resolving frame: %s from thread: %s" % (self.frame_id, self.thread_id))
|
||||
dbg.writer.addCommand(cmd)
|
||||
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalEvaluateExpression
|
||||
#=======================================================================================================================
|
||||
class InternalEvaluateExpression(InternalThreadCommand):
|
||||
""" gets the value of a variable """
|
||||
|
||||
def __init__(self, seq, thread_id, frame_id, expression, doExec):
|
||||
self.sequence = seq
|
||||
self.thread_id = thread_id
|
||||
self.frame_id = frame_id
|
||||
self.expression = expression
|
||||
self.doExec = doExec
|
||||
|
||||
def doIt(self, dbg):
|
||||
""" Converts request into python variable """
|
||||
try:
|
||||
result = pydevd_vars.evaluateExpression(self.thread_id, self.frame_id, self.expression, self.doExec)
|
||||
xml = "<xml>"
|
||||
xml += pydevd_vars.varToXML(result, "")
|
||||
xml += "</xml>"
|
||||
cmd = dbg.cmdFactory.makeEvaluateExpressionMessage(self.sequence, xml)
|
||||
dbg.writer.addCommand(cmd)
|
||||
except:
|
||||
exc = GetExceptionTracebackStr()
|
||||
sys.stderr.write('%s\n' % (exc,))
|
||||
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error evaluating expression " + exc)
|
||||
dbg.writer.addCommand(cmd)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# PydevdFindThreadById
|
||||
#=======================================================================================================================
|
||||
def PydevdFindThreadById(thread_id):
|
||||
try:
|
||||
# there was a deadlock here when I did not remove the tracing function when thread was dead
|
||||
threads = threading.enumerate()
|
||||
for i in threads:
|
||||
if thread_id == GetThreadId(i):
|
||||
return i
|
||||
|
||||
sys.stderr.write("Could not find thread %s\n" % thread_id)
|
||||
sys.stderr.write("Available: %s\n" % [GetThreadId(t) for t in threads])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
'''
|
||||
This module holds the constants used for specifying the states of the debugger.
|
||||
'''
|
||||
|
||||
STATE_RUN = 1
|
||||
STATE_SUSPEND = 2
|
||||
|
||||
try:
|
||||
__setFalse = False
|
||||
except:
|
||||
import __builtin__
|
||||
setattr(__builtin__, 'True', 1)
|
||||
setattr(__builtin__, 'False', 0)
|
||||
|
||||
DEBUG_TRACE_LEVEL = -1
|
||||
DEBUG_TRACE_BREAKPOINTS = -1
|
||||
|
||||
class DebugInfoHolder:
|
||||
#we have to put it here because it can be set through the command line (so, the
|
||||
#already imported references would not have it).
|
||||
DEBUG_RECORD_SOCKET_READS = False
|
||||
|
||||
#Optimize with psyco? This gave a 50% speedup in the debugger in tests
|
||||
USE_PSYCO_OPTIMIZATION = True
|
||||
|
||||
#Hold a reference to the original _getframe (because psyco will change that as soon as it's imported)
|
||||
import sys #Note: the sys import must be here anyways (others depend on it)
|
||||
GetFrame = sys._getframe
|
||||
|
||||
#Used to determine the maximum size of each variable passed to eclipse -- having a big value here may make
|
||||
#the communication slower -- as the variables are being gathered lazily in the latest version of eclipse,
|
||||
#this value was raised from 200 to 1000.
|
||||
MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 1000
|
||||
|
||||
import threading
|
||||
import os
|
||||
|
||||
_nextThreadIdLock = threading.Lock()
|
||||
|
||||
#=======================================================================================================================
|
||||
# Python 3?
|
||||
#=======================================================================================================================
|
||||
IS_PY3K = False
|
||||
try:
|
||||
if sys.version_info[0] >= 3:
|
||||
IS_PY3K = True
|
||||
except AttributeError:
|
||||
pass #Not all versions have sys.version_info
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# Jython?
|
||||
#=======================================================================================================================
|
||||
try:
|
||||
import org.python.core.PyDictionary #@UnresolvedImport @UnusedImport -- just to check if it could be valid
|
||||
def DictContains(d, key):
|
||||
return d.has_key(key)
|
||||
except:
|
||||
try:
|
||||
#Py3k does not have has_key anymore, and older versions don't have __contains__
|
||||
DictContains = dict.__contains__
|
||||
except:
|
||||
DictContains = dict.has_key
|
||||
|
||||
#=======================================================================================================================
|
||||
# NextId
|
||||
#=======================================================================================================================
|
||||
class NextId:
|
||||
|
||||
def __init__(self):
|
||||
self._id = 0
|
||||
|
||||
def __call__(self):
|
||||
#No need to synchronize here
|
||||
self._id += 1
|
||||
return self._id
|
||||
|
||||
_nextThreadId = NextId()
|
||||
|
||||
#=======================================================================================================================
|
||||
# GetThreadId
|
||||
#=======================================================================================================================
|
||||
def GetThreadId(thread):
|
||||
try:
|
||||
return thread.__pydevd_id__
|
||||
except AttributeError:
|
||||
_nextThreadIdLock.acquire()
|
||||
try:
|
||||
#We do a new check with the lock in place just to be sure that nothing changed
|
||||
if not hasattr(thread, '__pydevd_id__'):
|
||||
try:
|
||||
pid = os.getpid()
|
||||
except AttributeError:
|
||||
try:
|
||||
#Jython does not have it!
|
||||
import java.lang.management.ManagementFactory #@UnresolvedImport -- just for jython
|
||||
pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
|
||||
pid = pid.replace('@', '_')
|
||||
except:
|
||||
#ok, no pid available (will be unable to debug multiple processes)
|
||||
pid = '000001'
|
||||
|
||||
thread.__pydevd_id__ = 'pid%s_seq%s' % (pid, _nextThreadId())
|
||||
finally:
|
||||
_nextThreadIdLock.release()
|
||||
|
||||
return thread.__pydevd_id__
|
||||
|
||||
#===============================================================================
|
||||
# Null
|
||||
#===============================================================================
|
||||
class Null:
|
||||
"""
|
||||
Gotten from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def __getattr__(self, mname):
|
||||
return self
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
return self
|
||||
|
||||
def __delattr__(self, name):
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "<Null>"
|
||||
|
||||
def __str__(self):
|
||||
return "Null"
|
||||
|
||||
def __len__(self):
|
||||
return 0
|
||||
|
||||
def __getitem__(self):
|
||||
return self
|
||||
|
||||
def __setitem__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def write(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __nonzero__(self):
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if Null():
|
||||
sys.stdout.write('here\n')
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
'''
|
||||
This module provides utilities to get the absolute filenames so that we can be sure that:
|
||||
- The case of a file will match the actual file in the filesystem (otherwise breakpoints won't be hit).
|
||||
- Providing means for the user to make path conversions when doing a remote debugging session in
|
||||
one machine and debugging in another.
|
||||
|
||||
To do that, the PATHS_FROM_CLIENT_TO_SERVER constant must be filled with the appropriate paths.
|
||||
|
||||
E.g.:
|
||||
If the server has the structure
|
||||
/user/projects/my_project/src/package/module1.py
|
||||
|
||||
and the client has:
|
||||
c:\my_project\src\package\module1.py
|
||||
|
||||
the PATHS_FROM_CLIENT_TO_SERVER would have to be:
|
||||
PATHS_FROM_CLIENT_TO_SERVER = [(r'c:\my_project\src', r'/user/projects/my_project/src')]
|
||||
|
||||
@note: DEBUG_CLIENT_SERVER_TRANSLATION can be set to True to debug the result of those translations
|
||||
|
||||
@note: the case of the paths is important! Note that this can be tricky to get right when one machine
|
||||
uses a case-independent filesystem and the other uses a case-dependent filesystem (if the system being
|
||||
debugged is case-independent, 'normcase()' should be used on the paths defined in PATHS_FROM_CLIENT_TO_SERVER).
|
||||
|
||||
@note: all the paths with breakpoints must be translated (otherwise they won't be found in the server)
|
||||
|
||||
@note: to enable remote debugging in the target machine (pydev extensions in the eclipse installation)
|
||||
import pydevd;pydevd.settrace(host, stdoutToServer, stderrToServer, port, suspend)
|
||||
|
||||
see parameter docs on pydevd.py
|
||||
|
||||
@note: for doing a remote debugging session, all the pydevd_ files must be on the server accessible
|
||||
through the PYTHONPATH (and the PATHS_FROM_CLIENT_TO_SERVER only needs to be set on the target
|
||||
machine for the paths that'll actually have breakpoints).
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
import os.path
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
normcase = os.path.normcase
|
||||
basename = os.path.basename
|
||||
exists = os.path.exists
|
||||
join = os.path.join
|
||||
|
||||
try:
|
||||
rPath = os.path.realpath #@UndefinedVariable
|
||||
except:
|
||||
# jython does not support os.path.realpath
|
||||
# realpath is a no-op on systems without islink support
|
||||
rPath = os.path.abspath
|
||||
|
||||
#defined as a list of tuples where the 1st element of the tuple is the path in the client machine
|
||||
#and the 2nd element is the path in the server machine.
|
||||
#see module docstring for more details.
|
||||
PATHS_FROM_CLIENT_TO_SERVER = []
|
||||
|
||||
|
||||
#example:
|
||||
#PATHS_FROM_CLIENT_TO_SERVER = [
|
||||
#(normcase(r'd:\temp\temp_workspace_2\test_python\src\yyy\yyy'),
|
||||
# normcase(r'd:\temp\temp_workspace_2\test_python\src\hhh\xxx'))]
|
||||
|
||||
DEBUG_CLIENT_SERVER_TRANSLATION = False
|
||||
|
||||
#caches filled as requested during the debug session
|
||||
NORM_FILENAME_CONTAINER = {}
|
||||
NORM_FILENAME_AND_BASE_CONTAINER = {}
|
||||
NORM_FILENAME_TO_SERVER_CONTAINER = {}
|
||||
NORM_FILENAME_TO_CLIENT_CONTAINER = {}
|
||||
|
||||
|
||||
def _NormFile(filename):
|
||||
try:
|
||||
return NORM_FILENAME_CONTAINER[filename]
|
||||
except KeyError:
|
||||
r = normcase(rPath(filename))
|
||||
#cache it for fast access later
|
||||
NORM_FILENAME_CONTAINER[filename] = r
|
||||
return r
|
||||
|
||||
#Now, let's do a quick test to see if we're working with a version of python that has no problems
|
||||
#related to the names generated...
|
||||
try:
|
||||
try:
|
||||
code = rPath.func_code
|
||||
except AttributeError:
|
||||
code = rPath.__code__
|
||||
if not exists(_NormFile(code.co_filename)):
|
||||
sys.stderr.write('-------------------------------------------------------------------------------\n')
|
||||
sys.stderr.write('pydev debugger: CRITICAL WARNING: This version of python seems to be incorrectly compiled (internal generated filenames are not absolute)\n')
|
||||
sys.stderr.write('pydev debugger: The debugger may still function, but it will work slower and may miss breakpoints.\n')
|
||||
sys.stderr.write('pydev debugger: Related bug: http://bugs.python.org/issue1666807\n')
|
||||
sys.stderr.write('-------------------------------------------------------------------------------\n')
|
||||
|
||||
initial_norm_file = _NormFile
|
||||
def _NormFile(filename): #Let's redefine _NormFile to work with paths that may be incorrect
|
||||
ret = initial_norm_file(filename)
|
||||
if not exists(ret):
|
||||
#We must actually go on and check if we can find it as if it was a relative path for some of the paths in the pythonpath
|
||||
for path in sys.path:
|
||||
ret = initial_norm_file(join(path, filename))
|
||||
if exists(ret):
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('pydev debugger: Unable to find real location for: %s\n' % (filename,))
|
||||
ret = filename
|
||||
|
||||
return ret
|
||||
except:
|
||||
#Don't fail if there's something not correct here -- but at least print it to the user so that we can correct that
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if PATHS_FROM_CLIENT_TO_SERVER:
|
||||
#Work on the client and server slashes.
|
||||
client_sep = None
|
||||
server_sep = None
|
||||
for client_prefix, server_prefix in PATHS_FROM_CLIENT_TO_SERVER:
|
||||
if client_sep is not None and server_sep is not None:
|
||||
break
|
||||
|
||||
if client_sep is None:
|
||||
for c in client_prefix:
|
||||
if c in ('/', '\\'):
|
||||
client_sep = c
|
||||
break
|
||||
|
||||
if server_sep is None:
|
||||
for c in server_prefix:
|
||||
if c in ('/', '\\'):
|
||||
server_sep = c
|
||||
break
|
||||
|
||||
#If they're the same or one of them cannot be determined, just make it all None.
|
||||
if client_sep == server_sep or client_sep is None or server_sep is None:
|
||||
client_sep = server_sep = None
|
||||
|
||||
|
||||
#only setup translation functions if absolutely needed!
|
||||
def NormFileToServer(filename):
|
||||
try:
|
||||
return NORM_FILENAME_TO_SERVER_CONTAINER[filename]
|
||||
except KeyError:
|
||||
#used to translate a path from the client to the debug server
|
||||
translated = normcase(filename)
|
||||
for client_prefix, server_prefix in PATHS_FROM_CLIENT_TO_SERVER:
|
||||
if translated.startswith(client_prefix):
|
||||
if DEBUG_CLIENT_SERVER_TRANSLATION:
|
||||
sys.stderr.write('pydev debugger: replacing to server: %s\n' % (translated,))
|
||||
translated = translated.replace(client_prefix, server_prefix)
|
||||
if DEBUG_CLIENT_SERVER_TRANSLATION:
|
||||
sys.stderr.write('pydev debugger: sent to server: %s\n' % (translated,))
|
||||
break
|
||||
else:
|
||||
if DEBUG_CLIENT_SERVER_TRANSLATION:
|
||||
sys.stderr.write('pydev debugger: to server: unable to find matching prefix for: %s in %s\n' % \
|
||||
(translated, [x[0] for x in PATHS_FROM_CLIENT_TO_SERVER]))
|
||||
|
||||
#Note that when going to the server, we do the replace first and only later do the norm file.
|
||||
if client_sep is not None:
|
||||
translated = translated.replace(server_sep, client_sep)
|
||||
ret = _NormFile(translated)
|
||||
|
||||
NORM_FILENAME_TO_SERVER_CONTAINER[filename] = translated
|
||||
return ret
|
||||
|
||||
|
||||
def NormFileToClient(filename):
|
||||
try:
|
||||
return NORM_FILENAME_TO_CLIENT_CONTAINER[filename]
|
||||
except KeyError:
|
||||
#used to translate a path from the debug server to the client
|
||||
translated = normcase(filename)
|
||||
for client_prefix, server_prefix in PATHS_FROM_CLIENT_TO_SERVER:
|
||||
if translated.startswith(server_prefix):
|
||||
if DEBUG_CLIENT_SERVER_TRANSLATION:
|
||||
sys.stderr.write('pydev debugger: replacing to client: %s\n' % (translated,))
|
||||
translated = translated.replace(server_prefix, client_prefix)
|
||||
if DEBUG_CLIENT_SERVER_TRANSLATION:
|
||||
sys.stderr.write('pydev debugger: sent to client: %s\n' % (translated,))
|
||||
break
|
||||
else:
|
||||
if DEBUG_CLIENT_SERVER_TRANSLATION:
|
||||
sys.stderr.write('pydev debugger: to client: unable to find matching prefix for: %s in %s\n' % \
|
||||
(translated, [x[1] for x in PATHS_FROM_CLIENT_TO_SERVER]))
|
||||
|
||||
#When going to the client, first we do the norm file and only later the replace for slashes.
|
||||
ret = _NormFile(translated)
|
||||
if client_sep is not None:
|
||||
ret = ret.replace(client_sep, server_sep)
|
||||
|
||||
NORM_FILENAME_TO_CLIENT_CONTAINER[filename] = ret
|
||||
return ret
|
||||
|
||||
else:
|
||||
#no translation step needed (just inline the calls)
|
||||
NormFileToClient = _NormFile
|
||||
NormFileToServer = _NormFile
|
||||
|
||||
|
||||
def GetFilenameAndBase(frame):
|
||||
#This one is just internal (so, does not need any kind of client-server translation)
|
||||
f = frame.f_code.co_filename
|
||||
try:
|
||||
return NORM_FILENAME_AND_BASE_CONTAINER[f]
|
||||
except KeyError:
|
||||
filename = _NormFile(f)
|
||||
base = basename(filename)
|
||||
NORM_FILENAME_AND_BASE_CONTAINER[f] = filename, base
|
||||
return filename, base
|
||||
@@ -0,0 +1,194 @@
|
||||
from pydevd_comm import * #@UnusedWildImport
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
import traceback #@Reimport
|
||||
import os.path
|
||||
basename = os.path.basename
|
||||
|
||||
#=======================================================================================================================
|
||||
# PyDBFrame
|
||||
#=======================================================================================================================
|
||||
class PyDBFrame:
|
||||
'''This makes the tracing for a given frame, so, the trace_dispatch
|
||||
is used initially when we enter into a new context ('call') and then
|
||||
is reused for the entire context.
|
||||
'''
|
||||
|
||||
def __init__(self, *args):
|
||||
#args = mainDebugger, filename, base, info, t, frame
|
||||
#yeap, much faster than putting in self and the getting it from self later on
|
||||
self._args = args[:-1]
|
||||
|
||||
def setSuspend(self, *args, **kwargs):
|
||||
self._args[0].setSuspend(*args, **kwargs)
|
||||
|
||||
def doWaitSuspend(self, *args, **kwargs):
|
||||
self._args[0].doWaitSuspend(*args, **kwargs)
|
||||
|
||||
def trace_dispatch(self, frame, event, arg):
|
||||
if event not in ('line', 'call', 'return', 'exception'):
|
||||
return None
|
||||
|
||||
mainDebugger, filename, info, thread = self._args
|
||||
|
||||
breakpoint = mainDebugger.breakpoints.get(filename)
|
||||
|
||||
|
||||
if info.pydev_state == STATE_RUN:
|
||||
#we can skip if:
|
||||
#- we have no stop marked
|
||||
#- we should make a step return/step over and we're not in the current frame
|
||||
can_skip = (info.pydev_step_cmd is None and info.pydev_step_stop is None)\
|
||||
or (info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER) and info.pydev_step_stop is not frame)
|
||||
else:
|
||||
can_skip = False
|
||||
|
||||
# Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint,
|
||||
# we will return nothing for the next trace
|
||||
#also, after we hit a breakpoint and go to some other debugging state, we have to force the set trace anyway,
|
||||
#so, that's why the additional checks are there.
|
||||
if not breakpoint:
|
||||
if can_skip:
|
||||
return None
|
||||
|
||||
else:
|
||||
#checks the breakpoint to see if there is a context match in some function
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
#global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>'):
|
||||
curr_func_name = ''
|
||||
|
||||
for _b, condition, func_name in breakpoint.values(): #jython does not support itervalues()
|
||||
#will match either global or some function
|
||||
if func_name in ('None', curr_func_name):
|
||||
break
|
||||
|
||||
else: # if we had some break, it won't get here (so, that's a context that we want to skip)
|
||||
if can_skip:
|
||||
#print 'skipping', frame.f_lineno, info.pydev_state, info.pydev_step_stop, info.pydev_step_cmd
|
||||
return None
|
||||
|
||||
#We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame
|
||||
#print 'NOT skipped', frame.f_lineno, frame.f_code.co_name
|
||||
|
||||
|
||||
try:
|
||||
line = frame.f_lineno
|
||||
|
||||
#return is not taken into account for breakpoint hit because we'd have a double-hit in this case
|
||||
#(one for the line and the other for the return).
|
||||
if event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoint is not None \
|
||||
and DictContains(breakpoint, line):
|
||||
|
||||
#ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
|
||||
# lets do the conditional stuff here
|
||||
condition = breakpoint[line][1]
|
||||
|
||||
if condition is not None:
|
||||
try:
|
||||
val = eval(condition, frame.f_globals, frame.f_locals)
|
||||
if not val:
|
||||
return self.trace_dispatch
|
||||
|
||||
except:
|
||||
sys.stderr.write('Error while evaluating expression\n')
|
||||
traceback.print_exc()
|
||||
return self.trace_dispatch
|
||||
|
||||
self.setSuspend(thread, CMD_SET_BREAK)
|
||||
|
||||
# if thread has a suspend flag, we suspend with a busy wait
|
||||
if info.pydev_state == STATE_SUSPEND:
|
||||
self.doWaitSuspend(thread, frame, event, arg)
|
||||
return self.trace_dispatch
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
#step handling. We stop when we hit the right frame
|
||||
try:
|
||||
|
||||
if info.pydev_step_cmd == CMD_STEP_INTO:
|
||||
|
||||
stop = event in ('line', 'return')
|
||||
|
||||
elif info.pydev_step_cmd == CMD_STEP_OVER:
|
||||
|
||||
stop = info.pydev_step_stop is frame and event in ('line', 'return')
|
||||
|
||||
elif info.pydev_step_cmd == CMD_STEP_RETURN:
|
||||
|
||||
stop = event == 'return' and info.pydev_step_stop is frame
|
||||
|
||||
elif info.pydev_step_cmd == CMD_RUN_TO_LINE:
|
||||
stop = False
|
||||
if event == 'line':
|
||||
#Yes, we can only act on line events (weird hum?)
|
||||
#Note: This code is duplicated at pydevd.py
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
#global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>'):
|
||||
curr_func_name = ''
|
||||
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
line = info.pydev_next_line
|
||||
if frame.f_lineno == line:
|
||||
stop = True
|
||||
else:
|
||||
if frame.f_trace is None:
|
||||
frame.f_trace = self.trace_dispatch
|
||||
frame.f_lineno = line
|
||||
frame.f_trace = None
|
||||
stop = True
|
||||
|
||||
else:
|
||||
stop = False
|
||||
|
||||
if stop:
|
||||
#event is always == line or return at this point
|
||||
if event == 'line':
|
||||
self.setSuspend(thread, info.pydev_step_cmd)
|
||||
self.doWaitSuspend(thread, frame, event, arg)
|
||||
else: #return event
|
||||
back = frame.f_back
|
||||
if back is not None:
|
||||
|
||||
#When we get to the pydevd run function, the debugging has actually finished for the main thread
|
||||
#(note that it can still go on for other threads, but for this one, we just make it finish)
|
||||
#So, just setting it to None should be OK
|
||||
if basename(back.f_code.co_filename) == 'pydevd.py' and back.f_code.co_name == 'run':
|
||||
back = None
|
||||
|
||||
|
||||
if back is not None:
|
||||
#if we're in a return, we want it to appear to the user in the previous frame!
|
||||
self.setSuspend(thread, info.pydev_step_cmd)
|
||||
self.doWaitSuspend(thread, back, event, arg)
|
||||
else:
|
||||
#in jython we may not have a back frame
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_step_cmd = None
|
||||
info.pydev_state = STATE_RUN
|
||||
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
info.pydev_step_cmd = None
|
||||
|
||||
#if we are quitting, let's stop the tracing
|
||||
retVal = None
|
||||
if not mainDebugger.quitting:
|
||||
retVal = self.trace_dispatch
|
||||
|
||||
return retVal
|
||||
|
||||
if USE_PSYCO_OPTIMIZATION:
|
||||
try:
|
||||
import psyco
|
||||
trace_dispatch = psyco.proxy(trace_dispatch)
|
||||
except ImportError:
|
||||
if hasattr(sys, 'exc_clear'): #jython does not have it
|
||||
sys.exc_clear() #don't keep the traceback
|
||||
pass #ok, psyco not available
|
||||
@@ -0,0 +1,31 @@
|
||||
class IORedirector:
|
||||
'''This class works to redirect the write function to many streams
|
||||
'''
|
||||
|
||||
def __init__(self, *args):
|
||||
self._redirectTo = args
|
||||
|
||||
def write(self, s):
|
||||
for r in self._redirectTo:
|
||||
try:
|
||||
r.write(s)
|
||||
except:
|
||||
pass
|
||||
|
||||
class IOBuf:
|
||||
'''This class works as a replacement for stdio and stderr.
|
||||
It is a buffer and when its contents are requested, it will erase what
|
||||
|
||||
it has so far so that the next return will not return the same contents again.
|
||||
'''
|
||||
def __init__(self):
|
||||
self.buflist = []
|
||||
|
||||
def getvalue(self):
|
||||
b = self.buflist
|
||||
self.buflist = [] #clear it
|
||||
return ''.join(b)
|
||||
|
||||
def write(self, s):
|
||||
self.buflist.append(s)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
'''
|
||||
Psyco stub: should implement all the external API from psyco.
|
||||
'''
|
||||
|
||||
def proxy(func, *args, **kwargs):
|
||||
return func
|
||||
|
||||
def bind(func, *args, **kwargs):
|
||||
return func
|
||||
|
||||
def unbind(func, *args, **kwargs):
|
||||
return func
|
||||
|
||||
def unproxy(func, *args, **kwargs):
|
||||
return func
|
||||
|
||||
def full(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def log(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def runonly(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def background(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def cannotcompile(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def profile(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def stop(*args, **kwargs):
|
||||
pass
|
||||
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
Copied from the python xreload (available for change)
|
||||
|
||||
Alternative to reload().
|
||||
|
||||
This works by executing the module in a scratch namespace, and then
|
||||
patching classes, methods and functions in place. This avoids the
|
||||
need to patch instances. New objects are copied into the target
|
||||
namespace.
|
||||
|
||||
Some of the many limitations include:
|
||||
|
||||
- Global mutable objects other than classes are simply replaced, not patched
|
||||
|
||||
- Code using metaclasses is not handled correctly
|
||||
|
||||
- Code creating global singletons is not handled correctly
|
||||
|
||||
- Functions and methods using decorators (other than classmethod and
|
||||
staticmethod) is not handled correctly
|
||||
|
||||
- Renamings are not handled correctly
|
||||
|
||||
- Dependent modules are not reloaded
|
||||
|
||||
- When a dependent module contains 'from foo import bar', and
|
||||
reloading foo deletes foo.bar, the dependent module continues to use
|
||||
the old foo.bar object rather than failing
|
||||
|
||||
- Frozen modules and modules loaded from zip files aren't handled
|
||||
correctly
|
||||
|
||||
- Classes involving __slots__ are not handled correctly
|
||||
"""
|
||||
|
||||
import imp
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
def xreload(mod):
|
||||
"""Reload a module in place, updating classes, methods and functions.
|
||||
|
||||
Args:
|
||||
mod: a module object
|
||||
|
||||
Returns:
|
||||
The (updated) input object itself.
|
||||
"""
|
||||
# Get the module name, e.g. 'foo.bar.whatever'
|
||||
modname = mod.__name__
|
||||
# Get the module namespace (dict) early; this is part of the type check
|
||||
modns = mod.__dict__
|
||||
# Parse it into package name and module name, e.g. 'foo.bar' and 'whatever'
|
||||
i = modname.rfind(".")
|
||||
if i >= 0:
|
||||
pkgname, modname = modname[:i], modname[i+1:]
|
||||
else:
|
||||
pkgname = None
|
||||
# Compute the search path
|
||||
if pkgname:
|
||||
# We're not reloading the package, only the module in it
|
||||
pkg = sys.modules[pkgname]
|
||||
path = pkg.__path__ # Search inside the package
|
||||
else:
|
||||
# Search the top-level module path
|
||||
pkg = None
|
||||
path = None # Make find_module() uses the default search path
|
||||
# Find the module; may raise ImportError
|
||||
(stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path)
|
||||
# Turn it into a code object
|
||||
try:
|
||||
# Is it Python source code or byte code read from a file?
|
||||
if kind not in (imp.PY_COMPILED, imp.PY_SOURCE):
|
||||
# Fall back to built-in reload()
|
||||
return reload(mod)
|
||||
if kind == imp.PY_SOURCE:
|
||||
source = stream.read()
|
||||
code = compile(source, filename, "exec")
|
||||
else:
|
||||
import marshal
|
||||
code = marshal.load(stream)
|
||||
finally:
|
||||
if stream:
|
||||
stream.close()
|
||||
# Execute the code. We copy the module dict to a temporary; then
|
||||
# clear the module dict; then execute the new code in the module
|
||||
# dict; then swap things back and around. This trick (due to
|
||||
# Glyph Lefkowitz) ensures that the (readonly) __globals__
|
||||
# attribute of methods and functions is set to the correct dict
|
||||
# object.
|
||||
tmpns = modns.copy()
|
||||
modns.clear()
|
||||
modns["__name__"] = tmpns["__name__"]
|
||||
exec(code, modns)
|
||||
# Now we get to the hard part
|
||||
oldnames = set(tmpns)
|
||||
newnames = set(modns)
|
||||
# Update attributes in place
|
||||
for name in oldnames & newnames:
|
||||
modns[name] = _update(tmpns[name], modns[name])
|
||||
# Done!
|
||||
return mod
|
||||
|
||||
|
||||
def _update(oldobj, newobj):
|
||||
"""Update oldobj, if possible in place, with newobj.
|
||||
|
||||
If oldobj is immutable, this simply returns newobj.
|
||||
|
||||
Args:
|
||||
oldobj: the object to be updated
|
||||
newobj: the object used as the source for the update
|
||||
|
||||
Returns:
|
||||
either oldobj, updated in place, or newobj.
|
||||
"""
|
||||
if oldobj is newobj:
|
||||
# Probably something imported
|
||||
return newobj
|
||||
if type(oldobj) is not type(newobj):
|
||||
# Cop-out: if the type changed, give up
|
||||
return newobj
|
||||
if hasattr(newobj, "__reload_update__"):
|
||||
# Provide a hook for updating
|
||||
return newobj.__reload_update__(oldobj)
|
||||
|
||||
if hasattr(types, 'ClassType'):
|
||||
classtype = types.ClassType
|
||||
else:
|
||||
classtype = type
|
||||
|
||||
if isinstance(newobj, classtype):
|
||||
return _update_class(oldobj, newobj)
|
||||
if isinstance(newobj, types.FunctionType):
|
||||
return _update_function(oldobj, newobj)
|
||||
if isinstance(newobj, types.MethodType):
|
||||
return _update_method(oldobj, newobj)
|
||||
if isinstance(newobj, classmethod):
|
||||
return _update_classmethod(oldobj, newobj)
|
||||
if isinstance(newobj, staticmethod):
|
||||
return _update_staticmethod(oldobj, newobj)
|
||||
# Not something we recognize, just give up
|
||||
return newobj
|
||||
|
||||
|
||||
# All of the following functions have the same signature as _update()
|
||||
|
||||
|
||||
def _update_function(oldfunc, newfunc):
|
||||
"""Update a function object."""
|
||||
oldfunc.__doc__ = newfunc.__doc__
|
||||
oldfunc.__dict__.update(newfunc.__dict__)
|
||||
oldfunc.__code__ = newfunc.__code__
|
||||
oldfunc.__defaults__ = newfunc.__defaults__
|
||||
return oldfunc
|
||||
|
||||
|
||||
def _update_method(oldmeth, newmeth):
|
||||
"""Update a method object."""
|
||||
# XXX What if im_func is not a function?
|
||||
_update(oldmeth.im_func, newmeth.im_func)
|
||||
return oldmeth
|
||||
|
||||
|
||||
def _update_class(oldclass, newclass):
|
||||
"""Update a class object."""
|
||||
olddict = oldclass.__dict__
|
||||
newdict = newclass.__dict__
|
||||
oldnames = set(olddict)
|
||||
newnames = set(newdict)
|
||||
for name in newnames - oldnames:
|
||||
setattr(oldclass, name, newdict[name])
|
||||
for name in oldnames - newnames:
|
||||
delattr(oldclass, name)
|
||||
for name in oldnames & newnames - set(['__dict__', '__doc__']):
|
||||
setattr(oldclass, name, _update(olddict[name], newdict[name]))
|
||||
return oldclass
|
||||
|
||||
|
||||
def _update_classmethod(oldcm, newcm):
|
||||
"""Update a classmethod update."""
|
||||
# While we can't modify the classmethod object itself (it has no
|
||||
# mutable attributes), we *can* extract the underlying function
|
||||
# (by calling __get__(), which returns a method object) and update
|
||||
# it in-place. We don't have the class available to pass to
|
||||
# __get__() but any object except None will do.
|
||||
_update(oldcm.__get__(0), newcm.__get__(0))
|
||||
return newcm
|
||||
|
||||
|
||||
def _update_staticmethod(oldsm, newsm):
|
||||
"""Update a staticmethod update."""
|
||||
# While we can't modify the staticmethod object itself (it has no
|
||||
# mutable attributes), we *can* extract the underlying function
|
||||
# (by calling __get__(), which returns it) and update it in-place.
|
||||
# We don't have the class available to pass to __get__() but any
|
||||
# object except None will do.
|
||||
_update(oldsm.__get__(0), newsm.__get__(0))
|
||||
return newsm
|
||||
@@ -0,0 +1,352 @@
|
||||
try:
|
||||
import StringIO
|
||||
except:
|
||||
import io as StringIO
|
||||
import traceback
|
||||
|
||||
try:
|
||||
__setFalse = False
|
||||
except:
|
||||
import __builtin__
|
||||
setattr(__builtin__, 'True', 1)
|
||||
setattr(__builtin__, 'False', 0)
|
||||
|
||||
|
||||
MAX_ITEMS_TO_HANDLE = 500
|
||||
TOO_LARGE_MSG = 'Too large to show contents. Max items to show: ' + str(MAX_ITEMS_TO_HANDLE)
|
||||
TOO_LARGE_ATTR = 'Unable to handle:'
|
||||
|
||||
#=======================================================================================================================
|
||||
# UnableToResolveVariableException
|
||||
#=======================================================================================================================
|
||||
class UnableToResolveVariableException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InspectStub
|
||||
#=======================================================================================================================
|
||||
class InspectStub:
|
||||
def isbuiltin(self, _args):
|
||||
return False
|
||||
def isroutine(self, object):
|
||||
return False
|
||||
|
||||
try:
|
||||
import inspect
|
||||
except:
|
||||
inspect = InspectStub()
|
||||
|
||||
try:
|
||||
import java.lang #@UnresolvedImport
|
||||
except:
|
||||
pass
|
||||
|
||||
#types does not include a MethodWrapperType
|
||||
try:
|
||||
MethodWrapperType = type([].__str__)
|
||||
except:
|
||||
MethodWrapperType = None
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# AbstractResolver
|
||||
#=======================================================================================================================
|
||||
class AbstractResolver:
|
||||
'''
|
||||
This class exists only for documentation purposes to explain how to create a resolver.
|
||||
|
||||
Some examples on how to resolve things:
|
||||
- list: getDictionary could return a dict with index->item and use the index to resolve it later
|
||||
- set: getDictionary could return a dict with id(object)->object and reiterate in that array to resolve it later
|
||||
- arbitrary instance: getDictionary could return dict with attr_name->attr and use getattr to resolve it later
|
||||
'''
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
'''
|
||||
In this method, we'll resolve some child item given the string representation of the item in the key
|
||||
representing the previously asked dictionary.
|
||||
|
||||
@param var: this is the actual variable to be resolved.
|
||||
@param attribute: this is the string representation of a key previously returned in getDictionary.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def getDictionary(self, var):
|
||||
'''
|
||||
@param var: this is the variable that should have its children gotten.
|
||||
|
||||
@return: a dictionary where each pair key, value should be shown to the user as children items
|
||||
in the variables view for the given var.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# DefaultResolver
|
||||
#=======================================================================================================================
|
||||
class DefaultResolver:
|
||||
'''
|
||||
DefaultResolver is the class that'll actually resolve how to show some variable.
|
||||
'''
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
return getattr(var, attribute)
|
||||
|
||||
def getDictionary(self, var):
|
||||
if MethodWrapperType:
|
||||
return self._getPyDictionary(var)
|
||||
else:
|
||||
return self._getJyDictionary(var)
|
||||
|
||||
def _getJyDictionary(self, obj):
|
||||
ret = {}
|
||||
found = java.util.HashMap()
|
||||
|
||||
original = obj
|
||||
if hasattr(obj, '__class__') and obj.__class__ == java.lang.Class:
|
||||
|
||||
#get info about superclasses
|
||||
classes = []
|
||||
classes.append(obj)
|
||||
c = obj.getSuperclass()
|
||||
while c != None:
|
||||
classes.append(c)
|
||||
c = c.getSuperclass()
|
||||
|
||||
#get info about interfaces
|
||||
interfs = []
|
||||
for obj in classes:
|
||||
interfs.extend(obj.getInterfaces())
|
||||
classes.extend(interfs)
|
||||
|
||||
#now is the time when we actually get info on the declared methods and fields
|
||||
for obj in classes:
|
||||
|
||||
declaredMethods = obj.getDeclaredMethods()
|
||||
declaredFields = obj.getDeclaredFields()
|
||||
for i in range(len(declaredMethods)):
|
||||
name = declaredMethods[i].getName()
|
||||
ret[name] = declaredMethods[i].toString()
|
||||
found.put(name, 1)
|
||||
|
||||
for i in range(len(declaredFields)):
|
||||
name = declaredFields[i].getName()
|
||||
found.put(name, 1)
|
||||
#if declaredFields[i].isAccessible():
|
||||
declaredFields[i].setAccessible(True)
|
||||
#ret[name] = declaredFields[i].get( declaredFields[i] )
|
||||
try:
|
||||
ret[name] = declaredFields[i].get(original)
|
||||
except:
|
||||
ret[name] = declaredFields[i].toString()
|
||||
|
||||
#this simple dir does not always get all the info, that's why we have the part before
|
||||
#(e.g.: if we do a dir on String, some methods that are from other interfaces such as
|
||||
#charAt don't appear)
|
||||
try:
|
||||
d = dir(original)
|
||||
for name in d:
|
||||
if found.get(name) is not 1:
|
||||
ret[name] = getattr(original, name)
|
||||
except:
|
||||
#sometimes we're unable to do a dir
|
||||
pass
|
||||
|
||||
return ret
|
||||
|
||||
def _getPyDictionary(self, var):
|
||||
filterPrivate = False
|
||||
filterSpecial = True
|
||||
filterFunction = True
|
||||
filterBuiltIn = True
|
||||
|
||||
names = dir(var)
|
||||
d = {}
|
||||
|
||||
#Be aware that the order in which the filters are applied attempts to
|
||||
#optimize the operation by removing as many items as possible in the
|
||||
#first filters, leaving fewer items for later filters
|
||||
|
||||
if filterBuiltIn or filterFunction:
|
||||
for n in names:
|
||||
if filterSpecial:
|
||||
if n.startswith('__') and n.endswith('__'):
|
||||
continue
|
||||
|
||||
if filterPrivate:
|
||||
if n.startswith('_') or n.endswith('__'):
|
||||
continue
|
||||
|
||||
try:
|
||||
attr = getattr(var, n)
|
||||
|
||||
#filter builtins?
|
||||
if filterBuiltIn:
|
||||
if inspect.isbuiltin(attr):
|
||||
continue
|
||||
|
||||
#filter functions?
|
||||
if filterFunction:
|
||||
if inspect.isroutine(attr) or isinstance(attr, MethodWrapperType):
|
||||
continue
|
||||
except:
|
||||
#if some error occurs getting it, let's put it to the user.
|
||||
strIO = StringIO.StringIO()
|
||||
traceback.print_exc(file=strIO)
|
||||
attr = strIO.getvalue()
|
||||
|
||||
d[ n ] = attr
|
||||
|
||||
return d
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# DictResolver
|
||||
#=======================================================================================================================
|
||||
class DictResolver:
|
||||
|
||||
def resolve(self, dict, key):
|
||||
if key == '__len__':
|
||||
return None
|
||||
|
||||
if '(' not in key:
|
||||
#we have to treat that because the dict resolver is also used to directly resolve the global and local
|
||||
#scopes (which already have the items directly)
|
||||
return dict[key]
|
||||
|
||||
#ok, we have to iterate over the items to find the one that matches the id, because that's the only way
|
||||
#to actually find the reference from the string we have before.
|
||||
expected_id = int(key.split('(')[-1][:-1])
|
||||
for key, val in dict.items():
|
||||
if id(key) == expected_id:
|
||||
return val
|
||||
|
||||
raise UnableToResolveVariableException()
|
||||
|
||||
def getDictionary(self, dict):
|
||||
ret = {}
|
||||
|
||||
for key, val in dict.items():
|
||||
#we need to add the id because otherwise we cannot find the real object to get its contents later on.
|
||||
key = '%s (%s)' % (key, id(key))
|
||||
ret[key] = val
|
||||
|
||||
ret['__len__'] = len(dict)
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# TupleResolver
|
||||
#=======================================================================================================================
|
||||
class TupleResolver: #to enumerate tuples and lists
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
'''
|
||||
@param var: that's the original attribute
|
||||
@param attribute: that's the key passed in the dict (as a string)
|
||||
'''
|
||||
if attribute == '__len__' or attribute == TOO_LARGE_ATTR:
|
||||
return None
|
||||
return var[int(attribute)]
|
||||
|
||||
def getDictionary(self, var):
|
||||
#return dict( [ (i, x) for i, x in enumerate(var) ] )
|
||||
# modified 'cause jython does not have enumerate support
|
||||
l = len(var)
|
||||
d = {}
|
||||
|
||||
if l < MAX_ITEMS_TO_HANDLE:
|
||||
format = '%0' + str(int(len(str(l)))) + 'd'
|
||||
|
||||
|
||||
for i, item in zip(range(l), var):
|
||||
d[ format % i ] = item
|
||||
else:
|
||||
d[TOO_LARGE_ATTR] = TOO_LARGE_MSG
|
||||
d['__len__'] = len(var)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# SetResolver
|
||||
#=======================================================================================================================
|
||||
class SetResolver:
|
||||
'''
|
||||
Resolves a set as dict id(object)->object
|
||||
'''
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
if attribute == '__len__':
|
||||
return None
|
||||
|
||||
attribute = int(attribute)
|
||||
for v in var:
|
||||
if id(v) == attribute:
|
||||
return v
|
||||
|
||||
raise UnableToResolveVariableException('Unable to resolve %s in %s' % (attribute, var))
|
||||
|
||||
def getDictionary(self, var):
|
||||
d = {}
|
||||
for item in var:
|
||||
d[ id(item) ] = item
|
||||
d['__len__'] = len(var)
|
||||
return d
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InstanceResolver
|
||||
#=======================================================================================================================
|
||||
class InstanceResolver:
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
field = var.__class__.getDeclaredField(attribute)
|
||||
field.setAccessible(True)
|
||||
return field.get(var)
|
||||
|
||||
def getDictionary(self, obj):
|
||||
ret = {}
|
||||
|
||||
declaredFields = obj.__class__.getDeclaredFields()
|
||||
for i in range(len(declaredFields)):
|
||||
name = declaredFields[i].getName()
|
||||
try:
|
||||
declaredFields[i].setAccessible(True)
|
||||
ret[name] = declaredFields[i].get(obj)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# JyArrayResolver
|
||||
#=======================================================================================================================
|
||||
class JyArrayResolver:
|
||||
'''
|
||||
This resolves a regular Object[] array from java
|
||||
'''
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
if attribute == '__len__':
|
||||
return None
|
||||
return var[int(attribute)]
|
||||
|
||||
def getDictionary(self, obj):
|
||||
ret = {}
|
||||
|
||||
for i in range(len(obj)):
|
||||
ret[ i ] = obj[i]
|
||||
|
||||
ret['__len__'] = len(obj)
|
||||
return ret
|
||||
|
||||
defaultResolver = DefaultResolver()
|
||||
dictResolver = DictResolver()
|
||||
tupleResolver = TupleResolver()
|
||||
instanceResolver = InstanceResolver()
|
||||
jyArrayResolver = JyArrayResolver()
|
||||
setResolver = SetResolver()
|
||||
@@ -0,0 +1,78 @@
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
|
||||
try:
|
||||
import cStringIO as StringIO #may not always be available @UnusedImport
|
||||
except:
|
||||
try:
|
||||
import StringIO #@Reimport
|
||||
except:
|
||||
import io as StringIO
|
||||
|
||||
import threading
|
||||
import sys #@Reimport
|
||||
import traceback
|
||||
|
||||
class TracingFunctionHolder:
|
||||
'''This class exists just to keep some variables (so that we don't keep them in the global namespace).
|
||||
'''
|
||||
_original_tracing = None
|
||||
_warn = True
|
||||
_lock = threading.Lock()
|
||||
_traceback_limit = 1
|
||||
_warnings_shown = {}
|
||||
|
||||
|
||||
def GetExceptionTracebackStr():
|
||||
exc_info = sys.exc_info()
|
||||
s = StringIO.StringIO()
|
||||
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], file=s)
|
||||
return s.getvalue()
|
||||
|
||||
def _GetStackStr(frame):
|
||||
|
||||
msg = '\nIf this is needed, please check: ' + \
|
||||
'\nhttp://pydev.blogspot.com/2007/06/why-cant-pydev-debugger-work-with.html' + \
|
||||
'\nto see how to restore the debug tracing back correctly.\n'
|
||||
|
||||
if TracingFunctionHolder._traceback_limit:
|
||||
s = StringIO.StringIO()
|
||||
s.write('Call Location:\n')
|
||||
traceback.print_stack(f=frame, limit=TracingFunctionHolder._traceback_limit, file=s)
|
||||
msg = msg + s.getvalue()
|
||||
|
||||
return msg
|
||||
|
||||
def _InternalSetTrace(tracing_func):
|
||||
if TracingFunctionHolder._warn:
|
||||
frame = GetFrame()
|
||||
if frame is not None and frame.f_back is not None:
|
||||
if not frame.f_back.f_code.co_filename.lower().endswith('threading.py'):
|
||||
|
||||
message = \
|
||||
'\nPYDEV DEBUGGER WARNING:' + \
|
||||
'\nsys.settrace() should not be used when the debugger is being used.' + \
|
||||
'\nThis may cause the debugger to stop working correctly.' + \
|
||||
'%s' % _GetStackStr(frame.f_back)
|
||||
|
||||
if message not in TracingFunctionHolder._warnings_shown:
|
||||
#only warn about each message once...
|
||||
TracingFunctionHolder._warnings_shown[message] = 1
|
||||
sys.stderr.write('%s\n' % (message,))
|
||||
|
||||
TracingFunctionHolder._original_tracing(tracing_func)
|
||||
|
||||
def SetTrace(tracing_func):
|
||||
TracingFunctionHolder._lock.acquire()
|
||||
try:
|
||||
TracingFunctionHolder._warn = False
|
||||
_InternalSetTrace(tracing_func)
|
||||
TracingFunctionHolder._warn = True
|
||||
finally:
|
||||
TracingFunctionHolder._lock.release()
|
||||
|
||||
|
||||
def ReplaceSysSetTraceFunc():
|
||||
if TracingFunctionHolder._original_tracing is None:
|
||||
TracingFunctionHolder._original_tracing = sys.settrace
|
||||
sys.settrace = _InternalSetTrace
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
""" pydevd_vars deals with variables:
|
||||
resolution/conversion to XML.
|
||||
"""
|
||||
from pydevd_constants import * #@UnusedWildImport
|
||||
from types import * #@UnusedWildImport
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import sys #@Reimport
|
||||
try:
|
||||
from urllib import quote
|
||||
except:
|
||||
from urllib.parse import quote #@UnresolvedImport
|
||||
import threading
|
||||
import pydevd_resolver
|
||||
import traceback
|
||||
|
||||
#-------------------------------------------------------------------------- defining true and false for earlier versions
|
||||
|
||||
try:
|
||||
__setFalse = False
|
||||
except:
|
||||
import __builtin__
|
||||
setattr(__builtin__, 'True', 1)
|
||||
setattr(__builtin__, 'False', 0)
|
||||
|
||||
#------------------------------------------------------------------------------------------------------ class for errors
|
||||
|
||||
class VariableError(RuntimeError):pass
|
||||
class FrameNotFoundError(RuntimeError):pass
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------------------------------ resolvers in map
|
||||
|
||||
if not sys.platform.startswith("java"):
|
||||
typeMap = [
|
||||
#None means that it should not be treated as a compound variable
|
||||
|
||||
#isintance does not accept a tuple on some versions of python, so, we must declare it expanded
|
||||
(type(None), None,),
|
||||
(int, None),
|
||||
(float, None),
|
||||
(complex, None),
|
||||
(str, None),
|
||||
(tuple, pydevd_resolver.tupleResolver),
|
||||
(list, pydevd_resolver.tupleResolver),
|
||||
(dict, pydevd_resolver.dictResolver),
|
||||
]
|
||||
|
||||
try:
|
||||
typeMap.append((long, None))
|
||||
except:
|
||||
pass #not available on all python versions
|
||||
|
||||
try:
|
||||
typeMap.append((unicode, None))
|
||||
except:
|
||||
pass #not available on all python versions
|
||||
|
||||
try:
|
||||
typeMap.append((set, pydevd_resolver.setResolver))
|
||||
except:
|
||||
pass #not available on all python versions
|
||||
|
||||
try:
|
||||
typeMap.append((frozenset, pydevd_resolver.setResolver))
|
||||
except:
|
||||
pass #not available on all python versions
|
||||
|
||||
else: #platform is java
|
||||
from org.python import core #@UnresolvedImport
|
||||
typeMap = [
|
||||
(core.PyNone, None),
|
||||
(core.PyInteger, None),
|
||||
(core.PyLong, None),
|
||||
(core.PyFloat, None),
|
||||
(core.PyComplex, None),
|
||||
(core.PyString, None),
|
||||
(core.PyTuple, pydevd_resolver.tupleResolver),
|
||||
(core.PyList, pydevd_resolver.tupleResolver),
|
||||
(core.PyDictionary, pydevd_resolver.dictResolver),
|
||||
(core.PyStringMap, pydevd_resolver.dictResolver),
|
||||
]
|
||||
|
||||
if hasattr(core, 'PyJavaInstance'):
|
||||
#Jython 2.5b3 removed it.
|
||||
typeMap.append((core.PyJavaInstance, pydevd_resolver.instanceResolver))
|
||||
|
||||
|
||||
def getType(o):
|
||||
""" returns a triple (typeObject, typeString, resolver
|
||||
resolver != None means that variable is a container,
|
||||
and should be displayed as a hierarchy.
|
||||
Use the resolver to get its attributes.
|
||||
|
||||
All container objects should have a resolver.
|
||||
"""
|
||||
|
||||
try:
|
||||
type_object = type(o)
|
||||
type_name = type_object.__name__
|
||||
except:
|
||||
#This happens for org.python.core.InitModule
|
||||
return 'Unable to get Type', 'Unable to get Type', None
|
||||
|
||||
try:
|
||||
|
||||
if type_name == 'org.python.core.PyJavaInstance':
|
||||
return (type_object, type_name, pydevd_resolver.instanceResolver)
|
||||
|
||||
if type_name == 'org.python.core.PyArray':
|
||||
return (type_object, type_name, pydevd_resolver.jyArrayResolver)
|
||||
|
||||
for t in typeMap:
|
||||
if isinstance(o, t[0]):
|
||||
return (type_object, type_name, t[1])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
#no match return default
|
||||
return (type_object, type_name, pydevd_resolver.defaultResolver)
|
||||
|
||||
|
||||
def makeValidXmlValue(s):
|
||||
return s.replace('<', '<').replace('>', '>')
|
||||
|
||||
|
||||
def varToXML(v, name):
|
||||
""" single variable or dictionary to xml representation """
|
||||
type, typeName, resolver = getType(v)
|
||||
|
||||
try:
|
||||
if hasattr(v, '__class__'):
|
||||
try:
|
||||
cName = str(v.__class__)
|
||||
if cName.find('.') != -1:
|
||||
cName = cName.split('.')[-1]
|
||||
|
||||
elif cName.find("'") != -1: #does not have '.' (could be something like <type 'int'>)
|
||||
cName = cName[cName.index("'") + 1:]
|
||||
|
||||
if cName.endswith("'>"):
|
||||
cName = cName[:-2]
|
||||
except:
|
||||
cName = str(v.__class__)
|
||||
value = '%s: %s' % (cName, v)
|
||||
else:
|
||||
value = str(v)
|
||||
except:
|
||||
try:
|
||||
value = repr(v)
|
||||
except:
|
||||
value = 'Unable to get repr for %s' % v.__class__
|
||||
|
||||
xml = '<var name="%s" type="%s"' % (name, typeName)
|
||||
|
||||
if value:
|
||||
#cannot be too big... communication may not handle it.
|
||||
if len(value) > MAXIMUM_VARIABLE_REPRESENTATION_SIZE:
|
||||
value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE]
|
||||
value += '...'
|
||||
|
||||
#fix to work with unicode values
|
||||
try:
|
||||
if not IS_PY3K:
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf-8')
|
||||
else:
|
||||
if isinstance(value, bytes):
|
||||
value = value.encode('utf-8')
|
||||
except TypeError: #in java, unicode is a function
|
||||
pass
|
||||
|
||||
xmlValue = ' value="%s"' % (makeValidXmlValue(quote(value, '/>_= \t')))
|
||||
else:
|
||||
xmlValue = ''
|
||||
|
||||
if resolver is not None:
|
||||
xmlCont = ' isContainer="True"'
|
||||
else:
|
||||
xmlCont = ''
|
||||
|
||||
return ''.join((xml, xmlValue, xmlCont, ' />\n'))
|
||||
|
||||
|
||||
if USE_PSYCO_OPTIMIZATION:
|
||||
try:
|
||||
import psyco
|
||||
varToXML = psyco.proxy(varToXML)
|
||||
except ImportError:
|
||||
if hasattr(sys, 'exc_clear'): #jython does not have it
|
||||
sys.exc_clear() #don't keep the traceback -- clients don't want to see it
|
||||
|
||||
|
||||
def frameVarsToXML(frame):
|
||||
""" dumps frame variables to XML
|
||||
<var name="var_name" scope="local" type="type" value="value"/>
|
||||
"""
|
||||
xml = ""
|
||||
|
||||
keys = frame.f_locals.keys()
|
||||
if hasattr(keys, 'sort'):
|
||||
keys.sort() #Python 3.0 does not have it
|
||||
else:
|
||||
keys = sorted(keys) #Jython 2.1 does not have it
|
||||
|
||||
for k in keys:
|
||||
try:
|
||||
v = frame.f_locals[k]
|
||||
xml += varToXML(v, str(k))
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
sys.stderr.write("Unexpected error, recovered safely.\n")
|
||||
return xml
|
||||
|
||||
def iterFrames(initialFrame):
|
||||
'''NO-YIELD VERSION: Iterates through all the frames starting at the specified frame (which will be the first returned item)'''
|
||||
#cannot use yield
|
||||
frames = []
|
||||
|
||||
while initialFrame is not None:
|
||||
frames.append(initialFrame)
|
||||
initialFrame = initialFrame.f_back
|
||||
|
||||
return frames
|
||||
|
||||
def dumpFrames(thread_id):
|
||||
sys.stdout.write('dumping frames\n')
|
||||
if thread_id != GetThreadId(threading.currentThread()) :
|
||||
raise VariableError("findFrame: must execute on same thread")
|
||||
|
||||
curFrame = GetFrame()
|
||||
for frame in iterFrames(curFrame):
|
||||
sys.stdout.write('%s\n' % id(frame))
|
||||
|
||||
def findFrame(thread_id, frame_id):
|
||||
""" returns a frame on the thread that has a given frame_id """
|
||||
if thread_id != GetThreadId(threading.currentThread()) :
|
||||
raise VariableError("findFrame: must execute on same thread")
|
||||
|
||||
curFrame = GetFrame()
|
||||
if frame_id == "*":
|
||||
return curFrame # any frame is specified with "*"
|
||||
|
||||
frameFound = None
|
||||
lookingFor = int(frame_id)
|
||||
|
||||
for frame in iterFrames(curFrame):
|
||||
if lookingFor == id(frame):
|
||||
frameFound = frame
|
||||
del frame
|
||||
break
|
||||
|
||||
del frame
|
||||
|
||||
#for some reason unknown to me, python was holding a reference to the frame
|
||||
#if we didn't explicitly add those deletes (even after ending this context)
|
||||
#so, those dels are here for a reason (but still doesn't seem to fix everything)
|
||||
|
||||
#Reason: sys.exc_info holding reference to frame that raises exception (so, other places
|
||||
#need to call sys.exc_clear())
|
||||
del curFrame
|
||||
|
||||
if frameFound is None:
|
||||
msgFrames = ''
|
||||
i = 0
|
||||
|
||||
for frame in iterFrames(GetFrame()):
|
||||
i += 1
|
||||
msgFrames += str(id(frame))
|
||||
if i % 5 == 0:
|
||||
msgFrames += '\n'
|
||||
else:
|
||||
msgFrames += ' - '
|
||||
|
||||
errMsg = '''findFrame: frame not found.
|
||||
Looking for thread_id:%s, frame_id:%s
|
||||
Current thread_id:%s, available frames:
|
||||
%s
|
||||
''' % (thread_id, lookingFor, GetThreadId(threading.currentThread()), msgFrames)
|
||||
|
||||
raise FrameNotFoundError(errMsg)
|
||||
|
||||
return frameFound
|
||||
|
||||
def resolveCompoundVariable(thread_id, frame_id, scope, attrs):
|
||||
""" returns the value of the compound variable as a dictionary"""
|
||||
frame = findFrame(thread_id, frame_id)
|
||||
attrList = attrs.split('\t')
|
||||
if scope == "GLOBAL":
|
||||
var = frame.f_globals
|
||||
del attrList[0] # globals are special, and they get a single dummy unused attribute
|
||||
else:
|
||||
var = frame.f_locals
|
||||
|
||||
for k in attrList:
|
||||
type, _typeName, resolver = getType(var)
|
||||
var = resolver.resolve(var, k)
|
||||
|
||||
try:
|
||||
type, _typeName, resolver = getType(var)
|
||||
return resolver.getDictionary(var)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def evaluateExpression(thread_id, frame_id, expression, doExec):
|
||||
'''returns the result of the evaluated expression
|
||||
@param doExec: determines if we should do an exec or an eval
|
||||
'''
|
||||
frame = findFrame(thread_id, frame_id)
|
||||
|
||||
expression = expression.replace('@LINE@', '\n')
|
||||
|
||||
|
||||
#Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
|
||||
#(Names not resolved in generator expression in method)
|
||||
#See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
|
||||
updated_globals = dict()
|
||||
updated_globals.update(frame.f_globals)
|
||||
updated_globals.update(frame.f_locals) #locals later because it has precedence over the actual globals
|
||||
|
||||
try:
|
||||
|
||||
if doExec:
|
||||
try:
|
||||
#try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
|
||||
#it will have whatever the user actually did)
|
||||
compiled = compile(expression, '<string>', 'eval')
|
||||
except:
|
||||
exec(expression, updated_globals, frame.f_locals)
|
||||
else:
|
||||
result = eval(compiled, updated_globals, frame.f_locals)
|
||||
sys.stdout.write('%s\n' % (result,))
|
||||
return
|
||||
|
||||
else:
|
||||
result = None
|
||||
try:
|
||||
result = eval(expression, updated_globals, frame.f_locals)
|
||||
except Exception:
|
||||
s = StringIO()
|
||||
traceback.print_exc(file=s)
|
||||
result = s.getvalue()
|
||||
return result
|
||||
finally:
|
||||
#Should not be kept alive if an exception happens and this frame is kept in the stack.
|
||||
del updated_globals
|
||||
del frame
|
||||
|
||||
|
||||
def changeAttrExpression(thread_id, frame_id, attr, expression):
|
||||
'''Changes some attribute in a given frame.
|
||||
@note: it will not (currently) work if we're not in the topmost frame (that's a python
|
||||
deficiency -- and it appears that there is no way of making it currently work --
|
||||
will probably need some change to the python internals)
|
||||
'''
|
||||
frame = findFrame(thread_id, frame_id)
|
||||
|
||||
try:
|
||||
expression = expression.replace('@LINE@', '\n')
|
||||
#tests (needs proposed patch in python accepted)
|
||||
# if hasattr(frame, 'savelocals'):
|
||||
# if attr in frame.f_locals:
|
||||
# frame.f_locals[attr] = eval(expression, frame.f_globals, frame.f_locals)
|
||||
# frame.savelocals()
|
||||
# return
|
||||
#
|
||||
# elif attr in frame.f_globals:
|
||||
# frame.f_globals[attr] = eval(expression, frame.f_globals, frame.f_locals)
|
||||
# return
|
||||
|
||||
|
||||
if attr[:7] == "Globals":
|
||||
attr = attr[8:]
|
||||
if attr in frame.f_globals:
|
||||
frame.f_globals[attr] = eval(expression, frame.f_globals, frame.f_locals)
|
||||
else:
|
||||
#default way (only works for changing it in the topmost frame)
|
||||
exec('%s=%s' % (attr, expression), frame.f_globals, frame.f_locals)
|
||||
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
|
||||
#=======================================================================================================================
|
||||
# PydevdVmType
|
||||
#=======================================================================================================================
|
||||
class PydevdVmType:
|
||||
|
||||
PYTHON = 'python'
|
||||
JYTHON = 'jython'
|
||||
vm_type = None
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# SetVmType
|
||||
#=======================================================================================================================
|
||||
def SetVmType(vm_type):
|
||||
PydevdVmType.vm_type = vm_type
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# GetVmType
|
||||
#=======================================================================================================================
|
||||
def GetVmType():
|
||||
if PydevdVmType.vm_type is None:
|
||||
SetupType()
|
||||
return PydevdVmType.vm_type
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# SetupType
|
||||
#=======================================================================================================================
|
||||
def SetupType(str=None):
|
||||
if str is not None:
|
||||
PydevdVmType.vm_type = str
|
||||
return
|
||||
|
||||
if sys.platform.startswith("java"):
|
||||
PydevdVmType.vm_type = PydevdVmType.JYTHON
|
||||
else:
|
||||
PydevdVmType.vm_type = PydevdVmType.PYTHON
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
import fnmatch
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
|
||||
|
||||
try:
|
||||
__setFalse = False
|
||||
except:
|
||||
import __builtin__
|
||||
setattr(__builtin__, 'True', 1)
|
||||
setattr(__builtin__, 'False', 0)
|
||||
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# Jython?
|
||||
#=======================================================================================================================
|
||||
try:
|
||||
import org.python.core.PyDictionary #@UnresolvedImport @UnusedImport -- just to check if it could be valid
|
||||
def DictContains(d, key):
|
||||
return d.has_key(key)
|
||||
except:
|
||||
try:
|
||||
#Py3k does not have has_key anymore, and older versions don't have __contains__
|
||||
DictContains = dict.__contains__
|
||||
except:
|
||||
DictContains = dict.has_key
|
||||
|
||||
try:
|
||||
xrange
|
||||
except:
|
||||
#Python 3k does not have it
|
||||
xrange = range
|
||||
|
||||
try:
|
||||
enumerate
|
||||
except:
|
||||
def enumerate(lst):
|
||||
ret = []
|
||||
i=0
|
||||
for element in lst:
|
||||
ret.append((i, element))
|
||||
i+=1
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# getopt code copied since gnu_getopt is not available on jython 2.1
|
||||
#=======================================================================================================================
|
||||
class GetoptError(Exception):
|
||||
opt = ''
|
||||
msg = ''
|
||||
def __init__(self, msg, opt=''):
|
||||
self.msg = msg
|
||||
self.opt = opt
|
||||
Exception.__init__(self, msg, opt)
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
|
||||
def gnu_getopt(args, shortopts, longopts=[]):
|
||||
"""getopt(args, options[, long_options]) -> opts, args
|
||||
|
||||
This function works like getopt(), except that GNU style scanning
|
||||
mode is used by default. This means that option and non-option
|
||||
arguments may be intermixed. The getopt() function stops
|
||||
processing options as soon as a non-option argument is
|
||||
encountered.
|
||||
|
||||
If the first character of the option string is `+', or if the
|
||||
environment variable POSIXLY_CORRECT is set, then option
|
||||
processing stops as soon as a non-option argument is encountered.
|
||||
"""
|
||||
|
||||
opts = []
|
||||
prog_args = []
|
||||
if isinstance(longopts, ''.__class__):
|
||||
longopts = [longopts]
|
||||
else:
|
||||
longopts = list(longopts)
|
||||
|
||||
# Allow options after non-option arguments?
|
||||
if shortopts.startswith('+'):
|
||||
shortopts = shortopts[1:]
|
||||
all_options_first = True
|
||||
elif os.environ.get("POSIXLY_CORRECT"):
|
||||
all_options_first = True
|
||||
else:
|
||||
all_options_first = False
|
||||
|
||||
while args:
|
||||
if args[0] == '--':
|
||||
prog_args += args[1:]
|
||||
break
|
||||
|
||||
if args[0][:2] == '--':
|
||||
opts, args = do_longs(opts, args[0][2:], longopts, args[1:])
|
||||
elif args[0][:1] == '-':
|
||||
opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:])
|
||||
else:
|
||||
if all_options_first:
|
||||
prog_args += args
|
||||
break
|
||||
else:
|
||||
prog_args.append(args[0])
|
||||
args = args[1:]
|
||||
|
||||
return opts, prog_args
|
||||
|
||||
def do_longs(opts, opt, longopts, args):
|
||||
try:
|
||||
i = opt.index('=')
|
||||
except ValueError:
|
||||
optarg = None
|
||||
else:
|
||||
opt, optarg = opt[:i], opt[i + 1:]
|
||||
|
||||
has_arg, opt = long_has_args(opt, longopts)
|
||||
if has_arg:
|
||||
if optarg is None:
|
||||
if not args:
|
||||
raise GetoptError('option --%s requires argument' % opt, opt)
|
||||
optarg, args = args[0], args[1:]
|
||||
elif optarg:
|
||||
raise GetoptError('option --%s must not have an argument' % opt, opt)
|
||||
opts.append(('--' + opt, optarg or ''))
|
||||
return opts, args
|
||||
|
||||
# Return:
|
||||
# has_arg?
|
||||
# full option name
|
||||
def long_has_args(opt, longopts):
|
||||
possibilities = [o for o in longopts if o.startswith(opt)]
|
||||
if not possibilities:
|
||||
raise GetoptError('option --%s not recognized' % opt, opt)
|
||||
# Is there an exact match?
|
||||
if opt in possibilities:
|
||||
return False, opt
|
||||
elif opt + '=' in possibilities:
|
||||
return True, opt
|
||||
# No exact match, so better be unique.
|
||||
if len(possibilities) > 1:
|
||||
# XXX since possibilities contains all valid continuations, might be
|
||||
# nice to work them into the error msg
|
||||
raise GetoptError('option --%s not a unique prefix' % opt, opt)
|
||||
assert len(possibilities) == 1
|
||||
unique_match = possibilities[0]
|
||||
has_arg = unique_match.endswith('=')
|
||||
if has_arg:
|
||||
unique_match = unique_match[:-1]
|
||||
return has_arg, unique_match
|
||||
|
||||
def do_shorts(opts, optstring, shortopts, args):
|
||||
while optstring != '':
|
||||
opt, optstring = optstring[0], optstring[1:]
|
||||
if short_has_arg(opt, shortopts):
|
||||
if optstring == '':
|
||||
if not args:
|
||||
raise GetoptError('option -%s requires argument' % opt,
|
||||
opt)
|
||||
optstring, args = args[0], args[1:]
|
||||
optarg, optstring = optstring, ''
|
||||
else:
|
||||
optarg = ''
|
||||
opts.append(('-' + opt, optarg))
|
||||
return opts, args
|
||||
|
||||
def short_has_arg(opt, shortopts):
|
||||
for i in range(len(shortopts)):
|
||||
if opt == shortopts[i] != ':':
|
||||
return shortopts.startswith(':', i + 1)
|
||||
raise GetoptError('option -%s not recognized' % opt, opt)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# End getopt code
|
||||
#=======================================================================================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# parse_cmdline
|
||||
#=======================================================================================================================
|
||||
def parse_cmdline():
|
||||
""" parses command line and returns test directories, verbosity, test filter and test suites
|
||||
usage:
|
||||
runfiles.py -v|--verbosity <level> -f|--filter <regex> -t|--tests <Test.test1,Test2> dirs|files
|
||||
"""
|
||||
verbosity = 2
|
||||
test_filter = None
|
||||
tests = None
|
||||
|
||||
optlist, dirs = gnu_getopt(sys.argv[1:], "v:f:t:", ["verbosity=", "filter=", "tests="])
|
||||
for opt, value in optlist:
|
||||
if opt in ("-v", "--verbosity"):
|
||||
verbosity = value
|
||||
|
||||
elif opt in ("-f", "--filter"):
|
||||
test_filter = value.split(',')
|
||||
|
||||
elif opt in ("-t", "--tests"):
|
||||
tests = value.split(',')
|
||||
|
||||
if type([]) != type(dirs):
|
||||
dirs = [dirs]
|
||||
|
||||
ret_dirs = []
|
||||
for d in dirs:
|
||||
if '|' in d:
|
||||
#paths may come from the ide separated by |
|
||||
ret_dirs.extend(d.split('|'))
|
||||
else:
|
||||
ret_dirs.append(d)
|
||||
|
||||
return ret_dirs, int(verbosity), test_filter, tests
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# PydevTestRunner
|
||||
#=======================================================================================================================
|
||||
class PydevTestRunner:
|
||||
""" finds and runs a file or directory of files as a unit test """
|
||||
|
||||
__py_extensions = ["*.py", "*.pyw"]
|
||||
__exclude_files = ["__init__.*"]
|
||||
|
||||
def __init__(self, test_dir, test_filter=None, verbosity=2, tests=None):
|
||||
self.test_dir = test_dir
|
||||
self.__adjust_path()
|
||||
self.test_filter = self.__setup_test_filter(test_filter)
|
||||
self.verbosity = verbosity
|
||||
self.tests = tests
|
||||
|
||||
|
||||
def __adjust_path(self):
|
||||
""" add the current file or directory to the python path """
|
||||
path_to_append = None
|
||||
for n in xrange(len(self.test_dir)):
|
||||
dir_name = self.__unixify(self.test_dir[n])
|
||||
if os.path.isdir(dir_name):
|
||||
if not dir_name.endswith("/"):
|
||||
self.test_dir[n] = dir_name + "/"
|
||||
path_to_append = os.path.normpath(dir_name)
|
||||
elif os.path.isfile(dir_name):
|
||||
path_to_append = os.path.dirname(dir_name)
|
||||
else:
|
||||
msg = ("unknown type. \n%s\nshould be file or a directory.\n" % (dir_name))
|
||||
raise RuntimeError(msg)
|
||||
if path_to_append is not None:
|
||||
#Add it as the last one (so, first things are resolved against the default dirs and
|
||||
#if none resolves, then we try a relative import).
|
||||
sys.path.append(path_to_append)
|
||||
return
|
||||
|
||||
def __setup_test_filter(self, test_filter):
|
||||
""" turn a filter string into a list of filter regexes """
|
||||
if test_filter is None or len(test_filter) == 0:
|
||||
return None
|
||||
return [re.compile("test%s" % f) for f in test_filter]
|
||||
|
||||
def __is_valid_py_file(self, fname):
|
||||
""" tests that a particular file contains the proper file extension
|
||||
and is not in the list of files to exclude """
|
||||
is_valid_fname = 0
|
||||
for invalid_fname in self.__class__.__exclude_files:
|
||||
is_valid_fname += int(not fnmatch.fnmatch(fname, invalid_fname))
|
||||
if_valid_ext = 0
|
||||
for ext in self.__class__.__py_extensions:
|
||||
if_valid_ext += int(fnmatch.fnmatch(fname, ext))
|
||||
return is_valid_fname > 0 and if_valid_ext > 0
|
||||
|
||||
def __unixify(self, s):
|
||||
""" stupid windows. converts the backslash to forwardslash for consistency """
|
||||
return os.path.normpath(s).replace(os.sep, "/")
|
||||
|
||||
def __importify(self, s, dir=False):
|
||||
""" turns directory separators into dots and removes the ".py*" extension
|
||||
so the string can be used as import statement """
|
||||
if not dir:
|
||||
dirname, fname = os.path.split(s)
|
||||
|
||||
if fname.count('.') > 1:
|
||||
#if there's a file named xxx.xx.py, it is not a valid module, so, let's not load it...
|
||||
return
|
||||
|
||||
imp_stmt_pieces = [dirname.replace("\\", "/").replace("/", "."), os.path.splitext(fname)[0]]
|
||||
|
||||
if len(imp_stmt_pieces[0]) == 0:
|
||||
imp_stmt_pieces = imp_stmt_pieces[1:]
|
||||
|
||||
return ".".join(imp_stmt_pieces)
|
||||
|
||||
else: #handle dir
|
||||
return s.replace("\\", "/").replace("/", ".")
|
||||
|
||||
def __add_files(self, pyfiles, root, files):
|
||||
""" if files match, appends them to pyfiles. used by os.path.walk fcn """
|
||||
for fname in files:
|
||||
if self.__is_valid_py_file(fname):
|
||||
name_without_base_dir = self.__unixify(os.path.join(root, fname))
|
||||
pyfiles.append(name_without_base_dir)
|
||||
return
|
||||
|
||||
|
||||
def find_import_files(self):
|
||||
""" return a list of files to import """
|
||||
pyfiles = []
|
||||
|
||||
for base_dir in self.test_dir:
|
||||
if os.path.isdir(base_dir):
|
||||
if hasattr(os, 'walk'):
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
self.__add_files(pyfiles, root, files)
|
||||
else:
|
||||
# jython2.1 is too old for os.walk!
|
||||
os.path.walk(base_dir, self.__add_files, pyfiles)
|
||||
|
||||
elif os.path.isfile(base_dir):
|
||||
pyfiles.append(base_dir)
|
||||
|
||||
return pyfiles
|
||||
|
||||
def __get_module_from_str(self, modname, print_exception):
|
||||
""" Import the module in the given import path.
|
||||
* Returns the "final" module, so importing "coilib40.subject.visu"
|
||||
returns the "visu" module, not the "coilib40" as returned by __import__ """
|
||||
try:
|
||||
mod = __import__(modname)
|
||||
for part in modname.split('.')[1:]:
|
||||
mod = getattr(mod, part)
|
||||
return mod
|
||||
except:
|
||||
if print_exception:
|
||||
import traceback;traceback.print_exc()
|
||||
sys.stderr.write('ERROR: Module: %s could not be imported.\n' % (modname,))
|
||||
return None
|
||||
|
||||
def find_modules_from_files(self, pyfiles):
|
||||
""" returns a lisst of modules given a list of files """
|
||||
#let's make sure that the paths we want are in the pythonpath...
|
||||
imports = [self.__importify(s) for s in pyfiles]
|
||||
|
||||
system_paths = []
|
||||
for s in sys.path:
|
||||
system_paths.append(self.__importify(s, True))
|
||||
|
||||
|
||||
ret = []
|
||||
for imp in imports:
|
||||
if imp is None:
|
||||
continue #can happen if a file is not a valid module
|
||||
choices = []
|
||||
for s in system_paths:
|
||||
if imp.startswith(s):
|
||||
add = imp[len(s) + 1:]
|
||||
if add:
|
||||
choices.append(add)
|
||||
#sys.stdout.write(' ' + add + ' ')
|
||||
|
||||
if not choices:
|
||||
sys.stdout.write('PYTHONPATH not found for file: %s\n' % imp)
|
||||
else:
|
||||
for i, import_str in enumerate(choices):
|
||||
mod = self.__get_module_from_str(import_str, print_exception=i == len(choices) - 1)
|
||||
if mod is not None:
|
||||
ret.append(mod)
|
||||
break
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
def find_tests_from_modules(self, modules):
|
||||
""" returns the unittests given a list of modules """
|
||||
loader = unittest.TestLoader()
|
||||
|
||||
ret = []
|
||||
if self.tests:
|
||||
accepted_classes = {}
|
||||
accepted_methods = {}
|
||||
|
||||
for t in self.tests:
|
||||
splitted = t.split('.')
|
||||
if len(splitted) == 1:
|
||||
accepted_classes[t] = t
|
||||
|
||||
elif len(splitted) == 2:
|
||||
accepted_methods[t] = t
|
||||
|
||||
#===========================================================================================================
|
||||
# GetTestCaseNames
|
||||
#===========================================================================================================
|
||||
class GetTestCaseNames:
|
||||
"""Yes, we need a class for that (cannot use outer context on jython 2.1)"""
|
||||
|
||||
def __init__(self, accepted_classes, accepted_methods):
|
||||
self.accepted_classes = accepted_classes
|
||||
self.accepted_methods = accepted_methods
|
||||
|
||||
def __call__(self, testCaseClass):
|
||||
"""Return a sorted sequence of method names found within testCaseClass"""
|
||||
testFnNames = []
|
||||
className = testCaseClass.__name__
|
||||
|
||||
if DictContains(self.accepted_classes, className):
|
||||
for attrname in dir(testCaseClass):
|
||||
#If a class is chosen, we select all the 'test' methods'
|
||||
if attrname.startswith('test') and hasattr(getattr(testCaseClass, attrname), '__call__'):
|
||||
testFnNames.append(attrname)
|
||||
|
||||
else:
|
||||
for attrname in dir(testCaseClass):
|
||||
#If we have the class+method name, we must do a full check and have an exact match.
|
||||
if DictContains(self.accepted_methods, className + '.' + attrname):
|
||||
if hasattr(getattr(testCaseClass, attrname), '__call__'):
|
||||
testFnNames.append(attrname)
|
||||
|
||||
#sorted() is not available in jython 2.1
|
||||
testFnNames.sort()
|
||||
return testFnNames
|
||||
|
||||
|
||||
loader.getTestCaseNames = GetTestCaseNames(accepted_classes, accepted_methods)
|
||||
|
||||
|
||||
ret.extend([loader.loadTestsFromModule(m) for m in modules])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def filter_tests(self, test_objs):
|
||||
""" based on a filter name, only return those tests that have
|
||||
the test case names that match """
|
||||
test_suite = []
|
||||
for test_obj in test_objs:
|
||||
|
||||
if isinstance(test_obj, unittest.TestSuite):
|
||||
if test_obj._tests:
|
||||
test_obj._tests = self.filter_tests(test_obj._tests)
|
||||
if test_obj._tests:
|
||||
test_suite.append(test_obj)
|
||||
|
||||
elif isinstance(test_obj, unittest.TestCase):
|
||||
test_cases = []
|
||||
for tc in test_objs:
|
||||
try:
|
||||
testMethodName = tc._TestCase__testMethodName
|
||||
except AttributeError:
|
||||
#changed in python 2.5
|
||||
testMethodName = tc._testMethodName
|
||||
|
||||
if self.__match(self.test_filter, testMethodName) and self.__match_tests(self.tests, tc, testMethodName):
|
||||
test_cases.append(tc)
|
||||
return test_cases
|
||||
return test_suite
|
||||
|
||||
|
||||
def __match_tests(self, tests, test_case, test_method_name):
|
||||
if not tests:
|
||||
return 1
|
||||
|
||||
for t in tests:
|
||||
class_and_method = t.split('.')
|
||||
if len(class_and_method) == 1:
|
||||
#only class name
|
||||
if class_and_method[0] == test_case.__class__.__name__:
|
||||
return 1
|
||||
|
||||
elif len(class_and_method) == 2:
|
||||
if class_and_method[0] == test_case.__class__.__name__ and class_and_method[1] == test_method_name:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
|
||||
def __match(self, filter_list, name):
|
||||
""" returns whether a test name matches the test filter """
|
||||
if filter_list is None:
|
||||
return 1
|
||||
for f in filter_list:
|
||||
if re.match(f, name):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def run_tests(self):
|
||||
""" runs all tests """
|
||||
sys.stdout.write("Finding files...\n")
|
||||
files = self.find_import_files()
|
||||
sys.stdout.write('%s %s\n' % (self.test_dir, '... done'))
|
||||
sys.stdout.write("Importing test modules ... ")
|
||||
modules = self.find_modules_from_files(files)
|
||||
sys.stdout.write("done.\n")
|
||||
all_tests = self.find_tests_from_modules(modules)
|
||||
if self.test_filter or self.tests:
|
||||
|
||||
if self.test_filter:
|
||||
sys.stdout.write('Test Filter: %s' % ([p.pattern for p in self.test_filter],))
|
||||
|
||||
if self.tests:
|
||||
sys.stdout.write('Tests to run: %s' % (self.tests,))
|
||||
|
||||
all_tests = self.filter_tests(all_tests)
|
||||
|
||||
sys.stdout.write('\n')
|
||||
runner = unittest.TextTestRunner(stream=sys.stdout, descriptions=1, verbosity=verbosity)
|
||||
runner.run(unittest.TestSuite(all_tests))
|
||||
return
|
||||
|
||||
#=======================================================================================================================
|
||||
# main
|
||||
#=======================================================================================================================
|
||||
if __name__ == '__main__':
|
||||
dirs, verbosity, test_filter, tests = parse_cmdline()
|
||||
PydevTestRunner(dirs, test_filter, verbosity, tests).run_tests()
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
from unittest import TestResult
|
||||
|
||||
class TestListener:
|
||||
""" Simulate a Java interface by providing an abstract class
|
||||
All methods need to be implemented by classes extending TestListener
|
||||
"""
|
||||
|
||||
# Test was successful.
|
||||
def addSuccess(self, test):
|
||||
raise NotImplementedError, "TestListener.addSuccess()"
|
||||
|
||||
# An error occured
|
||||
def addError(self, test, err):
|
||||
raise NotImplementedError, "TestListener.addError()"
|
||||
|
||||
# A failure occurred.
|
||||
def addFailure(self, test, err):
|
||||
raise NotImplementedError, "TestListener.addFailure()"
|
||||
|
||||
# A test started.
|
||||
def startTest(self, test):
|
||||
raise NotImplementedError, "TestListener.startTest()"
|
||||
|
||||
# A test ended.
|
||||
def endTest(self, test):
|
||||
raise NotImplementedError, "TestListener.endTest()"
|
||||
|
||||
class TestResultWithListeners(TestResult):
|
||||
def __init__(self):
|
||||
TestResult.__init__(self)
|
||||
self.listeners = []
|
||||
|
||||
def startTest(self, test):
|
||||
TestResult.startTest(self, test)
|
||||
for listener in self.listeners:
|
||||
listener.startTest(test)
|
||||
|
||||
def endTest(self, test):
|
||||
for listener in self.listeners:
|
||||
listener.endTest(test)
|
||||
|
||||
def addSuccess(self, test):
|
||||
TestResult.addSuccess(self, test)
|
||||
for listener in self.listeners:
|
||||
listener.addSuccess(test)
|
||||
|
||||
def addError(self, test, err):
|
||||
TestResult.addError(self, test, err)
|
||||
for listener in self.listeners:
|
||||
listener.addError(test, err)
|
||||
|
||||
def addFailure(self, test, err):
|
||||
TestResult.addFailure(self, test, err)
|
||||
for listener in self.listeners:
|
||||
listener.addFailure(test, err)
|
||||
|
||||
def addListener(self, listener):
|
||||
self.listeners.append(listener)
|
||||
|
||||
def removeListener(self, listener):
|
||||
self.listeners.remove(listener)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/pluginTestSrc">
|
||||
<sourceFolder url="file://$MODULE_DIR$/pluginTestSrc" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="testFramework-java" />
|
||||
<orderEntry type="library" name="JUnit3" level="project" />
|
||||
<orderEntry type="module" module-name="java-impl" />
|
||||
<orderEntry type="module" module-name="python-tests" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="testFramework-java" />
|
||||
<orderEntry type="module" module-name="python" />
|
||||
<orderEntry type="module" module-name="codeInsight-tests" />
|
||||
<orderEntry type="library" name="JUnit3" level="project" />
|
||||
<orderEntry type="module" module-name="idea" />
|
||||
<orderEntry type="module" module-name="idea-tests" />
|
||||
<orderEntry type="module" module-name="testFramework" />
|
||||
<orderEntry type="module" module-name="python-ide" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="platform-ultimate" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="resources-en" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="xdebugger-impl" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="python-py" scope="RUNTIME" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<orderEntry type="module" module-name="xml-openapi" />
|
||||
<orderEntry type="library" name="OroMatcher" level="project" />
|
||||
<orderEntry type="module" module-name="xml" />
|
||||
<orderEntry type="module" module-name="xdebugger-api" />
|
||||
</component>
|
||||
<component name="copyright">
|
||||
<Base>
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
<component>
|
||||
<group id="navigation">
|
||||
<feature
|
||||
id="navigation.popup.class"
|
||||
tip-file="GoToClass.html"
|
||||
min-usage-count="3"
|
||||
first-show="1"
|
||||
successive-show="5"/>
|
||||
<feature
|
||||
id="navigation.popup.file"
|
||||
tip-file="GoToClass.html"
|
||||
min-usage-count="2"
|
||||
first-show="3"
|
||||
successive-show="5"/>
|
||||
<feature
|
||||
id="navigation.popup.symbol"
|
||||
tip-file="GoToSymbol.html"
|
||||
first-show="5"
|
||||
successive-show="8"/>
|
||||
<feature
|
||||
id="navigation.popup.camelprefix"
|
||||
tip-file="CamelPrefixesInNavigationPopups.html"
|
||||
min-usage-count="3"
|
||||
first-show="4"
|
||||
successive-show="7">
|
||||
<dependency id="navigation.popup.class"/>
|
||||
<dependency id="navigation.popup.file"/>
|
||||
<dependency id="navigation.popup.symbol"/>
|
||||
</feature>
|
||||
<feature
|
||||
id="navigation.popup.wildcards"
|
||||
tip-file="WildcardsInNavigationPopups.html"
|
||||
min-usage-count="2"
|
||||
first-show="3"
|
||||
successive-show="5">
|
||||
<dependency id="navigation.popup.class"/>
|
||||
<dependency id="navigation.popup.file"/>
|
||||
<dependency id="navigation.popup.symbol"/>
|
||||
</feature>
|
||||
<feature
|
||||
id="navigation.popup.file.structure"
|
||||
tip-file="FileStructurePopup.html"
|
||||
min-usage-count="3"
|
||||
first-show="2"
|
||||
successive-show="5"
|
||||
/>
|
||||
<feature
|
||||
id="navigation.recent.files"
|
||||
tip-file="RecentFiles.html"
|
||||
min-usage-count="3"
|
||||
first-show="3"
|
||||
successive-show="5"
|
||||
/>
|
||||
<feature
|
||||
id="navigation.select.in"
|
||||
tip-file="SelectIn.html"
|
||||
min-usage-count="3"
|
||||
first-show="4"
|
||||
successive-show="7"
|
||||
/>
|
||||
<feature
|
||||
id="navigation.goto.declaration"
|
||||
tip-file="GoToDeclaration.html"
|
||||
min-usage-count="15"
|
||||
first-show="1"
|
||||
successive-show="4"
|
||||
/>
|
||||
<feature
|
||||
id="navigation.goto.usages"
|
||||
tip-file="ShowUsages.html"
|
||||
min-usage-count="3"
|
||||
first-show="5"
|
||||
successive-show="5"/>
|
||||
</group>
|
||||
|
||||
<group id="completion">
|
||||
<feature
|
||||
id="editing.completion.basic"
|
||||
tip-file="CodeCompletion.html"
|
||||
min-usage-count="15"
|
||||
first-show="1"
|
||||
successive-show="3"/>
|
||||
<feature
|
||||
id="editing.completion.replace"
|
||||
tip-file="TabInLookups.html"
|
||||
min-usage-count="10"
|
||||
first-show="6"
|
||||
successive-show="8"
|
||||
/>
|
||||
<feature
|
||||
id="editing.completion.finishByDotEtc"
|
||||
tip-file="DotEtcInLookups.html"
|
||||
min-usage-count="7"
|
||||
first-show="4"
|
||||
successive-show="8"
|
||||
/>
|
||||
<feature
|
||||
id="editing.completion.camelHumps"
|
||||
tip-file="CamelHumpsInCodeCompletion.html"
|
||||
min-usage-count="8"
|
||||
first-show="5"
|
||||
successive-show="7"
|
||||
/>
|
||||
</group>
|
||||
|
||||
<group id="editing">
|
||||
<feature
|
||||
id="editing.select.word"
|
||||
tip-file="CtrlW.html"
|
||||
min-usage-count="10"
|
||||
first-show="2"
|
||||
successive-show="4"/>
|
||||
</group>
|
||||
|
||||
<group id="codeassists">
|
||||
<feature
|
||||
id="codeassists.quickjavadoc"
|
||||
tip-file="QuickJavaDoc.html"
|
||||
min-usage-count="3"
|
||||
first-show="3"
|
||||
successive-show="5"
|
||||
/>
|
||||
<feature
|
||||
id="codeassists.quickdefinition"
|
||||
tip-file="CtrlShiftI.html"
|
||||
min-usage-count="9"
|
||||
first-show="3"
|
||||
successive-show="5"
|
||||
/>
|
||||
<feature
|
||||
id="codeassists.quickdefinition.lookup"
|
||||
tip-file="CtrlShiftIForLookup.html"
|
||||
min-usage-count="1"
|
||||
first-show="5"
|
||||
successive-show="7">
|
||||
<dependency id="codeassists.quickdefinition"/>
|
||||
</feature>
|
||||
<feature
|
||||
id="codeassists.quickjavadoc.lookup"
|
||||
tip-file="QuickJavaDocInLookups.html"
|
||||
min-usage-count="1"
|
||||
first-show="5"
|
||||
successive-show="7">
|
||||
<dependency id="codeassists.quickjavadoc"/>
|
||||
</feature>
|
||||
<feature
|
||||
id="codeassists.quickjavadoc.ctrln"
|
||||
tip-file="QuickJavaDocInLookups.html"
|
||||
min-usage-count="1"
|
||||
first-show="5"
|
||||
successive-show="7">
|
||||
<dependency id="codeassists.quickjavadoc"/>
|
||||
</feature>
|
||||
<feature
|
||||
id="codeassists.surroundwith.statement"
|
||||
tip-file="RSurroundWith.html"
|
||||
first-show="5"
|
||||
successive-show="5"
|
||||
min-usage-count="4"
|
||||
/>
|
||||
<feature
|
||||
id="codeassists.surroundwith.expression"
|
||||
tip-file="RSurroundWith.html"
|
||||
first-show="6"
|
||||
successive-show="5"
|
||||
min-usage-count="3"
|
||||
/>
|
||||
<feature
|
||||
id="codeassists.comment.block"
|
||||
tip-file="RCommentCode.html"
|
||||
first-show="6"
|
||||
successive-show="5"
|
||||
min-usage-count="3"
|
||||
/>
|
||||
<feature
|
||||
id="codeassists.comment.line"
|
||||
tip-file="RCommentCode.html"
|
||||
first-show="6"
|
||||
successive-show="5"
|
||||
min-usage-count="3"
|
||||
/>
|
||||
</group>
|
||||
|
||||
<group id="refactoring">
|
||||
<feature
|
||||
id="refactoring.introduceVariable"
|
||||
tip-file="IntroduceVariable.html"
|
||||
min-usage-count="5"
|
||||
first-show="1"
|
||||
successive-show="3"
|
||||
/>
|
||||
|
||||
<feature
|
||||
id="refactoring.rename"
|
||||
tip-file="Rename.html"
|
||||
min-usage-count="2"
|
||||
first-show="1"
|
||||
successive-show="3"
|
||||
/>
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
<group id="ui">
|
||||
<feature
|
||||
id="ui.tree.speedsearch"
|
||||
tip-file="SpeedSearch.html"
|
||||
first-show="5"
|
||||
successive-show="4"
|
||||
min-usage-count="6"
|
||||
/>
|
||||
<feature
|
||||
id="ui.scheme.quickswitch"
|
||||
tip-file="QuickSwitchScheme.html"
|
||||
first-show="7"
|
||||
successive-show="3"
|
||||
min-usage-count="1"
|
||||
/>
|
||||
<feature
|
||||
id="ui.recentchanges"
|
||||
tip-file="RecentChanges.html"
|
||||
first-show="5"
|
||||
successive-show="5"
|
||||
min-usage-count="1"
|
||||
/>
|
||||
<!-- TODO: Uncomment usage in InsertPathAction when FeatureUsageTracker goes to OpenApi
|
||||
<feature
|
||||
id="ui.commandLine.insertPath"
|
||||
name="Insert path in command line"
|
||||
tip-file="InsertPathShortcut.html"
|
||||
first-show="6"
|
||||
successive-show="5"
|
||||
min-usage-count="3"
|
||||
/>
|
||||
-->
|
||||
<feature id="navigation.popup.action"
|
||||
tip-file="GoToAction.html"
|
||||
first-show="7"
|
||||
successive-show="3"
|
||||
min-usage-count="1"/>
|
||||
<feature id="find.recent.search"
|
||||
tip-file="RecentSearch.html"
|
||||
first-show="10"
|
||||
successive-show="3"
|
||||
min-usage-count="5"/>
|
||||
<feature id="find.completion"
|
||||
tip-file="CodeCompletionInSearch.html"
|
||||
first-show="10"
|
||||
successive-show="3"
|
||||
min-usage-count="5"/>
|
||||
</group>
|
||||
</component>
|
||||
@@ -41,7 +41,9 @@
|
||||
<!-- Run/Debug -->
|
||||
<configurationType implementation="com.jetbrains.python.run.PythonConfigurationType"/>
|
||||
<programRunner implementation="com.jetbrains.python.run.PythonRunner"/>
|
||||
<programRunner implementation="com.jetbrains.python.debugger.PyDebugRunner"/>
|
||||
<configurationProducer implementation="com.jetbrains.python.run.PythonRunConfigurationProducer"/>
|
||||
<xdebugger.breakpointType implementation="com.jetbrains.python.debugger.PyLineBreakpointType"/>
|
||||
|
||||
<configurationType implementation="com.jetbrains.python.testing.PythonUnitTestConfigurationType"/>
|
||||
<testSrcLocator implementation="com.jetbrains.python.testing.PythonUnitTestTestIdUrlProvider"/>
|
||||
@@ -117,7 +119,7 @@
|
||||
<completion.contributor language="DjangoTemplate"
|
||||
implementationClass="com.jetbrains.django.completion.DjangoNameCompletionContributor"/>
|
||||
|
||||
<psi.referenceContributor implementation="com.jetbrains.django.ref.ViewMethodReferenceContributor"/>
|
||||
<psi.referenceContributor implementation="com.jetbrains.django.ref.DjangoReferenceContributor"/>
|
||||
|
||||
<inspectionToolProvider implementation="com.jetbrains.django.inspection.DjangoInspectionToolProvider"/>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.PathUtil;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -23,4 +23,8 @@ public class PythonHelpersLocator {
|
||||
}
|
||||
return new File(jarPath);
|
||||
}
|
||||
|
||||
public static String getHelperPath(String scriptName) {
|
||||
return new File(getHelpersRoot(), scriptName).getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class PythonLanguage extends Language {
|
||||
private final IFileElementType ELTYPE_FILE = new IStubFileElementType(this) {
|
||||
@Override
|
||||
public int getStubVersion() {
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
private final Set<Class<? extends PyAnnotator>> _annotators = new CopyOnWriteArraySet<Class<? extends PyAnnotator>>();
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.execution.process.ProcessHandler;
|
||||
import com.intellij.execution.ui.ConsoleView;
|
||||
import com.intellij.execution.ui.ConsoleViewContentType;
|
||||
import com.intellij.execution.ui.ExecutionConsole;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.xdebugger.XDebugProcess;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
|
||||
import com.jetbrains.python.debugger.local.PyLocalPositionConverter;
|
||||
import com.jetbrains.python.debugger.pydev.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static javax.swing.SwingUtilities.invokeLater;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
// todo: bundle messages
|
||||
// todo: pydevd supports module reloading - look for a way to use the feature
|
||||
// todo: smart step into
|
||||
public class PyDebugProcess extends XDebugProcess {
|
||||
|
||||
private final PyPositionConverter myPositionConverter = new PyLocalPositionConverter();
|
||||
private final RemoteDebugger myDebugger;
|
||||
private final XBreakpointHandler[] myBreakpointHandlers;
|
||||
private final PyDebuggerEditorsProvider myEditorsProvider;
|
||||
private final ProcessHandler myProcessHandler;
|
||||
private final ExecutionConsole myExecutionConsole;
|
||||
private final Map<PySourcePosition, XLineBreakpoint> myRegisteredBreakpoints = new ConcurrentHashMap<PySourcePosition, XLineBreakpoint>();
|
||||
private volatile PyThreadInfo mySuspendedThread = null;
|
||||
|
||||
protected PyDebugProcess(@NotNull XDebugSession session,
|
||||
final ServerSocket serverSocket,
|
||||
final ExecutionConsole executionConsole,
|
||||
final ProcessHandler processHandler) {
|
||||
super(session);
|
||||
session.setPauseActionSupported(true); // todo: implement and drop
|
||||
myDebugger = new RemoteDebugger(this, serverSocket, 10);
|
||||
myBreakpointHandlers = new XBreakpointHandler[]{new PyLineBreakpointHandler(this)};
|
||||
myEditorsProvider = new PyDebuggerEditorsProvider();
|
||||
myProcessHandler = processHandler;
|
||||
myExecutionConsole = executionConsole;
|
||||
}
|
||||
|
||||
public PyPositionConverter getPositionConverter() {
|
||||
return myPositionConverter;
|
||||
}
|
||||
|
||||
public XBreakpointHandler<?>[] getBreakpointHandlers() {
|
||||
return myBreakpointHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XDebuggerEditorsProvider getEditorsProvider() {
|
||||
return myEditorsProvider;
|
||||
}
|
||||
|
||||
protected ProcessHandler doGetProcessHandler() {
|
||||
return myProcessHandler;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ExecutionConsole createConsole() {
|
||||
return myExecutionConsole;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionInitialized() {
|
||||
super.sessionInitialized();
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(null, "Connecting to debugger", false) {
|
||||
public void run(@NotNull final ProgressIndicator indicator) {
|
||||
indicator.setText("Connecting to debugger...");
|
||||
try {
|
||||
myDebugger.waitForConnect();
|
||||
handshake();
|
||||
registerBreakpoints();
|
||||
myDebugger.sendCommand(new RunCommand());
|
||||
}
|
||||
catch (final Exception e) {
|
||||
myProcessHandler.destroyProcess();
|
||||
invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
Messages.showErrorDialog("Unable to establish connection with debugger:\n" + e.getMessage(), "Connecting to debugger");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handshake() throws PyDebuggerException {
|
||||
final String remoteVersion = myDebugger.handshake();
|
||||
((ConsoleView)myExecutionConsole).print("Connected to pydevd (version " + remoteVersion + ")\n", ConsoleViewContentType.SYSTEM_OUTPUT);
|
||||
}
|
||||
|
||||
private void registerBreakpoints() {
|
||||
for (Map.Entry<PySourcePosition, XLineBreakpoint> entry : myRegisteredBreakpoints.entrySet()) {
|
||||
addBreakpoint(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void startStepOver() {
|
||||
resume(ResumeCommand.Mode.STEP_OVER);
|
||||
}
|
||||
|
||||
public void startStepInto() {
|
||||
resume(ResumeCommand.Mode.STEP_INTO);
|
||||
}
|
||||
|
||||
public void startStepOut() {
|
||||
resume(ResumeCommand.Mode.STEP_OUT);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
myDebugger.disconnect();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
resume(ResumeCommand.Mode.RESUME);
|
||||
}
|
||||
|
||||
private void resume(final ResumeCommand.Mode mode) {
|
||||
if (myDebugger.isConnected() && mySuspendedThread != null) {
|
||||
final ResumeCommand command = new ResumeCommand(mySuspendedThread.getId(), mode);
|
||||
mySuspendedThread = null;
|
||||
myDebugger.sendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
public void runToPosition(@NotNull XSourcePosition position) {
|
||||
if (myDebugger.isConnected() && mySuspendedThread != null) {
|
||||
final PySourcePosition pyPosition = myPositionConverter.convert(position);
|
||||
myDebugger.sendCommand(new SetBreakpointCommand(pyPosition.getFile(), pyPosition.getLine())); // set temp. breakpoint
|
||||
resume(ResumeCommand.Mode.RESUME);
|
||||
}
|
||||
}
|
||||
|
||||
public PyDebugValue evaluate(final String expression) throws PyDebuggerException {
|
||||
PyStackFrame frame = currentFrame();
|
||||
return myDebugger.evaluate(frame.getThreadId(), frame.getFrameId(), expression);
|
||||
}
|
||||
|
||||
public List<PyDebugValue> loadFrame() throws PyDebuggerException {
|
||||
PyStackFrame frame = currentFrame();
|
||||
return myDebugger.loadFrame(frame.getThreadId(), frame.getFrameId());
|
||||
}
|
||||
|
||||
private PyStackFrame currentFrame() throws PyDebuggerException {
|
||||
if (!myDebugger.isConnected()) {
|
||||
throw new PyDebuggerException("Disconnected");
|
||||
}
|
||||
|
||||
final PyStackFrame frame = (PyStackFrame)getSession().getCurrentStackFrame();
|
||||
if (frame == null) {
|
||||
throw new PyDebuggerException("Process is running");
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public void addBreakpoint(PySourcePosition position, XLineBreakpoint breakpoint) {
|
||||
myRegisteredBreakpoints.put(position, breakpoint);
|
||||
if (myDebugger.isConnected()) {
|
||||
myDebugger.sendCommand(new SetBreakpointCommand(position.getFile(), position.getLine()));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeBreakpoint(PySourcePosition position) {
|
||||
myRegisteredBreakpoints.remove(position);
|
||||
if (myDebugger.isConnected()) {
|
||||
myDebugger.sendCommand(new RemoveBreakpointCommand(position.getFile(), position.getLine()));
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<PyThreadInfo> getThreads() {
|
||||
return myDebugger.getThreads();
|
||||
}
|
||||
|
||||
public void threadSuspended(final PyThreadInfo threadInfo) {
|
||||
if (mySuspendedThread != null) {
|
||||
// todo: XDebugSession supports only one suspend context
|
||||
final ResumeCommand command = new ResumeCommand(threadInfo.getId(), ResumeCommand.Mode.RESUME);
|
||||
myDebugger.sendCommand(command);
|
||||
return;
|
||||
}
|
||||
mySuspendedThread = threadInfo;
|
||||
|
||||
final List<PyStackFrameInfo> frames = threadInfo.getFrames();
|
||||
if (frames != null) {
|
||||
final PySuspendContext suspendContext = new PySuspendContext(this, threadInfo);
|
||||
|
||||
XLineBreakpoint breakpoint = null;
|
||||
if (threadInfo.isStopOnBreakpoint()) {
|
||||
final PySourcePosition position = frames.get(0).getPosition();
|
||||
breakpoint = myRegisteredBreakpoints.get(position);
|
||||
if (breakpoint == null) {
|
||||
myDebugger.sendCommand(new RemoveBreakpointCommand(position.getFile(), position.getLine())); // remove temp. breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
if (breakpoint != null) {
|
||||
if (!getSession().breakpointReached(breakpoint, suspendContext)) {
|
||||
resume();
|
||||
}
|
||||
}
|
||||
else {
|
||||
getSession().positionReached(suspendContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.ExecutionResult;
|
||||
import com.intellij.execution.Executor;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.configurations.RunProfile;
|
||||
import com.intellij.execution.configurations.RunProfileState;
|
||||
import com.intellij.execution.executors.DefaultDebugExecutor;
|
||||
import com.intellij.execution.runners.ExecutionEnvironment;
|
||||
import com.intellij.execution.runners.GenericProgramRunner;
|
||||
import com.intellij.execution.ui.RunContentDescriptor;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.xdebugger.XDebugProcess;
|
||||
import com.intellij.xdebugger.XDebugProcessStarter;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XDebuggerManager;
|
||||
import com.jetbrains.python.PythonHelpersLocator;
|
||||
import com.jetbrains.python.run.AbstractPythonRunConfiguration;
|
||||
import com.jetbrains.python.run.CommandLinePatcher;
|
||||
import com.jetbrains.python.run.PythonCommandLineState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PyDebugRunner extends GenericProgramRunner {
|
||||
@NotNull
|
||||
public String getRunnerId() {
|
||||
return "PyDebugRunner";
|
||||
}
|
||||
|
||||
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
|
||||
return DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) && profile instanceof AbstractPythonRunConfiguration;
|
||||
}
|
||||
|
||||
protected RunContentDescriptor doExecute(Project project, Executor executor, RunProfileState state,
|
||||
RunContentDescriptor contentToReuse,
|
||||
ExecutionEnvironment env) throws ExecutionException {
|
||||
FileDocumentManager.getInstance().saveAllDocuments();
|
||||
|
||||
final ServerSocket serverSocket;
|
||||
try {
|
||||
//noinspection SocketOpenedButNotSafelyClosed
|
||||
serverSocket = new ServerSocket(0);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new ExecutionException("Failed to find free socket port", e);
|
||||
}
|
||||
|
||||
PythonCommandLineState pyState = (PythonCommandLineState) state;
|
||||
final ExecutionResult result = pyState.execute(new CommandLinePatcher() {
|
||||
public void patchCommandLine(GeneralCommandLine commandLine) {
|
||||
final String[] args = new String[]{
|
||||
PythonHelpersLocator.getHelperPath("pydev/pydevd.py"),
|
||||
"--client",
|
||||
"127.0.0.1",
|
||||
"--port",
|
||||
String.valueOf(serverSocket.getLocalPort()),
|
||||
"--file"
|
||||
};
|
||||
for (int i=0; i<args.length; i++) {
|
||||
commandLine.getParametersList().addAt(i, args[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final XDebugSession session = XDebuggerManager.getInstance(project).
|
||||
startSession(this, env, contentToReuse, new XDebugProcessStarter() {
|
||||
@NotNull
|
||||
public XDebugProcess start(@NotNull final XDebugSession session) {
|
||||
return new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler());
|
||||
}
|
||||
});
|
||||
return session.getRunContentDescriptor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.xdebugger.frame.XCompositeNode;
|
||||
import com.intellij.xdebugger.frame.XValue;
|
||||
import com.intellij.xdebugger.frame.XValueNode;
|
||||
import com.intellij.xdebugger.ui.DebuggerIcons;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
// todo: extensive types support
|
||||
// todo: trim long values
|
||||
// todo: load long lists by parts
|
||||
public class PyDebugValue extends XValue {
|
||||
|
||||
private final String myName;
|
||||
private final String myType;
|
||||
private final String myValue;
|
||||
|
||||
public PyDebugValue(final String name, final String type, final String value) {
|
||||
myName = name;
|
||||
myType = type;
|
||||
myValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computePresentation(@NotNull XValueNode node) {
|
||||
node.setPresentation(myName, DebuggerIcons.VALUE_ICON, myType, getValuePresentation(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeChildren(@NotNull XCompositeNode node) {
|
||||
// todo: support from pydevd needed (?)
|
||||
super.computeChildren(node);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return myValue;
|
||||
}
|
||||
|
||||
private String getValuePresentation() {
|
||||
String presentation;
|
||||
if ("str".equals(myType) || "unicode".equals(myType)) {
|
||||
presentation = "\"" + myValue + "\"";
|
||||
}
|
||||
else {
|
||||
presentation = myValue;
|
||||
}
|
||||
|
||||
return presentation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.psi.impl.PyExpressionCodeFragmentImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
public class PyDebuggerEditorsProvider extends XDebuggerEditorsProvider {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FileType getFileType() {
|
||||
return PythonFileType.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Document createDocument(@NotNull Project project, @NotNull String text, @Nullable XSourcePosition sourcePosition) {
|
||||
final PyExpressionCodeFragmentImpl fragment = new PyExpressionCodeFragmentImpl(project, "fragment.py", text, true);
|
||||
|
||||
/*
|
||||
final PsiElement element = getContextElement(project, sourcePosition);
|
||||
System.out.println("element:" + element);
|
||||
fragment.setContext(element);
|
||||
*/
|
||||
|
||||
// todo: bind to context
|
||||
return PsiDocumentManager.getInstance(project).getDocument(fragment);
|
||||
}
|
||||
|
||||
/*
|
||||
@Nullable
|
||||
private static PsiElement getContextElement(Project project, XSourcePosition sourcePosition) {
|
||||
if (sourcePosition != null) {
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(sourcePosition.getFile());
|
||||
final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
|
||||
if (psiFile != null) {
|
||||
int offset = sourcePosition.getOffset();
|
||||
final int lineEndOffset = document.getLineEndOffset(document.getLineNumber(offset));
|
||||
do {
|
||||
PsiElement element = psiFile.findElementAt(offset);
|
||||
if (element != null && !(element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
|
||||
PsiElement e = PsiTreeUtil.getParentOfType(element, PyElement.class);
|
||||
while (e != null) {
|
||||
if (e instanceof PyPrintStatement) {
|
||||
return e;
|
||||
}
|
||||
e = e.getParent();
|
||||
}
|
||||
//return RControlFlowBuilder.getControlFlowNodeElement(element);
|
||||
}
|
||||
offset = element.getTextRange().getEndOffset() + 1;
|
||||
}
|
||||
while (offset < lineEndOffset);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
public class PyDebuggerEvaluator extends XDebuggerEvaluator {
|
||||
|
||||
private final PyDebugProcess myDebugProcess;
|
||||
|
||||
public PyDebuggerEvaluator(@NotNull final PyDebugProcess debugProcess) {
|
||||
myDebugProcess = debugProcess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(@NotNull String expression, XEvaluationCallback callback) {
|
||||
// todo: parse expression and use either EVAL or EXEC (add parameter to evaluate)
|
||||
// todo: think on getting results from EXEC
|
||||
try {
|
||||
final PyDebugValue value = myDebugProcess.evaluate(expression);
|
||||
callback.evaluated(value);
|
||||
}
|
||||
catch (Exception e) {
|
||||
callback.errorOccurred("Unable to evaluate \"" + expression + "\": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextRange getExpressionRangeAtOffset(Project project, Document document, int offset) {
|
||||
final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
|
||||
if (psiFile != null) {
|
||||
PsiElement element = psiFile.findElementAt(offset);
|
||||
if (!(element instanceof PyExpression)) {
|
||||
element = PsiTreeUtil.getParentOfType(element, PyExpression.class);
|
||||
}
|
||||
if (element != null && isSimpleEnough(element)) {
|
||||
return element.getTextRange();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSimpleEnough(@Nullable PsiElement element) {
|
||||
return element instanceof PyLiteralExpression || element instanceof PyQualifiedExpression ||
|
||||
element instanceof PyCallExpression || element instanceof PyBinaryExpression ||
|
||||
element instanceof PyPrefixExpression || element instanceof PySliceExpression;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
|
||||
public class PyDebuggerException extends Exception {
|
||||
|
||||
public PyDebuggerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PyDebuggerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.xdebugger.frame.XExecutionStack;
|
||||
import com.intellij.xdebugger.frame.XStackFrame;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class PyExecutionStack extends XExecutionStack {
|
||||
|
||||
private final PyDebugProcess myDebugProcess;
|
||||
private final PyThreadInfo myThreadInfo;
|
||||
|
||||
public PyExecutionStack(@NotNull final PyDebugProcess debugProcess, @NotNull final PyThreadInfo threadInfo) {
|
||||
super(threadInfo.getName());
|
||||
myDebugProcess = debugProcess;
|
||||
myThreadInfo = threadInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XStackFrame getTopFrame() {
|
||||
final List<PyStackFrameInfo> frames = myThreadInfo.getFrames();
|
||||
return frames != null ? convert(myDebugProcess, frames.get(0)) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeStackFrames(int firstFrameIndex, XStackFrameContainer container) {
|
||||
if (myThreadInfo.getState() != PyThreadInfo.State.SUSPENDED) {
|
||||
container.errorOccured("Frames not available in non-suspended state");
|
||||
return;
|
||||
}
|
||||
|
||||
final List<PyStackFrameInfo> frames = myThreadInfo.getFrames();
|
||||
if (frames != null && firstFrameIndex <= frames.size()) {
|
||||
final List<PyStackFrame> xFrames = new LinkedList<PyStackFrame>();
|
||||
for (int i = firstFrameIndex; i < frames.size(); i++) {
|
||||
xFrames.add(convert(myDebugProcess, frames.get(i)));
|
||||
}
|
||||
container.addStackFrames(xFrames, true);
|
||||
}
|
||||
else {
|
||||
container.addStackFrames(Collections.<XStackFrame>emptyList(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static PyStackFrame convert(final PyDebugProcess debugProcess, final PyStackFrameInfo frameInfo) {
|
||||
return new PyStackFrame(debugProcess, frameInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties;
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class PyLineBreakpointHandler extends XBreakpointHandler<XLineBreakpoint<XBreakpointProperties>> {
|
||||
|
||||
private final PyDebugProcess myDebugProcess;
|
||||
|
||||
public PyLineBreakpointHandler(@NotNull final PyDebugProcess debugProcess) {
|
||||
super(PyLineBreakpointType.class);
|
||||
myDebugProcess = debugProcess;
|
||||
}
|
||||
|
||||
public void registerBreakpoint(@NotNull XLineBreakpoint<XBreakpointProperties> breakpoint) {
|
||||
final XSourcePosition position = breakpoint.getSourcePosition();
|
||||
if (position != null) {
|
||||
myDebugProcess.addBreakpoint(myDebugProcess.getPositionConverter().convert(position), breakpoint);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterBreakpoint(@NotNull XLineBreakpoint<XBreakpointProperties> breakpoint, boolean temporary) {
|
||||
final XSourcePosition position = breakpoint.getSourcePosition();
|
||||
if (position != null) {
|
||||
myDebugProcess.removeBreakpoint(myDebugProcess.getPositionConverter().convert(position));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.xdebugger.XDebuggerUtil;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties;
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
public class PyLineBreakpointType extends XLineBreakpointType<XBreakpointProperties> {
|
||||
|
||||
public PyLineBreakpointType() {
|
||||
super("python-line", "Python Line Breakpoint");
|
||||
}
|
||||
|
||||
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
||||
final Ref<Boolean> stoppable = Ref.create(false);
|
||||
if (file.getFileType() == PythonFileType.INSTANCE) {
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(file);
|
||||
if (document != null) {
|
||||
XDebuggerUtil.getInstance().iterateLine(project, document, line, new Processor<PsiElement>() {
|
||||
public boolean process(PsiElement psiElement) {
|
||||
if (psiElement instanceof PsiWhiteSpace || psiElement instanceof PsiComment) return true;
|
||||
// Python debugger seems to be able to stop on pretty much everything
|
||||
stoppable.set(true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return stoppable.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public XBreakpointProperties createBreakpointProperties(@NotNull VirtualFile file, int line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
public interface PyPositionConverter {
|
||||
|
||||
@NotNull
|
||||
PySourcePosition create(@NotNull final String file, final int line);
|
||||
|
||||
@NotNull
|
||||
PySourcePosition convert(@NotNull final XSourcePosition position);
|
||||
|
||||
@Nullable
|
||||
XSourcePosition convert(@NotNull final PySourcePosition position);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
public abstract class PySourcePosition {
|
||||
|
||||
private final String file;
|
||||
private final int line;
|
||||
|
||||
protected PySourcePosition(final String file, final int line) {
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
public String getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PySourcePosition)) return false;
|
||||
|
||||
PySourcePosition that = (PySourcePosition)o;
|
||||
|
||||
if (line != that.line) return false;
|
||||
if (file != null ? !file.equals(that.file) : that.file != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = file != null ? file.hashCode() : 0;
|
||||
result = 31 * result + line;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
|
||||
import com.intellij.xdebugger.frame.XCompositeNode;
|
||||
import com.intellij.xdebugger.frame.XStackFrame;
|
||||
import com.intellij.xdebugger.ui.DebuggerIcons;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class PyStackFrame extends XStackFrame {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyStackFrame");
|
||||
|
||||
private final PyDebugProcess myDebugProcess;
|
||||
private final PyStackFrameInfo myFrameInfo;
|
||||
private final XSourcePosition myPosition;
|
||||
|
||||
public PyStackFrame(@NotNull final PyDebugProcess debugProcess, @NotNull final PyStackFrameInfo frameInfo) {
|
||||
myDebugProcess = debugProcess;
|
||||
myFrameInfo = frameInfo;
|
||||
myPosition = myDebugProcess.getPositionConverter().convert(frameInfo.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public XSourcePosition getSourcePosition() {
|
||||
return myPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XDebuggerEvaluator getEvaluator() {
|
||||
return new PyDebuggerEvaluator(myDebugProcess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customizePresentation(SimpleColoredComponent component) {
|
||||
component.setIcon(DebuggerIcons.STACK_FRAME_ICON);
|
||||
|
||||
if (myPosition == null) {
|
||||
component.append("<frame not available>", SimpleTextAttributes.GRAY_ATTRIBUTES);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isExternal = true;
|
||||
final VirtualFile file = myPosition.getFile();
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(file);
|
||||
if (document != null) {
|
||||
final Project project = myDebugProcess.getSession().getProject();
|
||||
isExternal = !ProjectRootManager.getInstance(project).getFileIndex().isInContent(file);
|
||||
}
|
||||
|
||||
component.append(myFrameInfo.getName(), gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal));
|
||||
component.append(", ", gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal));
|
||||
component.append(myPosition.getFile().getName(), gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal));
|
||||
component.append(":", gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal));
|
||||
component.append(Integer.toString(myPosition.getLine() + 1), gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal));
|
||||
}
|
||||
|
||||
private static SimpleTextAttributes gray(SimpleTextAttributes attributes, boolean gray) {
|
||||
if (!gray) {
|
||||
return attributes;
|
||||
}
|
||||
else {
|
||||
return (attributes.getStyle() & SimpleTextAttributes.STYLE_ITALIC) != 0
|
||||
? SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES : SimpleTextAttributes.GRAYED_ATTRIBUTES;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeChildren(@NotNull final XCompositeNode node) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
final List<PyDebugValue> values = myDebugProcess.loadFrame();
|
||||
node.addChildren(values, true);
|
||||
// todo: globals (?)
|
||||
}
|
||||
catch (PyDebuggerException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getThreadId() {
|
||||
return myFrameInfo.getThreadId();
|
||||
}
|
||||
|
||||
public String getFrameId() {
|
||||
return myFrameInfo.getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
|
||||
public class PyStackFrameInfo {
|
||||
|
||||
private final String myThreadId;
|
||||
private final String myId;
|
||||
private final String myName;
|
||||
private final PySourcePosition myPosition;
|
||||
|
||||
public PyStackFrameInfo(final String threadId, final String id, final String name, final PySourcePosition position) {
|
||||
myThreadId = threadId;
|
||||
myId = id;
|
||||
myName = name;
|
||||
myPosition = position;
|
||||
}
|
||||
|
||||
public String getThreadId() {
|
||||
return myThreadId;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
public PySourcePosition getPosition() {
|
||||
return myPosition;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.intellij.xdebugger.frame.XExecutionStack;
|
||||
import com.intellij.xdebugger.frame.XSuspendContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
// todo: support for multiple stacks (threads)
|
||||
public class PySuspendContext extends XSuspendContext {
|
||||
|
||||
private final XExecutionStack myActiveStack;
|
||||
|
||||
public PySuspendContext(@NotNull final PyDebugProcess debugProcess, @NotNull final PyThreadInfo threadInfo) {
|
||||
myActiveStack = new PyExecutionStack(debugProcess, threadInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XExecutionStack getActiveExecutionStack() {
|
||||
return myActiveStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XExecutionStack[] getExecutionStacks() {
|
||||
return super.getExecutionStacks();
|
||||
/*
|
||||
final Collection<PyThreadInfo> threads = myDebugProcess.getThreads();
|
||||
if (threads.size() < 1) {
|
||||
return XExecutionStack.EMPTY_ARRAY;
|
||||
}
|
||||
else {
|
||||
XExecutionStack[] stacks = new XExecutionStack[threads.size()];
|
||||
int i = 0;
|
||||
for (PyThreadInfo thread : threads) {
|
||||
stacks[i++] = new PyExecutionStack(myDebugProcess, thread);
|
||||
}
|
||||
return stacks;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class PyThreadInfo {
|
||||
|
||||
public enum State {
|
||||
RUNNING, SUSPENDED, KILLED
|
||||
}
|
||||
|
||||
private final String myId;
|
||||
private final String myName;
|
||||
private List<PyStackFrameInfo> myFrames;
|
||||
private State myState;
|
||||
private final boolean myStopOnBreakpoint; // todo: remove
|
||||
|
||||
public PyThreadInfo(final String id, final String name, final List<PyStackFrameInfo> frames, final boolean stopOnBreakpoint) {
|
||||
myId = id;
|
||||
myName = name;
|
||||
myFrames = (frames != null && frames.size() > 0 ? Collections.unmodifiableList(frames) : null);
|
||||
myStopOnBreakpoint = stopOnBreakpoint;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized List<PyStackFrameInfo> getFrames() {
|
||||
return myFrames;
|
||||
}
|
||||
|
||||
public synchronized State getState() {
|
||||
return myState;
|
||||
}
|
||||
|
||||
public synchronized void updateState(final State state, final List<PyStackFrameInfo> frames) {
|
||||
myState = state;
|
||||
myFrames = (frames != null && frames.size() > 0 ? Collections.unmodifiableList(frames) : null);
|
||||
}
|
||||
|
||||
public boolean isStopOnBreakpoint() {
|
||||
return myStopOnBreakpoint;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -87,10 +87,10 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static List<LocalQuickFix> proposeImportFixes(final PyElement node, String ref_text) {
|
||||
static Collection<LocalQuickFix> proposeImportFixes(final PyElement node, String ref_text) {
|
||||
PsiFile exisitng_import_file = null; // if there's a matching existing import, this it the file it imports
|
||||
ImportFromExistingFix fix = null;
|
||||
List<LocalQuickFix> fixes = new ArrayList<LocalQuickFix>(2);
|
||||
Collection<LocalQuickFix> fixes = new HashSet<LocalQuickFix>(2);
|
||||
Set<String> seen_file_names = new HashSet<String>(); // true import names
|
||||
// maybe the name is importable via some existing 'import foo' statement, and only needs a qualifier.
|
||||
// walk up collecting all such statements and analyzing
|
||||
@@ -279,10 +279,10 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
}
|
||||
// look in other imported modules for this whole name
|
||||
if (! ref_in_import) {
|
||||
List<LocalQuickFix> import_fixes = proposeImportFixes(node, ref_text);
|
||||
Collection<LocalQuickFix> import_fixes = proposeImportFixes(node, ref_text);
|
||||
if (import_fixes.size() > 0) {
|
||||
actions.addAll(import_fixes);
|
||||
Object first_action = import_fixes.get(0);
|
||||
Object first_action = import_fixes.iterator().next();
|
||||
if (first_action instanceof HintAction) {
|
||||
hint_action = ((HintAction)first_action);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
|
||||
public interface PyExpressionCodeFragment extends PyFile {
|
||||
}
|
||||
@@ -36,5 +36,8 @@ public interface PyFile extends PyElement, PsiFile, PyDocStringOwner {
|
||||
@return an URL of file, maybe bogus if virtual file is not present.
|
||||
*/
|
||||
@NotNull
|
||||
String getUrl();
|
||||
String getUrl();
|
||||
|
||||
@Nullable
|
||||
PyFunction findTopLevelFunction(String name);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.FileViewProvider;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.SingleRootFileViewProvider;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
import com.intellij.psi.impl.file.impl.FileManager;
|
||||
import com.intellij.psi.impl.source.tree.FileElement;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
import com.jetbrains.python.psi.PyExpressionCodeFragment;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* clone of JSExpressionCodeFragment
|
||||
*/
|
||||
public class PyExpressionCodeFragmentImpl extends PyFileImpl implements PyExpressionCodeFragment {
|
||||
|
||||
private PsiElement myContext;
|
||||
private boolean myPhysical;
|
||||
private FileViewProvider myViewProvider;
|
||||
|
||||
public PyExpressionCodeFragmentImpl(Project project, @NonNls String name, CharSequence text, boolean isPhysical) {
|
||||
super(((PsiManagerEx)PsiManager.getInstance(project)).getFileManager().createFileViewProvider(
|
||||
new LightVirtualFile(name, FileTypeManager.getInstance().getFileTypeByFileName(name), text), isPhysical)
|
||||
);
|
||||
myPhysical = isPhysical;
|
||||
((SingleRootFileViewProvider)getViewProvider()).forceCachedPsi(this);
|
||||
}
|
||||
|
||||
protected PyExpressionCodeFragmentImpl clone() {
|
||||
final PyExpressionCodeFragmentImpl clone = (PyExpressionCodeFragmentImpl)cloneImpl((FileElement)calcTreeElement().clone());
|
||||
clone.myPhysical = false;
|
||||
clone.myOriginalFile = this;
|
||||
FileManager fileManager = ((PsiManagerEx)getManager()).getFileManager();
|
||||
SingleRootFileViewProvider cloneViewProvider = (SingleRootFileViewProvider)fileManager.createFileViewProvider(new LightVirtualFile(getName(), getLanguage(), getText()), false);
|
||||
cloneViewProvider.forceCachedPsi(clone);
|
||||
clone.myViewProvider = cloneViewProvider;
|
||||
return clone;
|
||||
}
|
||||
|
||||
public PsiElement getContext() {
|
||||
return myContext;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FileViewProvider getViewProvider() {
|
||||
if(myViewProvider != null) return myViewProvider;
|
||||
return super.getViewProvider();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
if (!super.isValid()) return false;
|
||||
if (myContext != null && !myContext.isValid()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isPhysical() {
|
||||
return myPhysical;
|
||||
}
|
||||
|
||||
public void setContext(PsiElement context) {
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,6 +68,16 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
|
||||
return fname;
|
||||
}
|
||||
|
||||
public PyFunction findTopLevelFunction(String name) {
|
||||
final List<PyFunction> functions = getTopLevelFunctions();
|
||||
for (PyFunction function : functions) {
|
||||
if (name.equals(function.getName())) {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Icon getIcon(int flags) {
|
||||
return PythonFileType.INSTANCE.getIcon();
|
||||
}
|
||||
|
||||
@@ -488,13 +488,13 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
if (from_import_stmt != null) {
|
||||
final PyReferenceExpression import_src = from_import_stmt.getImportSource();
|
||||
if (import_src != null) {
|
||||
processor.setNotice(" | " + import_src.getName());
|
||||
processor.setNotice(import_src.getName());
|
||||
PyResolveUtil.treeCrawlUp(processor, true, import_src.resolve()); // names from that module
|
||||
}
|
||||
}
|
||||
}
|
||||
// include builtin names
|
||||
processor.setNotice(" | __builtin__");
|
||||
processor.setNotice("__builtin__");
|
||||
PyResolveUtil.treeCrawlUp(processor, true, PyBuiltinCache.getInstance(this).getBuiltinsFile()); // names from __builtin__
|
||||
|
||||
// if we're a normal module, add module's attrs
|
||||
|
||||
@@ -41,7 +41,7 @@ public class VariantsProcessor implements PsiScopeProcessor {
|
||||
}
|
||||
|
||||
protected static LookupElementBuilder setItemNotice(final LookupElementBuilder item, String notice) {
|
||||
return item.setTailText(notice);
|
||||
return item.setTypeText(notice);
|
||||
}
|
||||
|
||||
public LookupElement[] getResult() {
|
||||
@@ -86,7 +86,7 @@ public class VariantsProcessor implements PsiScopeProcessor {
|
||||
final PyFromImportStatement from_import = (PyFromImportStatement)maybe_from_import;
|
||||
PyReferenceExpression src = from_import.getImportSource();
|
||||
if (src != null) {
|
||||
lookup_item = setItemNotice(lookup_item, " | " + src.getName());
|
||||
lookup_item = setItemNotice(lookup_item, src.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,29 +7,36 @@ import com.jetbrains.python.lexer.PythonLexer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: Alexey.Ivanov
|
||||
* Date: Aug 19, 2009
|
||||
* Time: 10:23:26 PM
|
||||
* @author Alexey.Ivanov
|
||||
*/
|
||||
public class PythonNamesValidator implements NamesValidator {
|
||||
private static final PythonLexer ourLexer = new PythonLexer();
|
||||
|
||||
public synchronized boolean isKeyword(@NotNull final String name, final Project project) {
|
||||
ourLexer.start(name);
|
||||
if (!PyTokenTypes.KEYWORDS.contains(ourLexer.getTokenType())) {
|
||||
try {
|
||||
ourLexer.start(name);
|
||||
if (!PyTokenTypes.KEYWORDS.contains(ourLexer.getTokenType())) {
|
||||
return false;
|
||||
}
|
||||
ourLexer.advance();
|
||||
return ourLexer.getTokenType() == null;
|
||||
}
|
||||
catch (StringIndexOutOfBoundsException e) {
|
||||
return false;
|
||||
}
|
||||
ourLexer.advance();
|
||||
return ourLexer.getTokenType() == null;
|
||||
}
|
||||
|
||||
public synchronized boolean isIdentifier(@NotNull final String name, final Project project) {
|
||||
ourLexer.start(name);
|
||||
if (ourLexer.getTokenType() != PyTokenTypes.IDENTIFIER) {
|
||||
try {
|
||||
ourLexer.start(name);
|
||||
if (ourLexer.getTokenType() != PyTokenTypes.IDENTIFIER) {
|
||||
return false;
|
||||
}
|
||||
ourLexer.advance();
|
||||
return ourLexer.getTokenType() == null;
|
||||
}
|
||||
catch (StringIndexOutOfBoundsException e) {
|
||||
return false;
|
||||
}
|
||||
ourLexer.advance();
|
||||
return ourLexer.getTokenType() == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.jetbrains.python.run;
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface CommandLinePatcher {
|
||||
void patchCommandLine(GeneralCommandLine commandLine);
|
||||
}
|
||||
@@ -40,7 +40,11 @@ public class PythonCommandLineState extends CommandLineState {
|
||||
|
||||
@Override
|
||||
public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
|
||||
final ProcessHandler processHandler = startProcess();
|
||||
return execute(null);
|
||||
}
|
||||
|
||||
public ExecutionResult execute(CommandLinePatcher patcher) throws ExecutionException {
|
||||
final ProcessHandler processHandler = startProcess(patcher);
|
||||
final ConsoleView console = createAndAttachConsole(getConfig().getProject(), processHandler);
|
||||
|
||||
return new DefaultExecutionResult(console, processHandler, createActions(console, processHandler));
|
||||
@@ -59,7 +63,14 @@ public class PythonCommandLineState extends CommandLineState {
|
||||
}
|
||||
|
||||
protected OSProcessHandler startProcess() throws ExecutionException {
|
||||
return startProcess(null);
|
||||
}
|
||||
|
||||
protected OSProcessHandler startProcess(CommandLinePatcher patcher) throws ExecutionException {
|
||||
GeneralCommandLine commandLine = generateCommandLine();
|
||||
if (patcher != null) {
|
||||
patcher.patchCommandLine(commandLine);
|
||||
}
|
||||
|
||||
final OSProcessHandler processHandler = new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString());
|
||||
ProcessTerminatedListener.attach(processHandler);
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.PythonHelpersLocator;
|
||||
import com.jetbrains.python.facet.PythonFacetSettings;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -34,7 +35,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
@@ -351,57 +354,29 @@ public class PythonSdkType extends SdkType {
|
||||
return getPythonBinaryPath(sdkHome).getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a number of resources as files to a temporary directory.
|
||||
* @param resourceNames each file created will have the same name as the resource it's created from.
|
||||
* @return the temporary directory
|
||||
* @throws IOException if anything goes wrong.
|
||||
*/
|
||||
private static File copyResourcesToTempDir(final String... resourceNames) throws IOException {
|
||||
final File tempdir = FileUtil.createTempDirectory("pycharm", "");
|
||||
for (final String resourceName : resourceNames) {
|
||||
final String text = FileUtil.loadTextAndClose(new InputStreamReader(PythonSdkType.class.getResourceAsStream(resourceName)));
|
||||
File target = new File(tempdir.getCanonicalPath(), resourceName);
|
||||
FileWriter out = new FileWriter(target);
|
||||
out.write(text);
|
||||
out.close();
|
||||
}
|
||||
return tempdir;
|
||||
}
|
||||
|
||||
|
||||
private final static String GENERATOR3 = "generator3.py";
|
||||
private final static String FIND_BINARIES = "find_binaries.py";
|
||||
|
||||
public static void generateBuiltinStubs(String sdkPath, final String stubsRoot) {
|
||||
new File(stubsRoot).mkdirs();
|
||||
|
||||
GeneralCommandLine commandLine = new GeneralCommandLine();
|
||||
commandLine.setExePath(getInterpreterPath(sdkPath)); // python
|
||||
commandLine.addParameter(PythonHelpersLocator.getHelperPath(GENERATOR3));
|
||||
|
||||
commandLine.addParameter("-d");
|
||||
commandLine.addParameter(stubsRoot); // -d stubs_root
|
||||
commandLine.addParameter("-b"); // for builtins
|
||||
commandLine.addParameter("-u"); // for update-only mode
|
||||
try {
|
||||
|
||||
final File tempDir = copyResourcesToTempDir(GENERATOR3, "pyparsing.py", "pyparsing_py3.py");
|
||||
|
||||
GeneralCommandLine commandLine = new GeneralCommandLine();
|
||||
commandLine.setExePath(getInterpreterPath(sdkPath)); // python
|
||||
|
||||
commandLine.addParameter(tempDir.getAbsolutePath() + File.separatorChar + GENERATOR3);
|
||||
|
||||
commandLine.addParameter("-d"); commandLine.addParameter(stubsRoot); // -d stubs_root
|
||||
commandLine.addParameter("-b"); // for builtins
|
||||
commandLine.addParameter("-u"); // for update-only mode
|
||||
try {
|
||||
final OSProcessHandler handler = new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString());
|
||||
handler.startNotify();
|
||||
handler.waitFor();
|
||||
handler.destroyProcess();
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
FileUtil.delete(tempDir);
|
||||
final OSProcessHandler handler = new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString());
|
||||
handler.startNotify();
|
||||
handler.waitFor();
|
||||
handler.destroyProcess();
|
||||
}
|
||||
catch (IOException e) {
|
||||
catch (ExecutionException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,64 +387,50 @@ public class PythonSdkType extends SdkType {
|
||||
* @param indicator ProgressIndicator to update, or null.
|
||||
*/
|
||||
public static void generateBinaryStubs(final String sdkPath, final String stubsRoot, ProgressIndicator indicator) {
|
||||
if (!new File(stubsRoot).exists()) return;
|
||||
if (!new File(stubsRoot).exists()) return;
|
||||
if (indicator != null) {
|
||||
indicator.setText("Generating skeletons of binary libs");
|
||||
}
|
||||
try {
|
||||
final int RUN_TIMEOUT = 10*1000; // 10 seconds per call is plenty enough; anything more is clearly wrong.
|
||||
final String bin_path = getInterpreterPath(sdkPath);
|
||||
final int RUN_TIMEOUT = 10 * 1000; // 10 seconds per call is plenty enough; anything more is clearly wrong.
|
||||
final String bin_path = getInterpreterPath(sdkPath);
|
||||
|
||||
final File tempDir = copyResourcesToTempDir(GENERATOR3, FIND_BINARIES, "pyparsing.py", "pyparsing_py3.py");
|
||||
final ProcessOutput run_result =
|
||||
SdkUtil.getProcessOutput(sdkPath, new String[]{bin_path, PythonHelpersLocator.getHelperPath(FIND_BINARIES)});
|
||||
|
||||
|
||||
try {
|
||||
final ProcessOutput run_result = SdkUtil.getProcessOutput(
|
||||
sdkPath, new String[] {bin_path, tempDir.getPath() + File.separatorChar + FIND_BINARIES}
|
||||
);
|
||||
|
||||
if (run_result.getExitCode() == 0) {
|
||||
for (String line : run_result.getStdoutLines()) {
|
||||
// line = "mod_name path"
|
||||
int cutpos = line.indexOf(' ');
|
||||
String modname = line.substring(0, cutpos);
|
||||
String mod_fname = modname.replace(".", File.separator); // "a.b.c" -> "a/b/c", no ext
|
||||
String fname = line.substring(cutpos+1);
|
||||
//String ext = fname.substring(fname.lastIndexOf('.')); // no way ext is absent
|
||||
// check if it's fresh
|
||||
File f_orig = new File(fname);
|
||||
File f_skel = new File(stubsRoot + File.separator + mod_fname + ".py");
|
||||
if (f_orig.lastModified() >= f_skel.lastModified()) {
|
||||
// stale skeleton, rebuild
|
||||
if (indicator != null) {
|
||||
indicator.setText2(modname);
|
||||
}
|
||||
LOG.info("Skeleton for " + modname);
|
||||
final ProcessOutput gen_result = SdkUtil.getProcessOutput(sdkPath,
|
||||
new String[] {bin_path, tempDir.getPath() + File.separatorChar + GENERATOR3, "-d", stubsRoot, modname}, RUN_TIMEOUT
|
||||
);
|
||||
if (gen_result.getExitCode() != 0) {
|
||||
StringBuffer sb = new StringBuffer("Skeleton for ");
|
||||
sb.append(modname).append(" failed. stderr: --");
|
||||
for (String err_line : gen_result.getStderrLines()) sb.append(err_line).append("\n");
|
||||
sb.append("--");
|
||||
LOG.warn(sb.toString());
|
||||
}
|
||||
}
|
||||
if (run_result.getExitCode() == 0) {
|
||||
for (String line : run_result.getStdoutLines()) {
|
||||
// line = "mod_name path"
|
||||
int cutpos = line.indexOf(' ');
|
||||
String modname = line.substring(0, cutpos);
|
||||
String mod_fname = modname.replace(".", File.separator); // "a.b.c" -> "a/b/c", no ext
|
||||
String fname = line.substring(cutpos + 1);
|
||||
//String ext = fname.substring(fname.lastIndexOf('.')); // no way ext is absent
|
||||
// check if it's fresh
|
||||
File f_orig = new File(fname);
|
||||
File f_skel = new File(stubsRoot + File.separator + mod_fname + ".py");
|
||||
if (f_orig.lastModified() >= f_skel.lastModified()) {
|
||||
// stale skeleton, rebuild
|
||||
if (indicator != null) {
|
||||
indicator.setText2(modname);
|
||||
}
|
||||
LOG.info("Skeleton for " + modname);
|
||||
final ProcessOutput gen_result = SdkUtil
|
||||
.getProcessOutput(sdkPath, new String[]{bin_path, PythonHelpersLocator.getHelperPath(GENERATOR3), "-d", stubsRoot, modname},
|
||||
RUN_TIMEOUT);
|
||||
if (gen_result.getExitCode() != 0) {
|
||||
StringBuffer sb = new StringBuffer("Skeleton for ");
|
||||
sb.append(modname).append(" failed. stderr: --");
|
||||
for (String err_line : gen_result.getStderrLines()) sb.append(err_line).append("\n");
|
||||
sb.append("--");
|
||||
LOG.warn(sb.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (String err_line : run_result.getStderrLines()) sb.append(err_line).append("\n");
|
||||
LOG.error("failed to run " + FIND_BINARIES + ", exit code " + run_result.getExitCode() + ", stderr '" + sb.toString() + "'");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.delete(tempDir);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e);
|
||||
else {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (String err_line : run_result.getStderrLines()) sb.append(err_line).append("\n");
|
||||
LOG.error("failed to run " + FIND_BINARIES + ", exit code " + run_result.getExitCode() + ", stderr '" + sb.toString() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def foo(uno, dos, tres):
|
||||
def foo(auno, dos, tres):
|
||||
pass
|
||||
|
||||
foo(uno=)
|
||||
foo(auno=)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def foo(uno, dos, tres):
|
||||
def foo(auno, dos, tres):
|
||||
pass
|
||||
|
||||
foo(un<caret>)
|
||||
foo(aun<caret>)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def foo(boo1, *boo2, **boo3):
|
||||
def foo(xboo1, *boo2, **boo3):
|
||||
pass
|
||||
|
||||
foo(boo1=)
|
||||
foo(xboo1=)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def foo(boo1, *boo2, **boo3):
|
||||
def foo(xboo1, *boo2, **boo3):
|
||||
pass
|
||||
|
||||
foo(boo<caret>)
|
||||
foo(xboo<caret>)
|
||||
|
||||
@@ -14,4 +14,4 @@ class <info descr="null" type="INFORMATION" foreground="0x0000ff" background="0x
|
||||
pass
|
||||
|
||||
def <info descr="null" type="INFORMATION" foreground="0xff0000" background="0x000000" effectcolor="0xffffff" effecttype="BOXED" fonttype="1">__made_up__</info>(self):
|
||||
return None
|
||||
return <info type="INFORMATION">None</info>
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
def <info descr="null" type="INFORMATION" foreground="0xff0000" background="0x000000" effectcolor="0xffffff" effecttype="BOXED" fonttype="1">foo</info>():
|
||||
def <info descr="null" type="INFORMATION" foreground="0xff0000" background="0x000000" effectcolor="0xffffff" effecttype="BOXED" fonttype="1">a</info>():
|
||||
yield 1
|
||||
return False
|
||||
return <info type="INFORMATION">False</info>
|
||||
|
||||
@@ -5,4 +5,4 @@ class A:
|
||||
|
||||
class B(A):
|
||||
def foo(self):
|
||||
return self.<warning descr="Unresolved attribute reference 'x' for class 'B'">x</warning>
|
||||
return self.<caret><warning descr="Unresolved attribute reference 'x' for class 'B'">x</warning>
|
||||
|
||||
@@ -4,4 +4,4 @@ class A:
|
||||
|
||||
|
||||
a = A()
|
||||
a.<warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>+1
|
||||
a.<caret><warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>+1
|
||||
|
||||
@@ -3,4 +3,4 @@ class A:
|
||||
self.x = 1
|
||||
|
||||
def foo(self):
|
||||
a = self.<warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>
|
||||
a = self.<caret><warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"Doc comment stays intact"
|
||||
<warning descr="Unresolved reference 'AddImportDoc'">AddImportDoc</warning>.foo
|
||||
<caret><warning descr="Unresolved reference 'AddImportDoc'">AddImportDoc</warning>.foo
|
||||
|
||||
foo = 1
|
||||
|
||||
@@ -4,4 +4,4 @@ class A:
|
||||
|
||||
|
||||
a = A()
|
||||
a.<warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>()
|
||||
a.<caret><warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>()
|
||||
|
||||
@@ -3,4 +3,4 @@ class A:
|
||||
self.x = 1
|
||||
|
||||
def foo(self, a):
|
||||
self.<warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>(1, a)
|
||||
self.<caret><warning descr="Unresolved attribute reference 'y' for class 'A'">y</warning>(1, a)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
class A:
|
||||
def foo<error descr="Method must have a first parameter, usually called 'self'">()</error>: # Add 'self'
|
||||
def foo<caret><error descr="Method must have a first parameter, usually called 'self'">()</error>: # Add 'self'
|
||||
pass
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from AddToImportFromFoo import bar
|
||||
|
||||
<warning descr="Unresolved reference 'foo'">foo</warning> # must get imported
|
||||
<caret><warning descr="Unresolved reference 'foo'">foo</warning> # must get imported
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import QualifyByImportFoo
|
||||
|
||||
<warning descr="Unresolved reference 'foo'">foo</warning> # must be qualified
|
||||
<caret><warning descr="Unresolved reference 'foo'">foo</warning> # must be qualified
|
||||
|
||||
@@ -1 +1 @@
|
||||
a = 4; b = 5<warning descr="Trailing semicolon in the statement">;</warning>
|
||||
a = 4; b = 5<caret><warning descr="Trailing semicolon in the statement">;</warning>
|
||||
@@ -1,3 +1,3 @@
|
||||
class A:
|
||||
def foo(slf): # Rename to 'self'
|
||||
def foo(<caret>slf): # Rename to 'self'
|
||||
pass
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.jetbrains.python;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.fixtures.LightMarkedTestCase;
|
||||
import com.jetbrains.python.psi.PyAssignmentStatement;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.PyTargetExpression;
|
||||
import com.jetbrains.python.psi.PySubscriptionExpression;
|
||||
import com.jetbrains.python.psi.PyTargetExpression;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -14,7 +16,7 @@ import java.util.Map;
|
||||
* User: dcheryasov
|
||||
* Date: Dec 11, 2009 2:13:51 AM
|
||||
*/
|
||||
public class PyAssignmentMappingTest extends MarkedTestCase {
|
||||
public class PyAssignmentMappingTest extends LightMarkedTestCase {
|
||||
|
||||
public String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/psi/assignment/";
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.jetbrains.python;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.testFramework.ResolveTestCase;
|
||||
import com.jetbrains.python.fixtures.PyResolveTestCase;
|
||||
import com.jetbrains.python.psi.PyArgumentList;
|
||||
import com.jetbrains.python.psi.PyDecorator;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
@@ -13,7 +13,7 @@ import com.jetbrains.python.psi.PyFunction;
|
||||
* User: dcheryasov
|
||||
* Date: Dec 28, 2008 3:50:23 AM
|
||||
*/
|
||||
public class PyDecoratorTest extends ResolveTestCase {
|
||||
public class PyDecoratorTest extends PyResolveTestCase {
|
||||
private PsiElement find() throws Exception {
|
||||
PsiReference ref = configureByFile(getTestName(false) + ".py");
|
||||
return ref.getElement();
|
||||
|
||||
@@ -1,185 +1,141 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.idea.Bombed;
|
||||
import com.intellij.openapi.actionSystem.IdeActions;
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
|
||||
import com.intellij.testFramework.LightCodeInsightTestCase;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PyIndentTest extends LightCodeInsightTestCase {
|
||||
private void doTest(String before, String after) throws Exception {
|
||||
final String name = getTestName(false);
|
||||
public class PyIndentTest extends PyLightFixtureTestCase {
|
||||
private void doTest(final String before, String after) throws Exception {
|
||||
final String name = getTestName(false);
|
||||
|
||||
configureFromFileText(name + ".py", before);
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
myFixture.configureByText(name + ".py", before);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
CommandProcessor.getInstance().executeCommand(myFixture.getProject(), new Runnable() {
|
||||
public void run() {
|
||||
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_ENTER);
|
||||
}
|
||||
}, "", null);
|
||||
String s = myFixture.getFile().getText();
|
||||
myFixture.checkResult(after);
|
||||
}
|
||||
|
||||
EditorActionManager actionManager = EditorActionManager.getInstance();
|
||||
EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_ENTER);
|
||||
public void testSimpleIndent() throws Exception {
|
||||
doTest("a=1<caret>", "a=1\n<caret>");
|
||||
}
|
||||
|
||||
actionHandler.execute(getEditor(), DataManager.getInstance().getDataContext());
|
||||
public void testIndentColon() throws Exception {
|
||||
doTest("if a:<caret>", "if a:\n <caret>");
|
||||
}
|
||||
|
||||
String s = myFile.getText();
|
||||
checkResultByText(null, after, false);
|
||||
}
|
||||
public void testIndentStatementList() throws Exception {
|
||||
doTest("if a:<caret>\n print a", "if a:\n <caret>\n print a");
|
||||
}
|
||||
|
||||
public void testSimpleIndent() throws Exception {
|
||||
doTest("a=1<caret>", "a=1\n<caret>");
|
||||
}
|
||||
public void testIndentStatementList2() throws Exception {
|
||||
doTest("while a:\n print a<caret>", "while a:\n print a\n <caret>");
|
||||
}
|
||||
|
||||
public void testIndentColon() throws Exception {
|
||||
doTest("if a:<caret>", "if a:\n <caret>");
|
||||
}
|
||||
public void testIndentStatementList3() throws Exception {
|
||||
doTest("if a:\n print a<caret>\n\nprint b", "if a:\n print a\n <caret>\n\nprint b");
|
||||
}
|
||||
|
||||
public void testIndentStatementList() throws Exception {
|
||||
doTest("if a:<caret>\n print a", "if a:\n <caret>\n print a");
|
||||
}
|
||||
public void testIndentOneLineStatementList() throws Exception {
|
||||
doTest("if a:\n if b: print c<caret>\n print d", "if a:\n if b: print c\n <caret>\n print d");
|
||||
}
|
||||
|
||||
public void testIndentStatementList2() throws Exception {
|
||||
doTest("while a:\n print a<caret>", "while a:\n print a\n <caret>");
|
||||
}
|
||||
public void testIndentOneLineStatementListBreak() throws Exception {
|
||||
doTest("if a:\n if b:<caret> print c\n print d", "if a:\n if b:\n <caret>print c\n print d");
|
||||
}
|
||||
|
||||
public void testIndentStatementList3() throws Exception {
|
||||
doTest("if a:\n print a<caret>\n\nprint b",
|
||||
"if a:\n print a\n <caret>\n\nprint b");
|
||||
}
|
||||
@Bombed(month = Calendar.FEBRUARY, day = 17, user = "yole")
|
||||
public void testAlignInList() throws Exception {
|
||||
doTest("__all__ = [a,<caret>", "__all__ = [a,\n" + " <caret>");
|
||||
}
|
||||
|
||||
public void testIndentOneLineStatementList() throws Exception {
|
||||
doTest("if a:\n if b: print c<caret>\n print d",
|
||||
"if a:\n if b: print c\n <caret>\n print d");
|
||||
}
|
||||
public void testAlignInListMiddle() throws Exception {
|
||||
doTest("__all__ = [a,<caret>\n" + " c]", "__all__ = [a,\n" + " <caret>\n" + " c]");
|
||||
}
|
||||
|
||||
public void testIndentOneLineStatementListBreak() throws Exception {
|
||||
doTest("if a:\n if b:<caret> print c\n print d",
|
||||
"if a:\n if b:\n <caret>print c\n print d");
|
||||
}
|
||||
public void testAlignInListMiddle2() throws Exception {
|
||||
doTest("__all__ = [a,\n" + " b,<caret>\n" + " c]",
|
||||
"__all__ = [a,\n" + " b,\n" + " <caret>\n" + " c]");
|
||||
}
|
||||
|
||||
@Bombed(month = Calendar.FEBRUARY, day = 17, user="yole")
|
||||
public void testAlignInList() throws Exception {
|
||||
doTest("__all__ = [a,<caret>",
|
||||
"__all__ = [a,\n" +
|
||||
" <caret>");
|
||||
}
|
||||
@Bombed(month = Calendar.FEBRUARY, day = 17, user = "yole")
|
||||
public void testAlignInListComp() throws Exception {
|
||||
doTest("__all__ = [a for<caret>", "__all__ = [a for\n" + " <caret>");
|
||||
}
|
||||
|
||||
public void testAlignInListMiddle() throws Exception {
|
||||
doTest("__all__ = [a,<caret>\n" +
|
||||
" c]",
|
||||
"__all__ = [a,\n" +
|
||||
" <caret>\n" +
|
||||
" c]");
|
||||
}
|
||||
public void testClass() throws Exception {
|
||||
doTest("class A:\n" + " print a<caret>", "class A:\n" + " print a\n" + " <caret>");
|
||||
}
|
||||
|
||||
public void testAlignInListMiddle2() throws Exception {
|
||||
doTest("__all__ = [a,\n" +
|
||||
" b,<caret>\n" +
|
||||
" c]",
|
||||
"__all__ = [a,\n" +
|
||||
" b,\n" +
|
||||
" <caret>\n" +
|
||||
" c]");
|
||||
}
|
||||
@Bombed(month = Calendar.FEBRUARY, day = 17, user="yole")
|
||||
public void testAlignInListComp() throws Exception {
|
||||
doTest("__all__ = [a for<caret>",
|
||||
"__all__ = [a for\n" +
|
||||
" <caret>");
|
||||
}
|
||||
public void testClass2() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" + " if a: print b\n" + " def getAvailableActions(self):<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" + " if a: print b\n" + " def getAvailableActions(self):\n" + " <caret>");
|
||||
}
|
||||
|
||||
public void testClass() throws Exception {
|
||||
doTest("class A:\n" +
|
||||
" print a<caret>",
|
||||
"class A:\n" +
|
||||
" print a\n" +
|
||||
" <caret>");
|
||||
}
|
||||
public void testClass2_1() throws Exception {
|
||||
doTest(
|
||||
"class CombatExpertiseFeat(Ability):\n" + " if a: print b\n" + " def getAvailableActions(self):<caret>\n" + "class C2: pass",
|
||||
|
||||
public void testClass2() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" +
|
||||
" if a: print b\n" +
|
||||
" def getAvailableActions(self):<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" if a: print b\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" <caret>");
|
||||
}
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" if a: print b\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" <caret>\n" +
|
||||
"class C2: pass");
|
||||
}
|
||||
|
||||
public void testClass2_1() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" +
|
||||
" if a: print b\n" +
|
||||
" def getAvailableActions(self):<caret>\n" +
|
||||
"class C2: pass",
|
||||
public void testMultiDedent() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" + " def getAvailableActions(self):\n" + " result = ArrayList()<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" result = ArrayList()\n" +
|
||||
" <caret>");
|
||||
}
|
||||
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" if a: print b\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" <caret>\n" +
|
||||
"class C2: pass");
|
||||
}
|
||||
public void testMultiDedent1() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" + " def getAvailableActions(self):\n" + " if a:<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" + " def getAvailableActions(self):\n" + " if a:\n" + " <caret>");
|
||||
}
|
||||
|
||||
public void testMultiDedent() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" result = ArrayList()<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" result = ArrayList()\n" +
|
||||
" <caret>");
|
||||
}
|
||||
public void testMultiDedent2() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" + " def getAvailableActions(self): result = ArrayList()<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" + " def getAvailableActions(self): result = ArrayList()\n" + " <caret>");
|
||||
}
|
||||
|
||||
public void testMultiDedent1() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" if a:<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self):\n" +
|
||||
" if a:\n" +
|
||||
" <caret>");
|
||||
}
|
||||
public void testIfElse() throws Exception {
|
||||
doTest("if a:<caret>\n" + " b\n" + "else:\n" + " c", "if a:\n" + " <caret>\n" + " b\n" + "else:\n" + " c");
|
||||
}
|
||||
|
||||
public void testMultiDedent2() throws Exception {
|
||||
doTest("class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self): result = ArrayList()<caret>",
|
||||
"class CombatExpertiseFeat(Ability):\n" +
|
||||
" def getAvailableActions(self): result = ArrayList()\n" +
|
||||
" <caret>");
|
||||
}
|
||||
public void testIfElse2() throws Exception {
|
||||
doTest("if a:\n" + " b\n" + "else:<caret>\n" + " c", "if a:\n" + " b\n" + "else:\n" + " <caret>\n" + " c");
|
||||
}
|
||||
|
||||
public void testIfElse() throws Exception {
|
||||
doTest("if a:<caret>\n" +
|
||||
" b\n" +
|
||||
"else:\n" +
|
||||
" c",
|
||||
"if a:\n" +
|
||||
" <caret>\n" +
|
||||
" b\n" +
|
||||
"else:\n" +
|
||||
" c");
|
||||
}
|
||||
|
||||
public void testIfElse2() throws Exception {
|
||||
doTest("if a:\n" +
|
||||
" b\n" +
|
||||
"else:<caret>\n" +
|
||||
" c",
|
||||
"if a:\n" +
|
||||
" b\n" +
|
||||
"else:\n" +
|
||||
" <caret>\n" +
|
||||
" c");
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: formatter core problem?
|
||||
public void testAlignListBeforeEquals() throws Exception {
|
||||
doTest("__all__ <caret>= [a,\n" +
|
||||
" b]",
|
||||
"__all__ \n" +
|
||||
"<caret>= [a,\n" +
|
||||
" b]");
|
||||
}
|
||||
*/
|
||||
/*
|
||||
TODO: formatter core problem?
|
||||
public void testAlignListBeforeEquals() throws Exception {
|
||||
doTest("__all__ <caret>= [a,\n" +
|
||||
" b]",
|
||||
"__all__ \n" +
|
||||
"<caret>= [a,\n" +
|
||||
" b]");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightTestCase;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
import com.intellij.psi.stubs.StubIndex;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
|
||||
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
|
||||
@@ -15,7 +14,7 @@ import java.util.Collection;
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PyInheritorsSearchTest extends CodeInsightTestCase {
|
||||
public class PyInheritorsSearchTest extends PyLightFixtureTestCase {
|
||||
public void testSimple() throws Exception {
|
||||
setupProject();
|
||||
final PyClass pyClass = findClass("A");
|
||||
@@ -39,16 +38,20 @@ public class PyInheritorsSearchTest extends CodeInsightTestCase {
|
||||
|
||||
private void setupProject() throws Exception {
|
||||
String testName = getTestName(true);
|
||||
String root = PythonTestUtil.getTestDataPath() + "/inheritors/" + testName;
|
||||
VirtualFile rootDir = PsiTestUtil.createTestProjectStructure(myProject, myModule, root, myFilesToDelete, false);
|
||||
PsiTestUtil.addSourceContentToRoots(myModule, rootDir);
|
||||
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
||||
myFixture.copyDirectoryToProject(testName, "");
|
||||
PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments();
|
||||
}
|
||||
|
||||
private PyClass findClass(final String name) {
|
||||
final Collection<PyClass> classes = StubIndex.getInstance().get(PyClassNameIndex.KEY, name, myProject,
|
||||
ProjectScope.getProjectScope(myProject));
|
||||
final Project project = myFixture.getProject();
|
||||
final Collection<PyClass> classes = StubIndex.getInstance().get(PyClassNameIndex.KEY, name, project,
|
||||
ProjectScope.getProjectScope(project));
|
||||
assertEquals(1, classes.size());
|
||||
return classes.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/inheritors/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightTestCase;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import com.jetbrains.python.psi.*;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PyMultiFileResolveTest extends CodeInsightTestCase {
|
||||
public class PyMultiFileResolveTest extends PyLightFixtureTestCase {
|
||||
public void testSimple() throws Exception {
|
||||
PsiElement element = doResolve();
|
||||
assertTrue(element instanceof PyFile);
|
||||
@@ -111,17 +110,20 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase {
|
||||
private PsiFile prepareFile() throws Exception {
|
||||
String testName = getTestName(true);
|
||||
String fileName = getTestName(false) + ".py";
|
||||
String root = PythonTestUtil.getTestDataPath() + "/resolve/multiFile/" + testName;
|
||||
VirtualFile rootDir = PsiTestUtil.createTestProjectStructure(myProject, myModule, root, myFilesToDelete, false);
|
||||
PsiTestUtil.addSourceContentToRoots(myModule, rootDir);
|
||||
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
||||
myFixture.copyDirectoryToProject(testName, "");
|
||||
PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments();
|
||||
|
||||
VirtualFile sourceFile = rootDir.findChild(fileName);
|
||||
VirtualFile sourceFile = myFixture.findFileInTempDir(fileName);
|
||||
assert sourceFile != null;
|
||||
PsiFile psiFile = myPsiManager.findFile(sourceFile);
|
||||
PsiFile psiFile = myFixture.getPsiManager().findFile(sourceFile);
|
||||
return psiFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/resolve/multiFile/";
|
||||
}
|
||||
|
||||
private PsiElement doResolve() throws Exception {
|
||||
PsiFile psiFile = prepareFile();
|
||||
int offset = findMarkerOffset(psiFile);
|
||||
@@ -137,7 +139,7 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase {
|
||||
}
|
||||
|
||||
private int findMarkerOffset(final PsiFile psiFile) {
|
||||
Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile);
|
||||
Document document = PsiDocumentManager.getInstance(myFixture.getProject()).getDocument(psiFile);
|
||||
assert document != null;
|
||||
int offset = -1;
|
||||
for (int i=1; i<document.getLineCount(); i++) {
|
||||
|
||||
@@ -4,12 +4,12 @@ import com.intellij.lang.parameterInfo.CreateParameterInfoContext;
|
||||
import com.intellij.lang.parameterInfo.ParameterInfoHandler;
|
||||
import com.intellij.lang.parameterInfo.ParameterInfoUIContextEx;
|
||||
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.jetbrains.python.fixtures.LightMarkedTestCase;
|
||||
import com.jetbrains.python.psi.PyArgumentList;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -25,7 +25,7 @@ import java.util.Set;
|
||||
* <br/>User: dcheryasov
|
||||
* Date: Jul 14, 2009 3:42:44 AM
|
||||
*/
|
||||
public class PyParameterInfoTest extends MarkedTestCase {
|
||||
public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath()+ "/paramInfo/";
|
||||
}
|
||||
@@ -247,7 +247,7 @@ public class PyParameterInfoTest extends MarkedTestCase {
|
||||
* @throws Exception if it fails
|
||||
*/
|
||||
private Collector feignCtrlP(int offset) throws Exception {
|
||||
Collector collector = new Collector(getProject(), getFile(), offset);
|
||||
Collector collector = new Collector(myFixture.getProject(), myFixture.getFile(), offset);
|
||||
PyParameterInfoHandler handler = new PyParameterInfoHandler();
|
||||
collector.setParameterOwner(handler.findElementForParameterInfo(collector)); // finds arglist, sets items to show
|
||||
if (collector.getParameterOwner() != null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.fixtures.LightMarkedTestCase;
|
||||
import com.jetbrains.python.psi.PyDocStringOwner;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
@@ -17,7 +18,7 @@ import java.util.Map;
|
||||
* User: dcheryasov
|
||||
* Date: Jun 7, 2009 12:31:07 PM
|
||||
*/
|
||||
public class PyQuickDocTest extends MarkedTestCase {
|
||||
public class PyQuickDocTest extends LightMarkedTestCase {
|
||||
private PythonDocumentationProvider myProvider;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.openapi.editor.markup.GutterIconRenderer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.SdkModificator;
|
||||
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import com.jetbrains.python.inspections.PyMethodParametersInspection;
|
||||
import com.jetbrains.python.inspections.PyTrailingSemicolonInspection;
|
||||
import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection;
|
||||
import gnu.trove.TIntObjectHashMap;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Test actions that various inspections add.
|
||||
* User: dcheryasov
|
||||
* Date: Nov 29, 2008 12:47:08 AM
|
||||
*/
|
||||
public class PyQuickFixTest extends DaemonAnalyzerTestCase {
|
||||
public class PyQuickFixTest extends PyLightFixtureTestCase {
|
||||
|
||||
public void testAddImport() throws Exception {
|
||||
doInspectionTest("AddImport.py", PyUnresolvedReferencesInspection.class, PyBundle.message("ACT.NAME.add.import"), true, true);
|
||||
@@ -106,20 +102,6 @@ public class PyQuickFixTest extends DaemonAnalyzerTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
protected VirtualFile[] loadFiles(String[] names) {
|
||||
VirtualFile[] ret = new VirtualFile[names.length];
|
||||
String prefix = getTestDataPath();
|
||||
for (int i=0; i < names.length; i += 1) {
|
||||
ret[i] = getVirtualFile(prefix+names[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected void doTest(VirtualFile[] vFile, boolean checkWarnings, boolean checkInfos) throws Exception {
|
||||
configureByFiles(null, vFile);
|
||||
doDoTest(checkWarnings, checkInfos);
|
||||
}
|
||||
|
||||
protected @NonNls String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/inspections/";
|
||||
}
|
||||
@@ -148,68 +130,32 @@ public class PyQuickFixTest extends DaemonAnalyzerTestCase {
|
||||
boolean applyFix,
|
||||
boolean available
|
||||
) throws Exception {
|
||||
final LocalInspectionTool tool = (LocalInspectionTool)inspectionClass.newInstance();
|
||||
enableInspectionTool(tool);
|
||||
configureByFiles(null, testFiles);
|
||||
Collection<HighlightInfo> infos = doDoTest(true, false);
|
||||
|
||||
doQuickFixTest(infos, quickFixName, applyFix, available, testFiles[0]);
|
||||
disableInspectionTool(tool.getShortName());
|
||||
}
|
||||
|
||||
protected void doQuickFixTest(Collection<HighlightInfo> infos,
|
||||
String quickFixName,
|
||||
boolean applyFix,
|
||||
boolean shouldBeAvailable,
|
||||
String s) throws Exception {
|
||||
final List<IntentionAction> availableActions = new ArrayList<IntentionAction>(1);
|
||||
|
||||
TIntObjectHashMap<HighlightInfo> map = new TIntObjectHashMap<HighlightInfo>(infos.size());
|
||||
for (HighlightInfo info : infos) {
|
||||
final GutterIconRenderer renderer = info.getGutterIconRenderer();
|
||||
if (renderer == null) {
|
||||
assertFalse("There should be one intention for highlight info", map.containsKey(info.startOffset));
|
||||
map.put(info.startOffset, info);
|
||||
|
||||
if (info.quickFixActionRanges != null) {
|
||||
for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair : info.quickFixActionRanges) {
|
||||
IntentionAction action = pair.first.getAction();
|
||||
availableActions.add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final IntentionAction intentionAction = LightQuickFixTestCase.findActionWithText(availableActions, quickFixName);
|
||||
if (shouldBeAvailable) {
|
||||
myFixture.enableInspections(inspectionClass);
|
||||
myFixture.configureByFiles(testFiles);
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
final IntentionAction intentionAction = myFixture.findSingleIntention(quickFixName);
|
||||
if (available) {
|
||||
assertNotNull(intentionAction);
|
||||
if (applyFix) {
|
||||
myFixture.launchAction(intentionAction);
|
||||
|
||||
myFixture.checkResultByFile(graftBeforeExt(testFiles [0], "_after"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
assertNull(intentionAction);
|
||||
}
|
||||
|
||||
if (applyFix && shouldBeAvailable) {
|
||||
intentionAction.invoke(myProject, myEditor, myFile);
|
||||
|
||||
checkResultByFile(graftBeforeExt(s, "_after"));
|
||||
}
|
||||
}
|
||||
|
||||
// Turns "name.ext" to "name_insertion.ext"
|
||||
private @NonNls String graftBeforeExt(String name, String insertion) {
|
||||
|
||||
@NonNls
|
||||
private static String graftBeforeExt(String name, String insertion) {
|
||||
int dotpos = name.indexOf('.');
|
||||
if (dotpos < 0) dotpos = name.length();
|
||||
return name.substring(0, dotpos) + insertion + name.substring(dotpos, name.length());
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
protected Sdk getProjectJDK() {
|
||||
return createMockJdk(PathManager.getHomePath() + "/plugins/python/testData/mockPythonJDK");
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
private static Sdk createMockJdk(String jdkHome) {
|
||||
File jdkHomeFile = new File(jdkHome);
|
||||
if (!jdkHomeFile.exists()) return null;
|
||||
@@ -226,5 +172,18 @@ public class PyQuickFixTest extends DaemonAnalyzerTestCase {
|
||||
jdk.setVersionString("2.5");
|
||||
return jdk;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
protected static class PyWithSdkProjectDescriptor extends PyLightProjectDescriptor {
|
||||
@Override
|
||||
public Sdk getSdk() {
|
||||
return createMockJdk(PathManager.getHomePath() + "/plugins/python/testData/mockPythonJDK");
|
||||
}
|
||||
}
|
||||
|
||||
private static final LightProjectDescriptor ourProjectDescriptor = new PyWithSdkProjectDescriptor();
|
||||
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return ourProjectDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.jetbrains.python;
|
||||
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.testFramework.ResolveTestCase;
|
||||
import com.jetbrains.python.fixtures.PyResolveTestCase;
|
||||
import com.jetbrains.python.psi.PyCallExpression;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.EnumSet;
|
||||
* User: dcheryasov
|
||||
* Date: Aug 21, 2008
|
||||
*/
|
||||
public class PyResolveCalleeTest extends ResolveTestCase {
|
||||
public class PyResolveCalleeTest extends PyResolveTestCase {
|
||||
|
||||
private PyCallExpression.PyMarkedFunction resolveCallee() throws Exception {
|
||||
PsiReference ref = configureByFile(getTestName(false) + ".py");
|
||||
|
||||
@@ -9,10 +9,10 @@ import com.intellij.psi.PsiPolyVariantReference;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.testFramework.ResolveTestCase;
|
||||
import com.jetbrains.python.fixtures.PyResolveTestCase;
|
||||
import com.jetbrains.python.psi.*;
|
||||
|
||||
public class PyResolveTest extends ResolveTestCase {
|
||||
public class PyResolveTest extends PyResolveTestCase {
|
||||
private PsiElement resolve() throws Exception {
|
||||
PsiReference ref = configureByFile(getTestName(false) + ".py");
|
||||
return ref.resolve();
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.editorActions.SelectWordHandler;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.testFramework.LightCodeInsightTestCase;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.io.File;
|
||||
@@ -10,33 +10,34 @@ import java.io.File;
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PySelectWordTest extends LightCodeInsightTestCase {
|
||||
public class PySelectWordTest extends PyLightFixtureTestCase {
|
||||
public void testWord() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() throws Exception {
|
||||
myFixture.copyDirectoryToProject("", "");
|
||||
@NonNls final String path = getTestName(true);
|
||||
configureByFile(path + "/before.py");
|
||||
myFixture.configureByFile(path + "/before.py");
|
||||
int i = 1;
|
||||
while (true) {
|
||||
@NonNls String resultPath = path + "/after" + i + ".py";
|
||||
if (new File(getTestDataPath() + resultPath).exists()) {
|
||||
performAction();
|
||||
//System.out.println("comparing with "+resultPath);
|
||||
checkResultByFile("Step " + i, resultPath, false);
|
||||
myFixture.checkResultByFile(resultPath, false);
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(i>1);
|
||||
assertTrue(i > 1);
|
||||
}
|
||||
|
||||
private void performAction() {
|
||||
SelectWordHandler action = new SelectWordHandler(null);
|
||||
action.execute(getEditor(), DataManager.getInstance().getDataContext());
|
||||
action.execute(myFixture.getEditor(), DataManager.getInstance().getDataContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.fixtures.LightMarkedTestCase;
|
||||
import com.jetbrains.python.psi.*;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -10,7 +11,7 @@ import java.util.Map;
|
||||
* User: dcheryasov
|
||||
* Date: Mar 15, 2009 3:11:01 AM
|
||||
*/
|
||||
public class PyStatementPartsTest extends MarkedTestCase {
|
||||
public class PyStatementPartsTest extends LightMarkedTestCase {
|
||||
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/psi/parts/";
|
||||
|
||||
@@ -3,49 +3,34 @@
|
||||
*/
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightTestCase;
|
||||
import com.intellij.openapi.application.Result;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.psi.stubs.StubUpdatingIndex;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyFileImpl;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class PyStubsTest extends CodeInsightTestCase {
|
||||
private VirtualFile myRootDir;
|
||||
public class PyStubsTest extends PyLightFixtureTestCase {
|
||||
private static final String PARSED_ERROR_MSG = "Operations should have been performed on stubs but caused file to be parsed";
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
myRunCommandForTest = false;
|
||||
super.setUp();
|
||||
prepareRoots();
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/stubs/";
|
||||
}
|
||||
|
||||
private void assertNotParsed(PyFile file) {
|
||||
private static void assertNotParsed(PyFile file) {
|
||||
assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
|
||||
}
|
||||
|
||||
private void prepareRoots() throws Exception {
|
||||
new WriteAction() {
|
||||
protected void run(final Result result) throws Throwable {
|
||||
String root = PythonTestUtil.getTestDataPath() + "/stubs/";
|
||||
myRootDir = PsiTestUtil.createTestProjectStructure(myProject, myModule, root, myFilesToDelete, false);
|
||||
PsiTestUtil.addSourceContentToRoots(myModule, myRootDir);
|
||||
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public void testStubStructure() throws Exception {
|
||||
final PyFile file = getTestFile();
|
||||
final List<PyClass> classes = file.getTopLevelClasses();
|
||||
@@ -152,7 +137,7 @@ public class PyStubsTest extends CodeInsightTestCase {
|
||||
assertEquals(1, children.length);
|
||||
assertSame(pyClass, children[0]);
|
||||
|
||||
new WriteCommandAction(myProject, fileImpl) {
|
||||
new WriteCommandAction(myFixture.getProject(), fileImpl) {
|
||||
protected void run(final Result result) throws Throwable {
|
||||
pyClass.setName("RenamedClass");
|
||||
assertEquals("RenamedClass", pyClass.getName());
|
||||
@@ -162,7 +147,7 @@ public class PyStubsTest extends CodeInsightTestCase {
|
||||
StubElement fileStub = fileImpl.getStub();
|
||||
assertNull("There should be no stub if file holds tree element", fileStub);
|
||||
|
||||
FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, getProject(), null);
|
||||
FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, myFixture.getProject(), null);
|
||||
fileImpl.unloadContent();
|
||||
assertNull(fileImpl.getTreeElement()); // Test unload successed.
|
||||
|
||||
@@ -177,10 +162,10 @@ public class PyStubsTest extends CodeInsightTestCase {
|
||||
return getTestFile(getTestName(false) + ".py");
|
||||
}
|
||||
|
||||
private PyFile getTestFile(final String fileName) {
|
||||
VirtualFile sourceFile = myRootDir.findChild(fileName);
|
||||
private PyFile getTestFile(final String fileName) throws IOException {
|
||||
VirtualFile sourceFile = myFixture.copyFileToProject(fileName);
|
||||
assert sourceFile != null;
|
||||
PsiFile psiFile = myPsiManager.findFile(sourceFile);
|
||||
PsiFile psiFile = myFixture.getPsiManager().findFile(sourceFile);
|
||||
return (PyFile)psiFile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.impl.JavaSdkImpl;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.testFramework.ResolveTestCase;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PyToJavaResolveTest extends ResolveTestCase {
|
||||
protected Sdk getTestProjectJdk() {
|
||||
return JavaSdkImpl.getMockJdk15("java 1.5");
|
||||
}
|
||||
|
||||
private PsiElement resolve() throws Exception {
|
||||
PsiReference ref = configureByFile(getTestName(false) + ".py");
|
||||
return ref.resolve();
|
||||
}
|
||||
|
||||
public void testSimple() throws Exception {
|
||||
PsiElement target = resolve();
|
||||
assertTrue(target instanceof PsiClass);
|
||||
assertEquals("java.util.ArrayList", ((PsiClass) target).getQualifiedName());
|
||||
}
|
||||
|
||||
public void testMethod() throws Exception {
|
||||
PsiElement target = resolve();
|
||||
assertTrue(target instanceof PsiMethod);
|
||||
assertEquals("java.util.ArrayList", ((PsiMethod) target).getContainingClass().getQualifiedName());
|
||||
}
|
||||
|
||||
public void testField() throws Exception {
|
||||
PsiElement target = resolve();
|
||||
assertTrue(target instanceof PsiField);
|
||||
assertEquals("java.lang.System", ((PsiField) target).getContainingClass().getQualifiedName());
|
||||
}
|
||||
|
||||
public void testReturnValue() throws Exception {
|
||||
PsiElement target = resolve();
|
||||
assertTrue(target instanceof PsiMethod);
|
||||
assertEquals("java.util.List", ((PsiMethod) target).getContainingClass().getQualifiedName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/resolve/pyToJava/";
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ public class PythonAllTestsSuite {
|
||||
PyResolveTest.class,
|
||||
PyMultiFileResolveTest.class,
|
||||
PyResolveCalleeTest.class,
|
||||
PyToJavaResolveTest.class,
|
||||
PyAssignmentMappingTest.class,
|
||||
PythonCompletionTest.class,
|
||||
PyInheritorsSearchTest.class,
|
||||
@@ -30,6 +29,7 @@ public class PythonAllTestsSuite {
|
||||
PythonDemorganLawIntentionTest.class,
|
||||
PyQuickFixTest.class,
|
||||
PySelectWordTest.class,
|
||||
PySurroundWithTest.class
|
||||
};
|
||||
|
||||
public static TestSuite suite() {
|
||||
|
||||
@@ -4,17 +4,15 @@
|
||||
*/
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.completion.CodeCompletionHandlerBase;
|
||||
import com.intellij.codeInsight.completion.CompletionType;
|
||||
import com.intellij.testFramework.LightCodeInsightTestCase;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
|
||||
public class PythonCompletionTest extends LightCodeInsightTestCase{
|
||||
public class PythonCompletionTest extends PyLightFixtureTestCase {
|
||||
|
||||
private void doTest() throws Exception {
|
||||
final String testName = getTestName(true);
|
||||
configureByFile(testName + ".py");
|
||||
new CodeCompletionHandlerBase(CompletionType.BASIC).invoke(getProject(), getEditor(), getFile());
|
||||
checkResultByFile(testName + ".after.py");
|
||||
myFixture.configureByFile(testName + ".py");
|
||||
myFixture.completeBasic();
|
||||
myFixture.checkResultByFile(testName + ".after.py");
|
||||
}
|
||||
|
||||
protected String getTestDataPath() {
|
||||
|
||||
@@ -4,21 +4,29 @@
|
||||
*/
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
|
||||
public class PythonDemorganLawIntentionTest extends LightIntentionActionTestCase {
|
||||
public void test() throws Exception { doAllTests(); }
|
||||
|
||||
protected String getBasePath() {
|
||||
return "/demorgan";
|
||||
public class PythonDemorganLawIntentionTest extends PyLightFixtureTestCase {
|
||||
public void test1() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void test2() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void test3() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() throws Exception {
|
||||
myFixture.configureByFile("before" + getTestName(false) + ".py");
|
||||
final IntentionAction action = myFixture.findSingleIntention("DeMorgan Law");
|
||||
myFixture.launchAction(action);
|
||||
|
||||
}
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/intentions";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldBeAvailableAfterExecution() {
|
||||
return true;
|
||||
return PythonTestUtil.getTestDataPath() + "/intentions/demorgan";
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme;
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey;
|
||||
import com.intellij.openapi.editor.markup.EffectType;
|
||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -14,7 +14,7 @@ import java.awt.*;
|
||||
* Test highlighting added by annotators.
|
||||
* @author yole
|
||||
*/
|
||||
public class PythonHighlightingTest extends DaemonAnalyzerTestCase {
|
||||
public class PythonHighlightingTest extends PyLightFixtureTestCase {
|
||||
@NonNls
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/highlighting/";
|
||||
@@ -63,7 +63,7 @@ public class PythonHighlightingTest extends DaemonAnalyzerTestCase {
|
||||
}
|
||||
|
||||
public void testContinueInFinallyBlock() throws Exception {
|
||||
doTest();
|
||||
doTest(false, false);
|
||||
}
|
||||
|
||||
public void testReturnWithArgumentsInGenerator() throws Exception {
|
||||
@@ -101,11 +101,11 @@ public class PythonHighlightingTest extends DaemonAnalyzerTestCase {
|
||||
}
|
||||
|
||||
private void doTest() throws Exception {
|
||||
doTest(getTestName(true) + PyNames.DOT_PY, true, true);
|
||||
myFixture.testHighlighting(true, true, false, getTestName(true) + PyNames.DOT_PY);
|
||||
}
|
||||
|
||||
private void doTest(boolean checkWarnings, boolean checkInfos) throws Exception {
|
||||
doTest(getTestName(true) + PyNames.DOT_PY, checkWarnings, checkInfos);
|
||||
myFixture.testHighlighting(checkWarnings, checkInfos, false, getTestName(true) + PyNames.DOT_PY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-7
@@ -1,15 +1,17 @@
|
||||
package com.jetbrains.python;
|
||||
package com.jetbrains.python.fixtures;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.testFramework.PsiTestCase;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -19,7 +21,8 @@ import java.util.regex.Pattern;
|
||||
* User: dcheryasov
|
||||
* Date: Mar 14, 2009 11:57:52 PM
|
||||
*/
|
||||
public abstract class MarkedTestCase extends PsiTestCase {
|
||||
public abstract class LightMarkedTestCase extends PyLightFixtureTestCase {
|
||||
protected PsiFile myFile;
|
||||
|
||||
/**
|
||||
* Marker "as expected", any alphanumeric sting in angle brackets.
|
||||
@@ -67,7 +70,7 @@ public abstract class MarkedTestCase extends PsiTestCase {
|
||||
* @return mapping of markers to the PSI elements
|
||||
* @throws Exception
|
||||
*/
|
||||
protected Map<String, PsiElement> configureByFileText(String fileText, String fileName, @NonNls String markerRegexp)
|
||||
protected Map<String, PsiElement> configureByFileText(String fileText, final String fileName, @NonNls String markerRegexp)
|
||||
throws Exception
|
||||
{
|
||||
// build a map of marks to positions, and the text with marks stripped
|
||||
@@ -75,7 +78,7 @@ public abstract class MarkedTestCase extends PsiTestCase {
|
||||
Matcher mat = pat.matcher(fileText);
|
||||
int rest_index = 0; // from here on fileText is not yet looked at
|
||||
Map<String, Integer> offsets = new HashMap<String, Integer>();
|
||||
StringBuffer text = new StringBuffer();
|
||||
final StringBuffer text = new StringBuffer();
|
||||
while (mat.find(rest_index)) {
|
||||
String mark = mat.group();
|
||||
CharSequence prev_part = fileText.subSequence(rest_index, mat.start());
|
||||
@@ -87,7 +90,17 @@ public abstract class MarkedTestCase extends PsiTestCase {
|
||||
|
||||
// create a file and map marks to PSI elements
|
||||
Map<String, PsiElement> result = new HashMap<String, PsiElement>();
|
||||
myFile = createFile(myModule, fileName, text.toString());
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
myFile = myFixture.addFileToProject(fileName, text.toString());
|
||||
myFixture.configureFromExistingVirtualFile(myFile.getVirtualFile());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (Map.Entry<String, Integer> entry : offsets.entrySet()) {
|
||||
result.put(entry.getKey(), myFile.findElementAt(entry.getValue()));
|
||||
}
|
||||
@@ -100,4 +113,4 @@ public abstract class MarkedTestCase extends PsiTestCase {
|
||||
}
|
||||
|
||||
protected abstract String getTestDataPath();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import com.intellij.openapi.module.ModuleType;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.ContentEntry;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import com.intellij.testFramework.fixtures.CodeInsightTestFixture;
|
||||
@@ -23,10 +25,12 @@ public abstract class PyLightFixtureTestCase extends UsefulTestCase {
|
||||
private static final PyLightProjectDescriptor ourPyDescriptor = new PyLightProjectDescriptor();
|
||||
|
||||
protected CodeInsightTestFixture myFixture;
|
||||
private static boolean ourPlatformPrefixInitialized;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
initPlatformPrefix();
|
||||
IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
|
||||
TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder = factory.createLightFixtureBuilder(getProjectDescriptor());
|
||||
final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
|
||||
@@ -53,7 +57,12 @@ public abstract class PyLightFixtureTestCase extends UsefulTestCase {
|
||||
return ourPyDescriptor;
|
||||
}
|
||||
|
||||
private static class PyLightProjectDescriptor implements LightProjectDescriptor {
|
||||
protected PsiReference findReferenceBySignature(final String signature) {
|
||||
int pos = PsiDocumentManager.getInstance(myFixture.getProject()).getDocument(myFixture.getFile()).getText().indexOf(signature);
|
||||
return myFixture.getFile().findReferenceAt(pos);
|
||||
}
|
||||
|
||||
protected static class PyLightProjectDescriptor implements LightProjectDescriptor {
|
||||
public ModuleType getModuleType() {
|
||||
return EmptyModuleType.getInstance();
|
||||
}
|
||||
@@ -66,4 +75,19 @@ public abstract class PyLightFixtureTestCase extends UsefulTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public static void initPlatformPrefix() {
|
||||
if (!ourPlatformPrefixInitialized) {
|
||||
ourPlatformPrefixInitialized = true;
|
||||
boolean isIDEA = true;
|
||||
try {
|
||||
PyLightFixtureTestCase.class.getClassLoader().loadClass("com.intellij.openapi.project.impl.IdeaProjectManagerImpl");
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
isIDEA = false;
|
||||
}
|
||||
if (!isIDEA) {
|
||||
System.setProperty("idea.platform.prefix", "Python");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.jetbrains.python.fixtures;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public abstract class PyResolveTestCase extends PyLightFixtureTestCase {
|
||||
@NonNls protected static final String MARKER = "<ref>";
|
||||
|
||||
protected PsiReference configureByFile(final String filePath) throws Exception {
|
||||
VirtualFile testDataRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(getTestDataPath()));
|
||||
final VirtualFile file = testDataRoot.findFileByRelativePath(filePath);
|
||||
assertNotNull(file);
|
||||
|
||||
String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(file));
|
||||
int offset = fileText.indexOf(MARKER);
|
||||
assertTrue(offset >= 0);
|
||||
fileText = fileText.substring(0, offset) + fileText.substring(offset + MARKER.length());
|
||||
final String finalFileText = fileText;
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
myFixture.configureByText(new File(filePath).getName(), finalFileText);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
final PsiReference reference = myFixture.getFile().findReferenceAt(offset);
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user