" (c) eric johnson
" email: vimDebug at iijo dot org
" http://iijo.org

" --------------------------------------------------------------------
" Check prerequisites.

if (!has('perl') || !has('signs'))
   echo "VimDebug requires +perl and +signs"
   finish
endif

" --------------------------------------------------------------------
" Configuration variables.

" Make sure all the values remain coherent if you change any.

   " The VimDebug start key. If this key is not already mapped in
   " normal mode (nmap), we will map it to start VimDebug. Otherwise,
   " to start the debugger one can call DBGRstart(...) or use the GUI
   " with its menu interface.
let s:cfg_startKey = "<F12>"

   " GUI menu label.
let s:cfg_menuLabel = '&Debugger'

   " Key bindings and menu settings. Each entry has: key, label, map.
let s:cfg_interface = [
 \ ['<F8>',       '&Next',                   'DBGRnext()'],
 \ ['<F7>',       '&Step in',                'DBGRstep()'],
 \ ['<F6>',       'Step &out',               'DBGRstepout()'],
 \ ['<F9>',       '&Continue',               'DBGRcont()'],
 \ ['<Leader>b',  'Set &breakpoint',         'DBGRsetBreakPoint()'],
 \ ['<Leader>c',  'C&lear breakpoint',       'DBGRclearBreakPoint()'],
 \ ['<Leader>ca', 'Clear &all breakpoints',  'DBGRclearAllBreakPoints()'],
 \ ['<Leader>x/', '&Print value',            'DBGRprint(inputdialog("Value to print: "))'],
 \ ['<Leader>x',  'Print &value here',       'DBGRprint(expand("<cword>"))'],
 \ ['<Leader>/',  'E&xecute command',        'DBGRcommand(inputdialog("Command to execute: "))'],
 \ ['<F10>',      '&Restart',                'DBGRrestart()'],
 \ ['<F11>',      '&Quit',                   'DBGRquit()'],
\]

   " Global variables. Each entry has: global variable name, default
   " value.
let s:cfg_globals = {
 \ 'g:DBGRconsoleHeight'  : 7,
 \ 'g:DBGRlineNumbers'    : 1,
 \ 'g:DBGRshowConsole'    : 1,
 \ 'g:DBGRdebugArgs'      : "",
\}

" --------------------------------------------------------------------
" This function will be called at the end of this script to
" initialize everything.

function! s:Initialize ()
   perl << EOT
         # Setting up 'lib' like this is useful during development.
      use Dir::Self;
      use lib __DIR__ . "/../../..";
         # Obtain protocol constant values directly from the Perl
         # module. This will allow us to use things like "s:k_eor" for
         # example in our Vim code.
      use Vim::Debug::Protocol;
      use Vim::Debug::Daemon;
      for my $method (qw<
         k_compilerError
         k_runtimeError
         k_dbgrReady
         k_appExited
         k_eor
         k_badCmd
         k_connect
         k_disconnect
         k_doneFile
      >) {
         VIM::DoCommand("let s:$method = '" . Vim::Debug::Protocol->$method . "'");
      }
         # Later perl snippets will use these variables.
      $DBGRsocket1 = 0;
      $DBGRsocket2 = 0;
      $EOM = Vim::Debug::Protocol->k_eom . "\r\n";
      $EOM_LEN = length $EOM;
      $PORT = Vim::Debug::Daemon->port;
