"""
	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']))

