2008-04-02 09:10:18 +00:00

1005 lines
30 KiB
Python

"""
VimPdb.py
Pdb simulation within Vim (in an IDE like fashion).
Author:
Yaron Budowski
"""
import bdb
import vim
import time
import sys
import os
class PdbIDE(bdb.Bdb):
"""Simulates a Python debugger in an IDE-like mode (unlike PDB, which acts as a command-line console debugger)."""
#
# Constants
#
# The number of seconds to wait in the wait_in_debug() waiting loop.
PAUSE_DEBUG_WAIT_TIME = 0.2
# Various messages displayed to the user.
MESSAGE_NOT_IN_DEBUG_MODE = 'Error: Debugging not started yet'
MESSAGE_STARTING_DEBUG = 'Starting debugging...'
MESSAGE_PROGRAM_ENDED = 'Program ended. Restart debug to rerun program'
MESSAGE_ALREADY_AT_OLDEST_FRAME = 'Error: Already at oldest stack frame'
MESSAGE_ALREADY_AT_NEWEST_FRAME = 'Error: Already at newest stack frame'
MESSAGE_PROGRAM_ENDED_VIA_SYS_EXIT = 'Program ended via sys.exit(). Exit status: %d'
MESSAGE_PROGRAM_ENDED_UNCAUGHT_EXCEPTION = 'Program ended due to an uncaught exception.'
MESSAGE_NO_CONDITIONAL_BREAKPOINT = 'Error: No conditional breakpoint in current line'
MESSAGE_BREAKPOINT_CONDITION = 'Breakpoint Condition: %s'
MESSAGE_JUMP_ONLY_AT_BOTTOM_FRAME = 'Error: Can only jump to line within the bottom stack frame'
MESSAGE_JUMP_ONLY_IN_CURRENT_FILE = 'Error: Can only jump to line within the currently debugged file'
# Breakpoint types (used when saving\loading breakpoints from files).
BREAKPOINT_TYPE_REGULAR = 'regular'
BREAKPOINT_TYPE_TEMPORARY = 'temporary'
BREAKPOINT_TYPE_CONDITIONAL = 'conditional'
BREAKPOINT_TYPES = [BREAKPOINT_TYPE_REGULAR, BREAKPOINT_TYPE_CONDITIONAL, BREAKPOINT_TYPE_TEMPORARY]
def __init__(self):
# Initialize the parent Bdb class.
bdb.Bdb.__init__(self)
# Used so we won't pause until the main script is loaded completely.
self.wait_for_script_start = False
self.main_filename = None
# Used in wait_in_debug method (method doesn't return until pause_debug == False).
self.pause_debug = False
# Current debugged filename & line.
self.current_filename = None
self.current_line = -1
# Current debugged frame.
self.current_frame = None
self.current_stack_index = 0
self.stack = []
# A queue of Bdb methods to run. This is used when VimPdb methods (as opposed to Bdb methods) are called directly
# from the Vim file (VimPdb.vim) - these methods (such as do_toggle_breakpoint) use this queue to call Bdb methods
# (such as set_break) indirectly - it's done this way so the Bdb methods will be called from this instance's thread,
# and not from the Vim thread (which is the main thread). If the Bdb methods were called directly, it would screw up Python
# and Vim, and Vim will sometimes freeze\crash.
# The run_queued_methods method goes through this queue and executes the commands in it: each item is a list of
# function name and parameters.
self.methods_to_run = []
# The return value of the last method.
self.last_method_return_value = None
def start_debugging(self, filename, stop_immediately = True, args = []):
"""Starts a debug session for a file. If stop_immediately is set, session is paused on the first line of program."""
self.print_message(self.MESSAGE_STARTING_DEBUG)
new_globals = { '__name__': '__main__' }
new_locals = new_globals
self.wait_for_script_start = True # So we won't break before we reach the first line of the script being debugged.
self.stop_immediately = stop_immediately
self.main_filename = self.canonic(filename)
self.current_filename = self.main_filename
self.current_line = 1
# Highlight the breakpoints.
self.highlight_breakpoints(self.main_filename, *self.get_breakpoints_for_file(self.main_filename))
# Replace main directory with running script's directory in front of module search path.
sys.path[0] = os.path.dirname(self.main_filename)
try:
# Set command line arguments.
sys.argv = [self.main_filename] + args
# Run the script.
statement = 'execfile(r"%s")' % (self.main_filename)
self.run(statement, globals = new_globals, locals = new_locals)
# Program ended.
self.print_message(self.MESSAGE_PROGRAM_ENDED)
self.clear_current_line_highlighting()
self.clear_breakpoints_highlighting()
except SystemExit:
self.print_message(self.MESSAGE_PROGRAM_ENDED_VIA_SYS_EXIT % (sys.exc_info()[1]))
self.clear_current_line_highlighting()
self.clear_breakpoints_highlighting()
except:
self.print_message(self.MESSAGE_PROGRAM_ENDED_UNCAUGHT_EXCEPTION)
raise
self.clear_current_line_highlighting()
self.clear_breakpoints_highlighting()
def stop_debugging(self):
"""Stops the debugging session."""
if (not self.is_debugged()):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
self.quitting = True
#
# Debugging methods
#
def do_continue(self):
"""Continues the deugging session until reaching a breakpoint, etc."""
if (self.current_frame is None):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
self.set_continue()
self.pause_debug = False
def do_continue_until_return(self):
"""Continues running until returning from the current frame."""
if (self.current_frame is None):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
self.set_return(self.current_frame)
self.pause_debug = False
def do_step_into(self):
"""Does step into."""
if (self.current_frame is None):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
self.set_step()
self.pause_debug = False
def do_step_over(self):
"""Does step over (doesn't enter any functions in between)."""
if (self.current_frame is None):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
self.set_next(self.current_frame)
self.pause_debug = False
def do_move_up_in_stack_frame(self):
"""Moves up one level in the stack frame."""
if (not self.is_debugged()):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
if (self.current_stack_index <= 2):
self.print_message(self.MESSAGE_ALREADY_AT_OLDEST_FRAME)
return
self.current_stack_index -= 1
self.current_frame = self.stack[self.current_stack_index][0]
self.goto_current_line(self.current_frame)
def do_move_down_in_stack_frame(self):
"""Moves down one level in the stack frame."""
if (not self.is_debugged()):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
if (self.current_stack_index + 1== len(self.stack)):
self.print_message(self.MESSAGE_ALREADY_AT_NEWEST_FRAME)
return
self.current_stack_index += 1
self.current_frame = self.stack[self.current_stack_index][0]
self.goto_current_line(self.current_frame)
def do_toggle_breakpoint(self, filename, line_number, condition = None, temporary = False):
"""Sets\unsets a breakpoint."""
if (not self.is_debugged()):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
if (not self.is_code_line(filename, line_number)):
# Not a code line.
return
# First, prepare a list of all available breakpoints for this file.
breakpoints = self.get_file_breaks(filename)[:] # Make a copy so we won't be affected by changes.
if (line_number in breakpoints):
# Unset breakpoint.
self.clear_break(filename, line_number)
else:
# Set the breakpoint.
self.set_break(filename, line_number, int(temporary), condition)
# Re-Highlight the breakpoints.
self.highlight_breakpoints(filename, *self.get_breakpoints_for_file(filename))
def do_print_breakpoint_condition(self, filename, line_number):
"""Prints the condition of a breakpoint at the specified line number."""
if (not self.is_debugged()):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
# First, prepare a list of all available breakpoints for this file.
conditional_breakpoints = self.get_conditional_breakpoints(filename)
if (line_number not in conditional_breakpoints):
self.print_message(self.MESSAGE_NO_CONDITIONAL_BREAKPOINT)
return
breakpoint_instances = self.get_breaks(filename, line_number)
for breakpoint in breakpoint_instances:
if (breakpoint.cond):
self.print_message(self.MESSAGE_BREAKPOINT_CONDITION % (breakpoint.cond))
return
def do_clear_all_breakpoints(self, filename = None):
"""Clears all breakpoints. If filename is specified, only breakpoints for that filename are cleared."""
if (filename is None):
self.clear_all_breaks()
# Re-Highlight the breakpoints.
self.highlight_breakpoints(filename, *self.get_breakpoints_for_file(filename))
return
# Get all breakpoints for specified file.
file_breaks = self.get_file_breaks(filename)
for line_number in file_breaks:
self.clear_break(filename, line_number)
# Re-Highlight the breakpoints.
self.highlight_breakpoints(filename, *self.get_breakpoints_for_file(filename))
def do_clear(self, breakpoint_number):
"""Clears a specified breakpoint by number."""
self.clear_bpbynumber(breakpoint_number)
# Re-Highlight the breakpoints.
self.highlight_breakpoints(self.current_filename, *self.get_breakpoints_for_file(self.current_filename))
def do_eval(self, expression):
"""Evaluates an expression in the current debugging context."""
if (self.current_frame is None):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
try:
value = eval(expression, self.current_frame.f_globals, self.current_frame.f_locals)
self.print_message(value)
except:
(exc_type, value, traceback) = sys.exc_info()
if (not isinstance(exc_type, str)):
exc_type_name = exc_type.__name__
else:
exc_type_name = exc_type
self.print_message('%s: %s' % (exc_type_name, value))
def do_exec(self, statement):
"""Executes a statement in the current debugging context."""
if (self.current_frame is None):
self.print_message(self.MESSAGE_NOT_IN_DEBUG_MODE)
return
exec_locals = self.current_frame.f_locals
exec_globals = self.current_frame.f_locals
try:
code = compile(statement + '\n', '<stdin>', 'single')
exec code in exec_globals, exec_locals
except:
(exc_type, value, traceback) = sys.exc_info()
if (not isinstance(exc_type, str)):
exc_type_name = exc_type.__name__
else:
exc_type_name = exc_type
self.print_message('%s: %s' % (exc_type_name, value))
def do_jump(self, filename, line_number):
"""Jumps to a specified line in the currently debugged file."""
if (self.current_stack_index + 1 != len(self.stack)):
self.print_message(self.MESSAGE_JUMP_ONLY_AT_BOTTOM_FRAME)
return
if (self.canonic(filename) != self.current_filename):
self.print_message(self.MESSAGE_JUMP_ONLY_IN_CURRENT_FILE)
return
try:
self.current_frame.f_lineno = line_number
self.stack[self.current_stack_index] = (self.stack[self.current_stack_index][0], line_number)
self.goto_current_line(self.current_frame)
except ValueError, exc:
self.print_message('Error: %s' % (exc))
def do_print_stack_trace(self):
"""Prints the stack trace."""
output_stack_traces = []
# Prepare the stack trace string.
for current_stack_frame in self.stack[2:]: # Skip the first two entries (which aren't really part of the debugged code)
(frame, line_number) = current_stack_frame
if (frame is self.current_frame):
output_stack_traces.append(self.current_stack_entry_prefix + self.format_stack_entry(current_stack_frame))
else:
output_stack_traces.append(self.stack_entry_prefix + self.format_stack_entry(current_stack_frame))
final_stack_trace = self.stack_entries_joiner.join(output_stack_traces)
self.print_message('Stack Trace:\n' + final_stack_trace)
def goto_current_line(self, frame, display = True):
"""Moves the cursor to the currently debugged line, in the appropriate file. If display == False, don't highlight or move the cursor."""
if (not self.is_debugged()):
return
# Get the line number & filename.
line_number = frame.f_lineno
filename = self.canonic(frame.f_code.co_filename)
self.current_filename = filename
self.current_line = line_number
if (display):
# Load the file for editing (even if the file is not currently opened).
self.open_file(filename)
self.set_cursor_position(self.current_line, 0)
self.highlight_current_line(self.current_filename, self.current_line)
#
# Queue related methods
#
def add_queued_method(self, function_name, *parameters):
"""Adds a method to the methods to run queue. It will be called indirectly by run_queued_methods"""
self.methods_to_run.append([function_name, parameters])
def run_queued_methods(self):
"""Executes any methods queued for execution. Used so that the methods will be executed from this instance's
thread context (and not from the main Vim thread)."""
while (len(self.methods_to_run) > 0):
# Get the next method to run.
method_to_run = self.methods_to_run[0]
self.methods_to_run = self.methods_to_run[1:]
(function_name, parameters) = method_to_run
if (not hasattr(self, function_name)):
# Function doesn't exist.
raise
# TODO
#continue
# Run the function.
function_pointer = getattr(self, function_name)
self.last_method_return_value = function_pointer(*parameters)
def wait_in_debug(self, frame, traceback = None):
"""Loops as long as self.pause_debug is True."""
# Save the current frame, etc.
(self.stack, self.current_stack_index) = self.get_stack(frame, traceback)
self.current_frame = self.stack[self.current_stack_index][0]
self.goto_current_line(frame)
while ((self.pause_debug) and (not self.quitting)):
time.sleep(self.PAUSE_DEBUG_WAIT_TIME)
# Run any queued methods.
self.run_queued_methods()
self.pause_debug = True
#
# Saving\Restoring breakpoints methods
#
def is_breakpoint_enabled(self, filename, line):
"""Returns True if a breakpoint is enabled at the specified filename & line. False otherwise."""
if (self.get_breaks(filename, line)):
return True
else:
return False
def highlight_breakpoints_for_file(self, filename):
"""Highlights breakpoints for a given filename."""
self.highlight_breakpoints(self.canonic(filename), *self.get_breakpoints_for_file(self.canonic(filename)))
def highlight_current_line_for_file(self, filename):
"""Highlights current line for a given filename."""
canonic_filename = self.canonic(filename)
if (self.current_filename != canonic_filename):
# The given filename is not the currently debugged file.
return
self.highlight_current_line(canonic_filename, self.current_line)
def get_breakpoints(self):
"""Returns a list of active breakpoints."""
file_breakpoints = self.get_all_breaks()
returned_breakpoints = []
for filename in file_breakpoints.keys():
for line_number in file_breakpoints[filename]:
for breakpoint in self.get_breaks(filename, line_number):
new_breakpoint = {}
new_breakpoint['filename'] = filename
new_breakpoint['line'] = breakpoint.line
if (breakpoint.cond):
new_breakpoint['type'] = self.BREAKPOINT_TYPE_CONDITIONAL
new_breakpoint['condition'] = breakpoint.cond
elif (breakpoint.temporary):
new_breakpoint['type'] = self.BREAKPOINT_TYPE_TEMPORARY
else:
new_breakpoint['type'] = self.BREAKPOINT_TYPE_REGULAR
returned_breakpoints.append(new_breakpoint)
return returned_breakpoints
def set_breakpoints(self, breakpoints):
"""Sets\Adds breakpoints from a list of breakpoints."""
for breakpoint in breakpoints:
condition = None
temporary = False
if (breakpoint['type'] == self.BREAKPOINT_TYPE_CONDITIONAL):
condition = breakpoint['condition']
elif (breakpoint['type'] == self.BREAKPOINT_TYPE_TEMPORARY):
temporary = True
# Set the breakpoint
self.set_break(breakpoint['filename'], breakpoint['line'], int(temporary), condition)
# Re-highlight all of the breakpoints.
self.highlight_breakpoints(self.get_active_filename(), *self.get_breakpoints_for_file(self.get_active_filename()))
def load_breakpoints_from_file(self, filename):
"""Loads breakpoints from a file."""
if (not os.path.exists(filename)):
self.print_message('Error: File "%s" does not exist!' % (filename))
return
new_breakpoints = []
# First, clear all breakpoints.
#self.do_clear_all_breakpoints()
breakpoints_file = open(filename, 'rb')
# Load the breakpoints from the given file.
index = 0
for line in breakpoints_file.xreadlines():
line = line.strip()
index += 1
if (len(line) == 0):
continue
breakpoint_properties = line.split('\t')
if ((len(breakpoint_properties) < 3) or (len(breakpoint_properties) > 4)):
self.print_message('Error: Invalid line #%d at file "%s"' % (index, filename))
return
(breakpoint_filename, breakpoint_line, breakpoint_type) = breakpoint_properties[:3]
breakpoint_type = breakpoint_type.lower()
try:
breakpoint_line = int(breakpoint_line)
except ValueError:
self.print_message('Error: Invalid breakpoint line number in line #%d at file "%s"' % (index, filename))
return
if (breakpoint_type not in self.BREAKPOINT_TYPES):
self.print_message('Error: Invalid breakpoint type in line #%d at file "%s"' % (index, filename))
return
if ((breakpoint_type == self.BREAKPOINT_TYPE_CONDITIONAL) and (len(breakpoint_properties) != 4)):
self.print_message('Error: Missing/invalid breakpoint condition in line #%d at file "%s"' % (index, filename))
return
condition = None
temporary = False
if (breakpoint_type == self.BREAKPOINT_TYPE_CONDITIONAL):
condition = breakpoint_properties[3]
elif (breakpoint_type == self.BREAKPOINT_TYPE_TEMPORARY):
temporary = True
new_breakpoint = {}
new_breakpoint['filename'] = breakpoint_filename
new_breakpoint['line'] = breakpoint_line
new_breakpoint['type'] = breakpoint_type
new_breakpoint['condition'] = condition
new_breakpoint['temporary'] = temporary
new_breakpoints.append(new_breakpoint)
breakpoints_file.close()
# Set the loaded breakpoints.
self.set_breakpoints(new_breakpoints)
def save_breakpoints_to_file(self, filename):
"""Saves all active breakpoints to a file."""
breakpoints_file = open(filename, 'wb')
breakpoints = self.get_breakpoints()
for breakpoint in breakpoints:
line = '%s\t%s\t%s' % (breakpoint['filename'], breakpoint['line'], breakpoint['type'])
if (breakpoint['type'] == self.BREAKPOINT_TYPE_CONDITIONAL):
line += '\t' + breakpoint['condition']
breakpoints_file.write(line + '\n')
breakpoints_file.close()
#
# Helper methods
#
def is_debugged(self):
"""Checks whether or not there active debugging currently enabled."""
#if ((not hasattr(self, 'quitting')) or (self.quitting) or (not self.current_frame)):
if ((not hasattr(self, 'quitting')) or (self.quitting)):
return False
else:
return True
def is_exit_frame(self, frame):
"""Tests whether or not the current frame is of the exit frame."""
if (self.canonic(frame.f_code.co_filename) == '<string>'):
return True
else:
return False
def get_conditional_breakpoints(self, filename):
"""Returns a list of line numbers with conditional breakpoints for a given filename."""
conditional_breakpoints = []
# First, get the line numbers which have breakpoints set in them.
file_breaks = self.get_file_breaks(filename)
for line_number in file_breaks:
breakpoint_instances = self.get_breaks(filename, line_number)
for breakpoint in breakpoint_instances:
if (breakpoint.cond):
# Found a conditional breakpoint - add it to the list.
conditional_breakpoints.append(line_number)
return conditional_breakpoints
def get_temporary_breakpoints(self, filename):
"""Returns a list of line numbers with temporary breakpoints for a given filename."""
temporary_breakpoints = []
# First, get the line numbers which have breakpoints set in them.
file_breaks = self.get_file_breaks(filename)
for line_number in file_breaks:
breakpoint_instances = self.get_breaks(filename, line_number)
for breakpoint in breakpoint_instances:
if (breakpoint.temporary):
# Found a temporary breakpoint - add it to the list.
temporary_breakpoints.append(line_number)
return temporary_breakpoints
def get_breakpoints_for_file(self, filename):
"""Returns a tuple of (regular_breakpoints, conditional_breakpoints, temporary_breakpoints) for
a given filename."""
regular_breakpoints = self.get_file_breaks(filename)[:] # Make a copy so we won't be affected by changes.
conditional_breakpoints = self.get_conditional_breakpoints(filename)
temporary_breakpoints = self.get_temporary_breakpoints(filename)
# Remove any breakpoints which appear in the regular_breakpoints list, and are actually
# conditional or temporary breakpoints.
for breakpoint in regular_breakpoints:
if ((breakpoint in conditional_breakpoints) or (breakpoint in temporary_breakpoints)):
regular_breakpoints.remove(breakpoint)
return (regular_breakpoints, conditional_breakpoints, temporary_breakpoints)
def is_code_line(self, filename, line):
"""Returns True if the given line is a code line; False otherwise.
Warning: not comprehensive enough."""
import linecache
source_line = linecache.getline(self.canonic(filename), line)
if (not source_line):
return False
source_line = source_line.strip()
if ((len(source_line) == 0) or (source_line[0] == '#') or
(source_line[:3] == '"""') or (source_line[:3] == "'''")):
return False
return True
#
# Overridden Bdb methods
#
def format_stack_entry(self, stack_frame):
"""Formats the stack frame into a printable string."""
import linecache
(frame, line_number) = stack_frame
filename = self.canonic(frame.f_code.co_filename)
(directory, filename) = os.path.split(filename)
if (frame.f_code.co_name):
function_name = frame.f_code.co_name
else:
function_name = '<lambda>'
if ('__args__' in frame.f_locals.keys()):
args = frame.f_locals['__args__']
else:
args = ''
if ('__return__' in frame.f_locals.keys()):
return_value = '-> %s' % (frame.f_locals['__return__'])
else:
return_value = ''
source_line = linecache.getline(filename, line_number)
if (not source_line):
source_line = ''
else:
source_line = source_line.strip()
stack_entry_string = self.stack_entry_format % (
{'filename': filename, 'dir': directory, 'line': line_number, 'function': function_name,
'args': args, 'return_value': return_value, 'source_line': source_line})
return stack_entry_string
def user_call(self, frame, args):
if ((self.wait_for_script_start) or (self.quitting)):
# Haven't reached the start of the script yet.
return
if (self.stop_here(frame)):
# Change the cursor position to the currently debugged line.
self.wait_in_debug(frame)
def user_line(self, frame):
"""Called when we stop or break at this line."""
if (self.quitting):
return
if (self.wait_for_script_start):
if ((self.main_filename != self.canonic(frame.f_code.co_filename)) or (frame.f_lineno <= 0)):
# Haven't reached the start of the script yet.
return
# Reached the start of the main script being debugged.
self.wait_for_script_start = False
if (not self.stop_immediately):
# Debugging should start without pausing immediately.
self.set_continue()
self.pause_debug = False
else:
self.pause_debug = True
# Move to the current line being debugged.
self.wait_in_debug(frame)
def user_return(self, frame, return_value):
"""Called when a return trap is set here."""
if (self.quitting):
return
if (self.is_exit_frame(frame)):
# It's the last frame.
self.print_message(self.MESSAGE_PROGRAM_ENDED)
self.clear_current_line_highlighting()
self.clear_breakpoints_highlighting()
return
frame.f_locals['__return__'] = return_value
self.pause_debug = False
self.wait_in_debug(frame)
def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
"""Called if an exception occurs, but only if we are to stop at or just below this level."""
if (self.quitting):
return
frame.f_locals['__exception__'] = exc_type, exc_value
if (type(exc_type) == type('')):
exc_type_name = exc_type
else:
exc_type_name = exc_type.__name__
if (self.is_exit_frame(frame)):
# It's the last frame.
self.print_message(self.MESSAGE_PROGRAM_ENDED)
self.clear_current_line_highlighting()
self.clear_breakpoints_highlighting()
return
self.print_message("%s: %s" % (exc_type_name, exc_value))
self.wait_in_debug(frame)
#
# Methods to be overridden by the editor-specific child class.
#
def print_message(self, message):
"""Prints a message to the editor console"""
raise NotImplementedError()
def set_cursor_position(self, row, column):
"""Sets the cursor position for the current editor window."""
raise NotImplementedError()
def highlight_breakpoints(self, filename, regular_breakpoints, conditional_breakpoints, temporary_breakpoints):
"""Highlights the active breakpoints in the given file."""
raise NotImplementedError()
def highlight_current_line(self, filename, line):
"""Highlights the current debugged line."""
raise NotImplementedError()
def clear_current_line_highlighting(self):
"""Clears the highlighting of the current debugged line."""
raise NotImplementedError()
def clear_breakpoints_highlighting(self):
"""Clears the highlighting for the breakpoints."""
raise NotImplementedError()
def open_file(self, filename):
"""Opens a file for editing."""
raise NotImplementedError()
def get_active_filename(self):
"""Returns the filename of the active window."""
raise NotImplementedError()
class VimPdb(PdbIDE):
"""Integrates the Pdb IDE into Vim."""
#
# Constants
#
# The Vim group name used for highlighting the currently debugged line.
CURRENT_LINE_GROUP = 'PdbCurrentLineTemp'
USER_DEFINED_CURRENT_LINE_GROUP = 'PdbCurrentLine'
# The Vim group name used for highlighting the breakpoint line.
BREAKPOINT_GROUP = 'PdbBreakpoint'
# The Vim group name used for highlighting the conditional breakpoint line.
CONDITIONAL_BREAKPOINT_GROUP = 'PdbConditionalBreakpoint'
# The Vim group name used for highlighting the temporary breakpoint line.
TEMPORARY_BREAKPOINT_GROUP = 'PdbTemporaryBreakpoint'
def __init__(self):
# Initialize the parent PdbIDE class.
PdbIDE.__init__(self)
# The output buffer used when print_message() is called.
self.output_buffer = None
self.save_to_output_buffer = False
#
# Overridden methods, which implement the editor-specific functionalities.
#
def print_message(self, message):
"""Prints a message to the Vim console."""
if (self.save_to_output_buffer):
self.output_buffer = message
else:
print message
def set_cursor_position(self, row, column):
"""Sets the cursor position for the current Vim buffer."""
# Move to the right line.
self.normal_command('%dG' % (row))
# Move to the right column.
self.normal_command('0%dl' % (column))
def highlight_breakpoints(self, filename, regular_breakpoints, conditional_breakpoints, temporary_breakpoints):
"""Highlights the active breakpoints in the given file."""
self.clear_breakpoints_highlighting()
self._set_lines_highlighting(regular_breakpoints, self.BREAKPOINT_GROUP)
self._set_lines_highlighting(conditional_breakpoints, self.CONDITIONAL_BREAKPOINT_GROUP)
self._set_lines_highlighting(temporary_breakpoints, self.TEMPORARY_BREAKPOINT_GROUP)
def highlight_current_line(self, filename, line):
"""Highlights the current debugged line."""
if (self.canonic(vim.current.buffer.name) != filename):
# Current buffer isn't the last debugged filename.
return
self.command(r'highlight link %s %s' % (self.CURRENT_LINE_GROUP, self.USER_DEFINED_CURRENT_LINE_GROUP))
self.command(r'match %s "\%%%dl.\+"' % (self.CURRENT_LINE_GROUP, line))
def clear_current_line_highlighting(self):
"""Clears the highlighting of the current debugged line."""
self.command(r'highlight link %s NONE' % (self.CURRENT_LINE_GROUP))
def clear_breakpoints_highlighting(self):
"""Clears the highlighting for the breakpoints."""
self.command(r'syntax clear %s' % (self.BREAKPOINT_GROUP))
self.command(r'syntax clear %s' % (self.CONDITIONAL_BREAKPOINT_GROUP))
self.command(r'syntax clear %s' % (self.TEMPORARY_BREAKPOINT_GROUP))
def open_file(self, filename):
"""Opens a file for editing."""
if (self.canonic(vim.current.buffer.name) != filename):
vim_filename = filename.replace(' ', r'\ ')
self.command('e ' + filename)
def get_active_filename(self):
"""Returns the filename of the active buffer."""
return vim.current.buffer.name.replace(r'\ ', ' ')
def set_cursor_to_current_line(self):
"""Moves the cursor to the current debugged line."""
self.open_file(self.current_filename)
self.set_cursor_position(self.current_line, 0)
#
# Queue related methods
#
def run_method(self, function_name, *parameters):
"""Runs a method (using add_queued_method) and waits for its output; then prints it onto the screen."""
self.output_buffer = None
self.save_to_output_buffer = True
self.add_queued_method(function_name, *parameters)
while (self.output_buffer == None):
time.sleep(self.PAUSE_DEBUG_WAIT_TIME)
self.save_to_output_buffer = False
self.print_message(self.output_buffer)
def run_method_and_return_output(self, function_name, *parameters):
"""Runs a method (using add_queued_method) and waits for it to finish running;
then returns its return value."""
self.save_to_output_buffer = False
self.last_method_return_value = None
self.add_queued_method(function_name, *parameters)
while (self.last_method_return_value == None):
time.sleep(self.PAUSE_DEBUG_WAIT_TIME)
return self.last_method_return_value
#
# Helper methods
#
def normal_command(self, command):
"""Runs a command in normal mode."""
self.command('normal ' + command)
def command(self, command):
"""Runs a Vim (ex-mode) command"""
vim.command(command)
def _set_lines_highlighting(self, line_numbers, group_name):
"""Sets highlighting for a group of line numbers (given a group name)."""
for line_number in line_numbers:
self.command(r'syntax match %s "\%%%dl.\+"' % (group_name, line_number))
# Old method - doesn't work for line #1, and when the previous line ends with a quotation mark
# of the end of a string, for example.
# Highlight each group of lines.
#for line_range in line_ranges:
# self.command(r'syntax region %s start="\%%%dl$" end="\%%%dl.\+"' %
# (group_name, line_range['start'] - 1, line_range['end']))