EOT

      " Colors.
   hi currentLine term=reverse cterm=reverse gui=reverse
   hi breakPoint  term=NONE    cterm=NONE    gui=NONE
   hi empty       term=NONE    cterm=NONE    gui=NONE

      " Signs.
   sign define currentLine linehl=currentLine
   sign define breakPoint  linehl=breakPoint  text=>>
   sign define both        linehl=currentLine text=>>
   sign define empty       linehl=empty

      " Initialize globals to their default value, unless they already
      " have a value.
   for [l:var, l:dft_val] in items(s:cfg_globals)
      exec 
       \ "if ! exists('g:" . l:var . "') |" .
       \    "let " . l:var . " = '" . l:dft_val . "'| " .
       \ "endif"
   endfor

   " Script variables.

      " The string used to invoke the language's debugger.
   let s:incantation = ""

      " 0, the language's debugger is not running; 1, it is running.
   let s:dbgrIsRunning = 0

      " 0, a program is being debugged; 1, no program is being
      " debugged, or it has done running.
   let s:programDone = 1

      " Could eventually be some other debugger, but currently we
      " support only Perl.
   let s:debugger = "Perl"

   let s:consoleBufNr    = -99
   let s:bufNr           = 0
   let s:fileName        = ""
   let s:lineNumber      = 0
   let s:emptySigns      = []
   let s:breakPoints     = []
   let s:sessionId       = -1

   let s:interfaceSetting = 0

      " The user key bindings will be saved here if/when we launch
      " VimDebug. The entries of this list will be a bit different:
      " each one will be a two-element list of a key and of a
      " "saved-map" that will be provided by the 'savemap' vimscript.
   let s:userSavedkeys = []

      " Will be set to 1 (true) if the start key is defined
      " and we can map to it.
   let s:canMapStartKey = 0

   if s:cfg_startKey != "" && empty(maparg(s:cfg_startKey, "n"))
      let s:canMapStartKey = 1
   endif

      " Set up the start key and menus.
   call s:mapStartKey_DBGRstart()
   call s:VDmenuSet(0)

endfunction

" --------------------------------------------------------------------
" Debugger functions.

   " Start the debugger if it's not already running. If there is an
   " empty string argument, prompt for debugger arguments.
function! DBGRstart(...)
   if s:dbgrIsRunning
      echo "The debugger is already running."
      return
   endif
   try
      call s:Incantation(a:000)
      call s:StartVdd()
      " do after system() so nongui vim doesn't show a blank screen
      echo "\rstarting the debugger..."
      call s:SocketConnect()
      if has("autocmd")
         autocmd VimLeave * call DBGRquit()
      endif
      call DBGRopenConsole()
      redraw!
      call s:HandleCmdResult("connected to VimDebug daemon")
      call s:Handshake()
      call s:HandleCmdResult("started the debugger")
      call s:SocketConnect2()
      call s:HandleCmdResult2()
      call _VDsetInterface(1)
      call s:mapStartKey_toggleKeyBindings()
      let s:dbgrIsRunning = 1
      let s:programDone = 0
   catch /AbortLaunch/
      echo "Debugger launch aborted."
   catch /MissingVdd/
      echo "vdd is not in your PATH. Something went wrong with your VimDebug install."
   catch /.*/
      echo "Unexpected error: " . v:exception
   endtry
endfunction

function! DBGRnext()
   if !s:Copacetic()
      return
   endif
   echo "\rnext..."
   call s:SocketWrite("next")
   call s:HandleCmdResult()
endfunction

function! DBGRstep()
   if !s:Copacetic()
      return
   endif
   echo "\rstep..."
   call s:SocketWrite("step")
   call s:HandleCmdResult()
endfunction

function! DBGRstepout()
   if !s:Copacetic()
      return
   endif
   echo "\rstepout..."
   call s:SocketWrite("stepout")
   call s:HandleCmdResult()
endfunction

function! DBGRcont()
   if !s:Copacetic()
      return
   endif
   echo "\rcontinue..."
   call s:SocketWrite("cont")
   call s:HandleCmdResult()
endfunction

function! DBGRsetBreakPoint()
   if !s:Copacetic()
      return
   endif

   let l:currFileName = bufname("%")
   let l:bufNr        = bufnr("%")
   let l:currLineNr   = line(".")
   let l:id           = s:CreateId(l:bufNr, l:currLineNr)

   if count(s:breakPoints, l:id) == 1
      redraw! | echo "\rbreakpoint already set"
      return
   endif

   " tell vdd
   call s:SocketWrite("break:" . l:currLineNr . ':' . l:currFileName)

   call add(s:breakPoints, l:id)

   " check if a currentLine sign is already placed
   if (s:lineNumber == l:currLineNr)
      exe "sign unplace " . l:id
      exe "sign place " . l:id . " line=" . l:currLineNr . " name=both file=" . l:currFileName
   else
      exe "sign place " . l:id . " line=" . l:currLineNr . " name=breakPoint file=" . l:currFileName
   endif

   call s:HandleCmdResult("breakpoint set")
