"
" VimPdb.vim
"
" Intergrates a Python debugger into Vim in an IDE-like fashion.
"
" Author:
" 	Yaron Budowski
"




"
" Initialization code
"
"
try
    let current_dir = expand("<sfile>:h")
    python import sys
    exe 'python sys.path.insert(0, r"' . current_dir . '")'
    python import VimPdb
catch
    map <buffer> <silent> <F5> :echoerr 'No python -> no debugger ...'<CR>
    finish
endtry


function! PdbInitialize()
	" Initializes the VimPdb pluging.

	au BufLeave *.py :call PdbBuffLeave()
	au BufEnter *.py :call PdbBuffEnter()
	au BufEnter *.py :call PdbMapKeyboard()
	au VimLeave *.py :call PdbStopDebug()

	call PdbMapKeyboard()

	let current_dir = expand("<sfile>:h")
	python import sys
	exe 'python sys.path.insert(0, r"' . current_dir . '")'
	python import VimPdb

	python << EOF
import vim
import threading
import time
import re

reload(VimPdb)


# The VimPdb instance used for debugging.
vim_pdb = VimPdb.VimPdb()
vim_pdb.stack_entry_format = vim.eval('g:stack_entry_format')
vim_pdb.stack_entry_prefix = vim.eval('g:stack_entry_prefix')
vim_pdb.current_stack_entry_prefix = vim.eval('g:current_stack_entry_prefix')
vim_pdb.stack_entries_joiner = vim.eval('g:stack_entries_joiner')


def vim_pdb_start_debug(stop_immediately, args):
	global vim_pdb
	vim_pdb.start_debugging(vim.current.buffer.name, stop_immediately, args)


def parse_command_line(line):
	"""Parses command line."""
	args = []
	while (len(line) > 0):
		if (line[0] == '"'):
			next_quotation_mark = line.find('"', 1)
			if (next_quotation_mark == -1):
				# No ending quotation mark found.
				line = line[1:]
				continue

			# Treat anything between the two quotation marks as one argument.
			args.append(line[1:next_quotation_mark])
			line = line[next_quotation_mark + 1:]
			continue

		match = re.search('\s+', line)
		if (not match):
			# No whitespace found - save the argument until the end of the line.
			args.append(line)
			line = ""
			continue
		if (match.start() == 0):
			# Whitespace in the beginning of the line - skip it.
			line = line[match.end():]
			continue

		args.append(line[:match.start()])
		line = line[match.end():]

	return args
EOF

endfunction



"
" Vim event related functions
"

function! PdbBuffLeave()
	" Used when leaving the current buffer - clear all highlighting.

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('clear_current_line_highlighting')
	vim_pdb.add_queued_method('clear_breakpoints_highlighting')
EOF
endfunction

function! PdbBuffEnter()
	" Used when entering a new buffer - highlighting all breakpoints, etc (if there are any).

	python <<EOF
if (vim_pdb.is_debugged()):
	file('out.txt', 'a').write('BuffEnter\n')
	vim_pdb.add_queued_method('highlight_current_line_for_file', vim.current.buffer.name)
	vim_pdb.add_queued_method('highlight_breakpoints_for_file', vim.current.buffer.name)
EOF
endfunction


"
" Start\Stop debugging functions
"


function! PdbStartDebug(stop_immediately, args)
	" Start a debugging session for the current buffer.
	
	python << EOF
if ((not vim_pdb) or (not vim_pdb.is_debugged())):
	# Start a new VimPdb debugging thread (so Vim won't get halted).
	stop_immediately = bool(int(vim.eval('a:stop_immediately')))
	args = list(vim.eval('a:args'))
	vim_pdb_thread = threading.Thread(target = vim_pdb_start_debug, args = (stop_immediately, args))
	vim_pdb_thread.setDaemon(False)
	vim_pdb_thread.start()
else:
	# Just continue the debugging.
	vim_pdb.add_queued_method('do_continue')
EOF


	if (g:auto_load_breakpoints_file == 1)
		" Load the default breakpoints file at the beginning of the
		" debugging session.
		call PdbLoadSavedBreakpoints(g:default_breakpoints_filename)
	endif
endfunction

function! PdbStartDebugWithArguments()
	" Start a debugging session for the current buffer, with a list of
	" arguments given by the user.
	
	python << EOF
# Get the arguments from the user.
command_line = vim.eval('input("Arguments: ")')

if (command_line is not None):
	# Parse the arguments.
	args = parse_command_line(command_line)

	if (not vim_pdb):
		vim.command('call PdbStartDebug(1, %s)' % (args))
	else:
		# TODO - special case?
		if (vim_pdb.is_debugged()):
			# Stop the existing debugging session.
			vim.command('call PdbStopDebug()')
			vim.command('call PdbInitialize()')

		vim.command('call PdbStartDebug(1, %s)' % (args))

EOF
endfunction


function! PdbStopDebug()
	" Stops an active debugging session.

	if (g:auto_save_breakpoints_file == 1)
		" Save to the default breakpoints file at the end of the
		" debugging session.
		call PdbSaveSavedBreakpoints(g:default_breakpoints_filename)
	endif
	
	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('stop_debugging')

	# Wait until the thread terminates.
	while (vim_pdb_thread.isAlive()):
		time.sleep(0.1)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF


endfunction

function! PdbRestartDebug()
	" Restarts a debugging session.
	call PdbStopDebug()
	call PdbStartDebug(1, [])
endfunction


"
" Saving\Loading breakpoints methods
"


function! PdbLoadSavedBreakpoints(...)
	" Loads saved breakpoints from a file.

	python <<EOF
if (vim_pdb.is_debugged()):
	if (int(vim.eval('a:0')) == 0):
		filename = vim.eval('input("Filename: ")')
	else:
		filename = vim.eval('a:1')

	if (filename is not None):
		vim_pdb.add_queued_method('load_breakpoints_from_file', filename)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE

EOF
endfunction


function! PdbSaveSavedBreakpoints(...)
	" Saves saved breakpoints to a file.

	python <<EOF
if (vim_pdb.is_debugged()):
	if (int(vim.eval('a:0')) == 0):
		filename = vim.eval('input("Filename: ")')
	else:
		filename = vim.eval('a:1')

	if (filename is not None):
		vim_pdb.add_queued_method('save_breakpoints_to_file', filename)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE

EOF
endfunction




"
" Deubgging methods
"


function! PdbContinue()
	" Continues a debugging session.

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_continue')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbStepInto()
	" Performs a step into

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_step_into')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbStepOver()
	" Performs a step over

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_step_over')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbContinueUntilReturn()
	" Performs continue until returning

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_continue_until_return')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction


function! PdbJumpToCurrentLine()
	" Jumps to the specified current line.

	python <<EOF
if (vim_pdb.is_debugged()):
	line_number = int(vim.eval('line(".")'))
	vim_pdb.add_queued_method('do_jump', vim.current.buffer.name, line_number)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction


function! PdbMoveUpInStackFrame()
	" Moves up one level in the stack frame.

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_move_up_in_stack_frame')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbMoveDownInStackFrame()
	" Moves down one level in the stack frame.

	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_move_down_in_stack_frame')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction


function! PdbToggleBreakpointOnCurrentLine()
	" Toggles breakpoint on the current line.

	python << EOF
if (vim_pdb.is_debugged()):
	line_number = int(vim.eval('line(".")'))
	vim_pdb.add_queued_method('do_toggle_breakpoint', vim.current.buffer.name, line_number)
	vim_pdb.add_queued_method('highlight_breakpoints_for_file', vim.current.buffer.name)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbToggleConditionalBreakpointOnCurrentLine()
	" Toggles a conditional breakpoint on the current line.

	python << EOF
if (vim_pdb.is_debugged()):
	line_number = int(vim.eval('line(".")'))

	if ((not vim_pdb.run_method_and_return_output('is_breakpoint_enabled', vim.current.buffer.name, line_number)) and
		(vim_pdb.run_method_and_return_output('is_code_line', vim.current.buffer.name, line_number))):
		condition = vim.eval('input("Condition: ")')

		if ((condition is not None) and (len(condition.strip()) > 0)):
			vim_pdb.add_queued_method('do_toggle_breakpoint', vim.current.buffer.name, line_number, condition.strip())
	else:
		condition = None
		vim_pdb.add_queued_method('do_toggle_breakpoint', vim.current.buffer.name, line_number, condition)

else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbToggleTemporaryBreakpointOnCurrentLine()
	" Toggles a temporary breakpoint on the current line.

	python << EOF
if (vim_pdb.is_debugged()):
	line_number = int(vim.eval('line(".")'))
	vim_pdb.add_queued_method('do_toggle_breakpoint', vim.current.buffer.name, line_number, None, True)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction



function PdbClearAllBreakpointsInCurrentFile()
	" Clears all breakpoints in the current file.

	python << EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_clear_all_breakpoints', vim.current.buffer.name)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function PdbClearAllBreakpoints()
	" Clears all breakpoints in all files.

	python << EOF
if (vim_pdb.is_debugged()):
	vim_pdb.add_queued_method('do_clear_all_breakpoints')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction


function! PdbPrintBreakpointConditionOnCurrentLine()
	" Prints the condition of the conditional breakpoint in the current line.

	python << EOF
if (vim_pdb.is_debugged()):
	line_number = int(vim.eval('line(".")'))

	print vim_pdb.run_method('do_print_breakpoint_condition', vim.current.buffer.name, line_number)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction



function! PdbEvalCurrentWord()
	" Evals the word currently under the cursor.

	python <<EOF
if (vim_pdb.is_debugged()):
	current_word = vim.eval('expand("<cword>")')

	if ((current_word is not None) and (len(current_word.strip()) > 0)):
		vim_pdb.run_method('do_eval', current_word)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbEvalCurrentWORD()
	" Evals the WORD currently under the cursor.

	python <<EOF