endfunction

function! DBGRclearBreakPoint()
   if !s:Copacetic()
      return
   endif

   let l:currFileName = bufname("%")
   let l:bufNr        = bufnr("%")
   let l:currLineNr   = line(".")
   let l:id           = s:CreateId(l:bufNr, l:currLineNr)

   if count(s:breakPoints, l:id) == 0
      redraw! | echo "\rno breakpoint set here"
      return
   endif

   " tell vdd
   call s:SocketWrite("clear:" . l:currLineNr . ':' . l:currFileName)

   call filter(s:breakPoints, 'v:val != l:id')
   exe "sign unplace " . l:id

   if(s:lineNumber == l:currLineNr)
      exe "sign place " . l:id . " line=" . l:currLineNr . " name=currentLine file=" . l:currFileName
   endif

   call s:HandleCmdResult("breakpoint disabled")
endfunction

function! DBGRclearAllBreakPoints()
   if !s:Copacetic()
      return
   endif

   call s:UnplaceBreakPointSigns()

   let l:currFileName = bufname("%")
   let l:bufNr        = bufnr("%")
   let l:currLineNr   = line(".")
   let l:id           = s:CreateId(l:bufNr, l:currLineNr)

   call s:SocketWrite("clearAll")

   " do this in case the last current line had a break point on it
   call s:UnplaceTheLastCurrentLineSign()                " unplace the old sign
   call s:PlaceCurrentLineSign(s:lineNumber, s:fileName) " place the new sign

   call s:HandleCmdResult("all breakpoints disabled")
endfunction

function! DBGRprint(...)
   if !s:Copacetic()
      return
   endif
   if a:0 > 0
      call s:SocketWrite("print:" . a:1)
      call s:HandleCmdResult()
   endif
endfunction

function! DBGRcommand(...)
   if !s:Copacetic()
      return
   endif
   echo ""
   if a:0 > 0
      call s:SocketWrite('command:' . a:1)
      call s:HandleCmdResult()
   endif
endfunction

function! DBGRrestart()
   if ! s:dbgrIsRunning
      echo "\rthe debugger is not running"
      return
   endif
   call s:SocketWrite("restart")
   " do after the system() call so that nongui vim doesn't show a blank screen
   echo "\rrestarting..."
   call s:UnplaceTheLastCurrentLineSign()
   redraw!
   call s:HandleCmdResult("restarted")
   let s:programDone = 0
endfunction

function! DBGRquit()
   if ! s:dbgrIsRunning
      echo "\rthe debugger is not running"
      return
   endif
   call _VDsetInterface(0)
   call s:mapStartKey_DBGRstart()

   " unplace all signs that were set in this debugging session
   call s:UnplaceBreakPointSigns()
   call s:UnplaceEmptySigns()
   call s:UnplaceTheLastCurrentLineSign()
   call s:SetNoNumber()

   call s:SocketWrite("quit")

   if has("autocmd")
     autocmd! VimLeave * call DBGRquit()
   endif

   " reinitialize script variables
   let s:lineNumber      = 0
   let s:fileName        = ""
   let s:bufNr           = 0
   let s:programDone     = 1

   let s:dbgrIsRunning = 0
   redraw! | echo "\rexited the debugger"

   " must do this last
   call DBGRcloseConsole()
endfunction

" --------------------------------------------------------------------
" Interface handling.

" These are the possible values of s:interfaceSetting, which tells us
" which key bindings are active and what the GUI menu looks like.
"
"  0 : User keys,     grayed out menu entries.
"  1 : VimDebug keys, active menu entries.
"  2 : User keys,     active menu entries, keys in  parentheses.

   " Request interface setting 0, 1, or 2, or 3 to toggle between 1
   " and 2.