if (vim_pdb.is_debugged()):
	current_word = vim.eval('expand("<cWORD>")')

	if ((current_word is not None) and (len(current_word.strip()) > 0)):
		vim_pdb.run_method('do_eval', current_word)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction

function! PdbEvalExpression()
	" Evals an expression given by the user.

	python <<EOF
if (vim_pdb.is_debugged()):
	expression = vim.eval('input("Eval Expression: ")')
	if (expression is not None):
		vim_pdb.run_method('do_eval', expression)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction


function! PdbExecStatement()
	" Execs a statement given by the user.

	python <<EOF
if (vim_pdb.is_debugged()):
	statement = vim.eval('input("Exec Statement: ")')
	if (statement is not None):
		vim_pdb.run_method('do_exec', statement)
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction


function! PdbPrintStackTrace()
	" Prints the current stack trace.
	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.run_method('do_print_stack_trace')
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction



function! PdbSetFocusToCurrentDebugLine()
	" Moves the cursor to the currently debugged line.
	python <<EOF
if (vim_pdb.is_debugged()):
	vim_pdb.set_cursor_to_current_line()
else:
	print VimPdb.VimPdb.MESSAGE_NOT_IN_DEBUG_MODE
EOF
endfunction



" ==========
" EDIT HERE
" ==========



"
" Line highlighting
"


highlight PdbCurrentLine guibg=DarkGreen
highlight PdbBreakpoint guibg=DarkRed
highlight PdbConditionalBreakpoint guibg=Purple
highlight PdbTemporaryBreakpoint guibg=SlateBlue


function! PdbMapKeyboard()
	"
	" Keyboard shortcuts
	"

	map <buffer> <silent> <F5> :call PdbStartDebug(1, [])<CR>
	" Start debug and don't pause immediately.
	map <buffer> <silent> <C-F5> :call PdbStartDebug(0, [])<CR>
	map <buffer> <silent> <C-S-F5> :call PdbStartDebugWithArguments()<CR>
	map <buffer> <silent> <S-F5> :call PdbStopDebug()<CR>
	map <buffer> <silent> <C-A-S-F5> :call PdbRestartDebug()<CR>

	map <buffer> <silent> <LocalLeader>l :call PdbLoadSavedBreakpoints()<CR>
	map <buffer> <silent> <LocalLeader>s :call PdbSaveSavedBreakpoints()<CR>

	map <buffer> <silent> <F7> :call PdbStepInto()<CR>
	map <buffer> <silent> <F8> :call PdbStepOver()<CR>
	map <buffer> <silent> <C-F8> :call PdbContinueUntilReturn()<CR>

	map <buffer> <silent> <F9> :call PdbMoveUpInStackFrame()<CR>
	map <buffer> <silent> <F10> :call PdbMoveDownInStackFrame()<CR>

	map <buffer> <silent> <F6> :call PdbSetFocusToCurrentDebugLine()<CR>
	map <buffer> <silent> <C-F6> :call PdbJumpToCurrentLine()<CR>

	map <buffer> <silent> <F2> :call PdbToggleBreakpointOnCurrentLine()<CR>
	map <buffer> <silent> <C-F2> :call PdbToggleConditionalBreakpointOnCurrentLine()<CR>
	map <buffer> <silent> <S-F2> :call PdbToggleTemporaryBreakpointOnCurrentLine()<CR>
	map <buffer> <silent> <C-S-F2> :call PdbClearAllBreakpointsInCurrentFile()<CR>
	map <buffer> <silent> <C-A-S-F2> :call PdbClearAllBreakpoints()<CR>

	map <buffer> <silent> <F11> :call PdbPrintBreakpointConditionOnCurrentLine()<CR>

	map <buffer> <silent> <F4> :call PdbEvalCurrentWord()<CR>
	map <buffer> <silent> <C-F4> :call PdbEvalCurrentWORD()<CR>

	map <buffer> <silent> <F3> :call PdbEvalExpression()<CR>
	map <buffer> <silent> <C-F3> :call PdbExecStatement()<CR>

	map <buffer> <silent> <F12> :call PdbPrintStackTrace()<CR>
endfunction


" The format string for displaying a stack entry.
let g:stack_entry_format = "%(dir)s\\%(filename)s (%(line)d): %(function)s(%(args)s) %(return_value)s %(source_line)s"
" The string used to join stack entries together.
let g:stack_entries_joiner = " ==>\n"
" The prefix to each stack entry - 'regular' and current stack entry.
let g:stack_entry_prefix = "  "
let g:current_stack_entry_prefix = "* "

" Should VimPdb look for saved breakpoints file when starting a debug session?
let g:auto_load_breakpoints_file = 0
" Should VimPdb save the breakpoints file when stopping the debug session?
let g:auto_save_breakpoints_file = 0
" The name of the default saved breakpoints file (in the currently debugged directory).
" Used when auto_load_breakpoints_file/auto_save_breakpoints_file are turned on.
let g:default_breakpoints_filename = "bplist.vpb"



"
" Main code
"


call PdbInitialize()