function! _VDsetInterface(request)
   if a:request == 3
      if s:interfaceSetting == 0
         return
      endif
         " Toggle between 1 and 2.
      let l:want = 3 - s:interfaceSetting
   else
      let l:want = a:request
   endif

   if l:want == 0 || l:want == 2
      call s:VDrestoreKeyBindings()
   elseif l:want == 1
      call s:VDsetKeyBindings()
   else
      return
   endif

   call s:VDmenuSet(l:want)
   let s:interfaceSetting = l:want
endfunction

function! s:VDsetKeyBindings ()
   let s:userSavedkeys = []
   for l:data in s:cfg_interface
      let l:key = l:data[0]
      let l:map = l:data[2]
      call add(s:userSavedkeys, [l:key, savemap#save_map("n", l:key)])
      exec "nmap " . l:key . " :call " . l:map . "<cr>"
   endfor
   echo "VimDebug keys are active."
endfunction

function! s:VDrestoreKeyBindings ()
   for l:key_savedmap in s:userSavedkeys
      let l:key = l:key_savedmap[0]
      let l:saved_map = l:key_savedmap[1]
      if empty(l:saved_map['__map_info'][0]['normal'])
         exec "unmap " . l:key
      else
         call l:saved_map.restore()
      endif
   endfor
   let s:userSavedkeys = []
   echo "User keys are active."
endfunction

function! s:VDmenu_Start (on_or_off)
   if a:on_or_off == 1
      exec "amenu " . s:cfg_menuLabel . ".Start :call DBGRstart(\"\")<cr>"
   else
      exec "amenu disable " . s:cfg_menuLabel . ".Start"
   endif
endfunction

function! s:VDmenu_Toggle (on_or_off)
   if a:on_or_off == 1
      exec "amenu " . s:cfg_menuLabel . ".To&ggle\\ key\\ bindings :call _VDsetInterface(3)<cr>"
   else
      exec "amenu disable "  . s:cfg_menuLabel . ".To&ggle\\ key\\ bindings"
   endif
endfunction

   " Set up the GUI menu.
function! s:VDmenuSet (request)
   if ! has("gui_running")
      return
   endif
      " Delete the existing menu.
   try
      exec ":aunmenu " . s:cfg_menuLabel
   catch
   endtry

      " Insert the first three menu lines.
   call s:VDmenu_Start(1)
   call s:VDmenu_Toggle(1)
   exec "amenu ". s:cfg_menuLabel . ".-separ- :"
      " Disable the relevant one.
   if a:request == 0
      call s:VDmenu_Toggle(0)
   else
      call s:VDmenu_Start(0)
   endif

      " Build the other menu entries.
   for l:data in s:cfg_interface
      let l:key   = l:data[0]
      let l:label = l:data[1]
      let l:map   = l:data[2]
      let l:esc_label_key = escape(l:label . "\t" . l:key, " \t")
      try
         if a:request == 0
            exec "amenu disable " . s:cfg_menuLabel . "." . l:esc_label_key
         elseif a:request == 1
            exec "amenu " . s:cfg_menuLabel . "." . l:esc_label_key . " :call " . l:map . "<cr>"
         else
            let l:esc_label_no_key = escape(l:label . "\t(" . l:key . ")", " \t")
            exec "amenu " . s:cfg_menuLabel . "." . l:esc_label_no_key . " :call " . l:map . "<cr>"
         endif
      catch
      endtry
   endfor
endfunction

function! s:mapStartKey_DBGRstart ()
   if s:canMapStartKey
      exec "nmap " . s:cfg_startKey . " :call DBGRstart(\"\")<cr>"
   endif
endfunction

function! s:mapStartKey_toggleKeyBindings ()
   if s:canMapStartKey
      exec "nmap " . s:cfg_startKey . " :call _VDsetInterface(3)<cr>"
   endif
endfunction

" --------------------------------------------------------------------
" User commands.

command! -nargs=* VDstart      call DBGRstart(<f-args>)
command! -nargs=0 VDtoggleKeys call _VDsetInterface(3)

" --------------------------------------------------------------------
" Utility functions.

   " Returns 1 if everything is copacetic, 0 otherwise.
function! s:Copacetic()
   if s:dbgrIsRunning != 1
      echo "\rthe debugger is not running"
      return 0
   elseif s:programDone
      echo "\rthe application being debugged terminated"
      return 0
   endif
   return 1
endfunction

function! s:PlaceEmptySign()
   let l:id = s:CreateId(bufnr("%"), "1")
   if count(s:emptySigns, l:id) == 0
      let l:fileName = bufname("%")
      call add(s:emptySigns, l:id)
      exe "sign place " . l:id . " line=1 name=empty file=" . l:fileName
   endif
endfunction

function! s:UnplaceEmptySigns()
   let l:oldBufNr = bufnr("%")
   for l:id in s:emptySigns
      let l:bufNr = s:BufNrFromId(l:id)
      if bufexists(l:bufNr) != 0
         if bufnr("%") != l:bufNr
            exe "buffer " . l:bufNr
         endif
         exe "sign unplace " . l:id
         exe "buffer " . l:oldBufNr
      endif
   endfor
   let s:emptySigns = []
endfunction

function! s:UnplaceBreakPointSigns()
   let l:oldBufNr = bufnr("%")
   for l:id in s:breakPoints
      let l:bufNr = s:BufNrFromId(l:id)
      if bufexists(l:bufNr) != 0
         if bufnr("%") != l:bufNr
            exe "buffer " . l:bufNr
         endif
         exe "sign unplace " . l:id
         exe "buffer " . l:oldBufNr
      endif
   endfor
   let s:breakPoints = []
endfunction

function! s:SetNumber()
   if g:DBGRlineNumbers == 1
      set number
   endif
endfunction

function! s:SetNoNumber()
   if g:DBGRlineNumbers == 1
      set nonumber
   endif
endfunction

function! s:CreateId(bufNr, lineNumber)
   return a:bufNr * 10000000 + a:lineNumber
endfunction

function! s:BufNrFromId(id)
   return a:id / 10000000
endfunction

function! s:LineNrFromId(id)
   return a:id % 10000000
endfunction

function! s:Incantation(dbgr_args_list)
   try
      let s:bufNr       = bufnr("%")
      let s:fileName    = bufname("%")
      if s:fileName == ""
         throw "NoFileToDebug"
      endif
      let l:nb_dbgr_args = len(a:dbgr_args_list)
      let g:DBGRdebugArgs =
       \ l:nb_dbgr_args == 0
       \ ? ""
       \ : l:nb_dbgr_args == 1 && a:dbgr_args_list[0] == ""
       \ ? inputdialog("Enter arguments for debugging, if any: ", g:DBGRdebugArgs)
       \ : join(a:dbgr_args_list)
      let s:incantation = "perl -Ilib -d " . s:fileName
      if g:DBGRdebugArgs != ""
         let s:incantation .= " " . g:DBGRdebugArgs
      endif
   catch /NoFileToDebug/
      echo "No file to debug."
      throw "AbortLaunch"
   catch
      echo "Exception caught: " . v:exception
      throw "AbortLaunch"
   endtry
endfunction

function! s:HandleCmdResult(...)
   let l:cmdResult  = split(s:SocketRead(), s:k_eor, 1)
   let [l:status, l:lineNumber, l:fileName, l:value, l:output] = l:cmdResult

   if l:status == s:k_dbgrReady
      call s:ConsolePrint(l:output)
      if len(l:lineNumber) > 0
         call s:CurrentLineMagic(l:lineNumber, l:fileName)
      endif

   elseif l:status == s:k_appExited
      call s:ConsolePrint(l:output)
      call s:HandleProgramTermination()
      redraw! | echo "The application being debugged terminated."

   elseif l:status == s:k_compilerError
      call s:ConsolePrint(l:output)
      call s:HandleProgramTermination()
      redraw! | echo "The program did not compile."

   elseif l:status == s:k_runtimeError
      call s:ConsolePrint(l:output)
      call s:HandleProgramTermination()
      redraw! | echo "There was a runtime error."

   elseif l:status == s:k_connect
      let s:sessionId = l:value

   elseif l:status == s:k_disconnect
      echo "disconnected"

   else
      echo " error:001. Something bad happened. Please report this to vimdebug at iijo dot org"
      echo got
   endif

   return
endfunction

function! s:HandleCmdResult2(...)
   let l:foo = s:SocketRead2()
endfunction

" - jumps to the lineNumber in the file, fileName
" - highlights the current line
" - returns nothing
function! s:CurrentLineMagic(lineNumber, fileName)

   let l:lineNumber = a:lineNumber
   let l:fileName   = a:fileName
   let l:fileName   = s:JumpToLine(l:lineNumber, l:fileName)

   " if no signs placed in this file, place an invisible one on line 1.
   " otherwise, the code will shift left when the old currentline sign is
   " unplaced and then shift right again when the new currentline sign is
   " placed.  and thats really annoying for the user.
   call s:PlaceEmptySign()
   call s:UnplaceTheLastCurrentLineSign()                " unplace the old sign
   call s:PlaceCurrentLineSign(l:lineNumber, l:fileName) " place the new sign
   call s:SetNumber()
   "z. " scroll page so that this line is in the middle

   " set script variables for next time
   let s:lineNumber = l:lineNumber
   let s:fileName   = l:fileName

   return
endfunction

" the fileName may have been changed if we stepped into a library or some
" other piece of code in an another file.  load the new file if thats
" necessary and then jump to lineNumber
"
" returns a fileName.
function! s:JumpToLine(lineNumber, fileName)
   let l:fileName = a:fileName

   " no buffer with this file has been loaded
   if !bufexists(bufname(l:fileName))
      exe ":e! " . l:fileName
   endif

   let l:winNr = bufwinnr(bufnr(l:fileName))
   if l:winNr != -1
      exe l:winNr . "wincmd w"
   endif

   " make a:fileName the current buffer
   if bufname(l:fileName) != bufname("%")
      exe ":buffer " . bufnr(l:fileName)
   endif

   " jump to line
   exe ":" . a:lineNumber
   normal z.
   if foldlevel(a:lineNumber) != 0
      normal zo
   endif

   return bufname(l:fileName)
endfunction

function! s:UnplaceTheLastCurrentLineSign()
   let l:lastId = s:CreateId(s:bufNr, s:lineNumber)
   exe 'sign unplace ' . l:lastId
   if count(s:breakPoints, l:lastId) == 1
      exe "sign place " . l:lastId . " line=" . s:lineNumber . " name=breakPoint file=" . s:fileName
   endif
endfunction

function! s:PlaceCurrentLineSign(lineNumber, fileName)
   let l:bufNr = bufnr(a:fileName)
   let s:bufNr = l:bufNr
   let l:id    = s:CreateId(l:bufNr, a:lineNumber)

   if count(s:breakPoints, l:id) == 1
      exe "sign place " . l:id .
        \ " line=" . a:lineNumber . " name=both file=" . a:fileName
   else
      exe "sign place " . l:id .
        \ " line=" . a:lineNumber . " name=currentLine file=" . a:fileName
   endif
endfunction

function! s:HandleProgramTermination()
   call s:UnplaceTheLastCurrentLineSign()
   let s:lineNumber  = 0
   let s:bufNr       = 0
   let s:programDone = 1
endfunction

" --------------------------------------------------------------------
" Debugger console functions.

function! DBGRopenConsole()
   if g:DBGRshowConsole == 0
      return 0
   endif
   new "debugger console"
   let s:consoleBufNr = bufnr('%')
   exe "resize " . g:DBGRconsoleHeight
   exe "sign place 9999 line=1 name=empty buffer=" . s:consoleBufNr
   call s:SetNumber()
   set buftype=nofile
   wincmd p
endfunction

function! DBGRcloseConsole()
   if g:DBGRshowConsole == 0
      return 0
   endif
   let l:consoleWinNr = bufwinnr(s:consoleBufNr)
   if l:consoleWinNr == -1
      return
   endif
   exe l:consoleWinNr . "wincmd w"
   q
endfunction

function! s:ConsolePrint(msg)
   if g:DBGRshowConsole == 0
      return 0
   endif
   let l:consoleWinNr = bufwinnr(s:consoleBufNr)
   if l:consoleWinNr == -1
      "call confirm(a:msg, "&Ok")
      call DBGRopenConsole()
      let l:consoleWinNr = bufwinnr(s:consoleBufNr)
   endif
   silent exe l:consoleWinNr . "wincmd w"
   let l:oldValue = @x
   let @x = a:msg
   silent exe 'normal G$"xp'
   let @x = l:oldValue
   normal G
   wincmd p
endfunction

" --------------------------------------------------------------------
" Socket functions.

function! s:StartVdd()
   if !executable('vdd')
      throw "MissingVdd"
   endif
   exec "silent :! vdd &"
endfunction

function! s:Handshake()
    let l:msg  = "start:" . s:sessionId .
               \      ":" . s:debugger .
               \      ":" . s:incantation
    call s:SocketWrite(l:msg)
endfunction

function! s:SocketConnect()
   perl << EOF
      use IO::Socket;
      foreach my $i (0..9) {
         $DBGRsocket1 = IO::Socket::INET->new(
            Proto    => "tcp",
            PeerAddr => "localhost",
            PeerPort => $PORT,
         );
         return if defined $DBGRsocket1;
         sleep 1;
      }
      my $msg = "cannot connect to port $PORT at localhost";
      VIM::Msg($msg);
      VIM::DoCommand("throw '${msg}'");
EOF
endfunction

function! s:SocketConnect2()
   perl << EOF
      use IO::Socket;
      foreach my $i (0..9) {
         $DBGRsocket2 = IO::Socket::INET->new(
            Proto    => "tcp",
            PeerAddr => "localhost",
            PeerPort => $PORT,
         );
         return if defined $DBGRsocket2;
         sleep 1;
      }
      my $msg = "cannot connect to port $PORT at localhost";
      VIM::Msg($msg);
      VIM::DoCommand("throw '${msg}'");
EOF
endfunction

function! s:SocketRead()
   try
      " yeah this is a very inefficient but non blocking loop.
      " vdd signals that its done sending a msg when it touches the file.
      " while VimDebug thinks, the user can cancel their operation.
      while !filereadable(s:k_doneFile)
      endwhile
   catch /Vim:Interrupt/
      echom "action cancelled"
      call s:SocketWrite2('stop:' . s:sessionId)  " disconnect
      call s:HandleCmdResult2()                   " handle disconnect
      call s:SocketConnect2()                     " reconnect
      call s:HandleCmdResult2()                   " handle reconnect
   endtry

   perl << EOF
      my $data = '';
      $data .= <$DBGRsocket1> until substr($data, -1 * $EOM_LEN) eq $EOM;
      $data .= <$DBGRsocket1> until substr($data, -1 * $EOM_LEN) eq $EOM;
      $data = substr($data, 0, -1 * $EOM_LEN); # chop EOM
      $data =~ s|'|''|g; # escape single quotes '
      VIM::DoCommand("call delete(s:k_doneFile)");
      VIM::DoCommand("return '" . $data . "'");
EOF
endfunction

function! s:SocketRead2()
   try
      " yeah this is a very inefficient but non blocking loop.
      " vdd signals that its done sending a msg when it touches the file.
      " while VimDebug thinks, the user can cancel their operation.
      while !filereadable(s:k_doneFile)
      endwhile
   endtry

   perl << EOF
      my $data = '';
      $data .= <$DBGRsocket2> until substr($data, -1 * $EOM_LEN) eq $EOM;
      $data .= <$DBGRsocket2> until substr($data, -1 * $EOM_LEN) eq $EOM;
      $data = substr($data, 0, -1 * $EOM_LEN); # chop EOM
      $data =~ s|'|''|g; # escape single quotes '
      VIM::DoCommand("call delete(s:k_doneFile)");
      VIM::DoCommand("return '" . $data . "'");
EOF
endfunction

function! s:SocketWrite(data)
   perl print $DBGRsocket1 VIM::Eval('a:data') . "\n";
endfunction

function! s:SocketWrite2(data)
   perl print $DBGRsocket2 VIM::Eval('a:data') . "\n";
endfunction

" --------------------------------------------------------------------
" Initialize everything.

call s:Initialize()