" Visual Mark
" 2005-10-27, brian wang
"
" Acknowledgements:
"   - Thanks to Hari Krishna Dara's genutils.vim (http://vim.sourceforge.net/scripts/script.php?script_id=197)
"   - Thanks to Mr. Charles E. Campbell, Jr. for making this script more plugin-like :-)
"   - Thanks to Mr. Charles E. Campbell, Jr. for making this script adapt to
"     dark/light backgrounds
"   - Thanks to Evgeny Filatov for noticing a nasty bug in Vm_get_line_number :-)

if exists("loaded_VisualMark")
  finish
endif
let loaded_VisualMark = 1
if !has("signs")
 echoerr "***sorry*** [".expand("%")."] your vim doesn't support signs"
 finish
endif

if &bg == "dark"
 highlight SignColor ctermfg=white ctermbg=blue guifg=white guibg=RoyalBlue3
else
 highlight SignColor ctermbg=white ctermfg=blue guibg=grey guifg=RoyalBlue3
endif

" ---------------------------------------------------------------------
"  Public Interface:
if !hasmapto('<Plug>Vm_toggle_sign')
  map <unique> <c-F2> <Plug>Vm_toggle_sign
  map <silent> <unique> mm <Plug>Vm_toggle_sign 
endif
nnoremap <silent> <script> <Plug>Vm_toggle_sign	:call Vm_toggle_sign()<cr>

if !hasmapto('<Plug>Vm_goto_next_sign')
  map <unique> <F2> <Plug>Vm_goto_next_sign
endif
nnoremap <silent> <script> <Plug>Vm_goto_next_sign	:call Vm_goto_next_sign()<cr>

if !hasmapto('<Plug>Vm_goto_prev_sign')
  map <unique> <s-F2> <Plug>Vm_goto_prev_sign
endif
nnoremap <silent> <script> <Plug>Vm_goto_prev_sign	:call Vm_goto_prev_sign()<cr>

" ---------------------------------------------------------------------
"  GetVimCmdOutput:
" Stole from Hari Krishna Dara's genutils.vim (http://vim.sourceforge.net/scripts/script.php?script_id=197)
"  to ease the scripts dependency issue
fun! s:GetVimCmdOutput(cmd)
"  call Dfunc("GetVimCmdOutput(cmd.".a:cmd.">)")

  " Save the original locale setting for the messages
  let old_lang = v:lang

  " Set the language to English
  exec ":lan mes en_US"

  let v:errmsg = ''
  let output   = ''
  let _z       = @z

  try
    redir @z
    silent exe a:cmd
  catch /.*/
    let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '')
  finally
    redir END
    if v:errmsg == ''
      let output = @z
    endif
    let @z = _z
  endtry

  " Restore the original locale
  exec ":lan mes " . old_lang

"  call Dret("GetVimCmdOutput <".output.">")
  return output
endfun

" ---------------------------------------------------------------------
"  Vm_place_sign:
fun! s:Vm_place_sign()
"  call Dfunc("Vm_place_sign()")

  if !exists("b:Vm_sign_number")
    let b:Vm_sign_number = 1
  endif

  let ln = line(".")

  exe 'sign define SignSymbol linehl=SignColor texthl=SignColor'
  exe 'sign place ' . b:Vm_sign_number . ' line=' . ln . ' name=SignSymbol buffer=' . winbufnr(0)

  let vsn              = b:Vm_sign_number
  let b:Vm_sign_number = b:Vm_sign_number + 1

"  call Dret("Vm_place_sign : sign#".vsn." line#".ln." buf#".winbufnr(0))
endfun

" ---------------------------------------------------------------------
" Vm_remove_sign:
fun! s:Vm_remove_sign(sign_id)
"  call Dfunc("Vm_remove_sign(sign_id=".a:sign_id.")")
  silent! exe 'sign unplace ' . a:sign_id . ' buffer=' . winbufnr(0)
"  call Dret("Vm_remove_sign")
endfun

" ---------------------------------------------------------------------
" Vm_remove_all_signs:
fun! s:Vm_remove_all_signs()
"  call Dfunc("Vm_remove_all_signs()")
  silent! exe 'sign unplace *'
"  call Dret("Vm_remove_all_signs")
endfun

" ---------------------------------------------------------------------
" Vm_get_sign_id_from_line:
fun! s:Vm_get_sign_id_from_line(line_number)
"  call Dfunc("Vm_get_sign_id_from_line(line_number=".a:line_number.")")

  let sign_list = s:GetVimCmdOutput('sign place buffer=' . winbufnr(0))
"  call Decho(sign_list)

  let line_str_index = match(sign_list, "line=" . a:line_number, 0)
  if line_str_index < 0
"    call Dret("Vm_get_sign_id_from_line -1")
    return -1
  endif

  let id_str_index = matchend(sign_list, "id=", line_str_index)
"  let tmp = strpart(sign_list, id_str_index, 10)   "Decho
"  call Decho("ID str index: " . tmp)
  if id_str_index < 0
"    call Dret("Vm_get_sign_id_from_line -1")
    return -1
  endif

  let space_index = match(sign_list, " ", id_str_index)
  let id          = strpart(sign_list, id_str_index, space_index - id_str_index)

"  call Dret("Vm_get_sign_id_from_line ".id)
  return id
endfun

" ---------------------------------------------------------------------
" Vm_toggle_sign:
fun! Vm_toggle_sign()
"  call Dfunc("Vm_toggle_sign()")

  let curr_line_number = line(".")
  let sign_id          = s:Vm_get_sign_id_from_line(curr_line_number)

  if sign_id < 0
    let is_on = 0
  else
    let is_on = 1
  endif

  if (is_on != 0)
    call s:Vm_remove_sign(sign_id)
  else
    call s:Vm_place_sign()
  endif

"  call Dret("Vm_toggle_sign")
endfun

" ---------------------------------------------------------------------
" Vm_get_line_number:
fun! s:Vm_get_line_number(string)
"  call Dfunc("Vm_get_line_number(string<".a:string.">)")

  let line_str_index = match(a:string, "line=", b:Vm_start_from)
  if line_str_index <= 0
"    call Dret("Vm_get_line_number -1")
    return -1
  endif

  let equal_sign_index = match(a:string, "=", line_str_index)
  let space_index      = match(a:string, " ", equal_sign_index)
  let line_number      = strpart(a:string, equal_sign_index + 1, space_index - equal_sign_index - 1)
  let b:Vm_start_from  = space_index

"  call Dret("Vm_get_line_number ".line_number." : =indx:".equal_sign_index." _indx=".space_index)
  return line_number + 0
endfun

" ---------------------------------------------------------------------
" Vm_get_next_sign_line:
fun! s:Vm_get_next_sign_line(curr_line_number)
  " call Dfunc("Vm_get_next_sign_line(curr_line_number=".a:curr_line_number.">)")

  let b:Vm_start_from = 1
  let sign_list = s:GetVimCmdOutput('sign place buffer=' . winbufnr(0))
  " call Decho("sign_list<".sign_list.">")

  let curr_line_number = a:curr_line_number
  let line_number = 1
  let is_no_sign  = 1
  let min_line_number = -1
  let min_line_number_diff = 0
  
  while 1
    let line_number = s:Vm_get_line_number(sign_list)
    if line_number < 0
      break
    endif

    " Record the very first line that has a sign
    if is_no_sign != 0 
      let min_line_number = line_number
    elseif line_number < min_line_number
      let min_line_number = line_number
    endif
    let is_no_sign = 0

    " let tmp_diff = curr_line_number - line_number
    let tmp_diff = line_number - curr_line_number
    if tmp_diff > 0
      " line_number is below curr_line_number
      if min_line_number_diff > 0 
        if tmp_diff < min_line_number_diff
          let min_line_number_diff = tmp_diff
        endif
      else
        let min_line_number_diff = tmp_diff
      endif
    endif

    " call Decho("[DBG] Line Diff: #" . min_line_number_diff)
  endwhile

  let line_number = curr_line_number + min_line_number_diff
  " call Decho("[DBG] Line Diff: #" . min_line_number_diff)
  " call Decho("[DBG] Line Num: #" . line_number)

  if is_no_sign != 0 || min_line_number_diff <= 0
    let line_number = min_line_number
  endif

  " call Dret("Vm_get_next_sign_line ".line_number . " XXX")
  return line_number
endfun

" ---------------------------------------------------------------------
" Vm_get_prev_sign_line:
fun! s:Vm_get_prev_sign_line(curr_line_number)
  " call Dfunc("Vm_get_prev_sign_line(curr_line_number=".a:curr_line_number.">)")

  let b:Vm_start_from = 1
  let sign_list = s:GetVimCmdOutput('sign place buffer=' . winbufnr(0))
  " call Decho("sign_list<".sign_list.">")

  let curr_line_number = a:curr_line_number
  let line_number = 1
  let is_no_sign  = 1
  let max_line_number = -1
  let max_line_number_diff = 0
  
  while 1
    let line_number = s:Vm_get_line_number(sign_list)
    if line_number < 0
      break
    endif

    " Record the very first line that has a sign
    if is_no_sign != 0 
      let max_line_number = line_number
    elseif line_number > max_line_number 
      let max_line_number = line_number
    endif
    let is_no_sign = 0

    let tmp_diff = curr_line_number - line_number
    if tmp_diff > 0
      " line_number is below curr_line_number
      if max_line_number_diff > 0 
        if tmp_diff < max_line_number_diff 
          let max_line_number_diff = tmp_diff
        endif
      else
        let max_line_number_diff = tmp_diff
      endif
    endif

    " call Decho("[DBG] Line Diff: #" . max_line_number_diff)
    " call Decho("[DBG] Tmp Diff: #" . tmp_diff)
  endwhile

  let line_number = curr_line_number - max_line_number_diff 
  " call Decho("[DBG] Line Diff: #" . max_line_number_diff)
  " call Decho("[DBG] Line Num: #" . line_number)

  if is_no_sign != 0 || max_line_number_diff <= 0
    let line_number = max_line_number 
  endif

  " call Dret("Vm_get_prev_sign_line ".line_number . " XXX")
  return line_number
endfun

" ---------------------------------------------------------------------
" Vm_goto_next_sign:
fun! Vm_goto_next_sign()
  " call Dfunc("Vm_goto_next_sign()")

  let curr_line_number      = line(".")
  let next_sign_line_number = s:Vm_get_next_sign_line(curr_line_number)

"  call Decho("Next sign line #:  " . next_sign_line_number)
  if next_sign_line_number >= 0
    exe ":" . next_sign_line_number
    "call Decho("Going to Line #" . next_sign_line_number)
  endif

"  call Dret("Vm_goto_next_sign")
endfun

" ---------------------------------------------------------------------
" Vm_goto_prev_sign:
fun! Vm_goto_prev_sign()
  " call Dfunc("Vm_goto_prev_sign()")

  let curr_line_number      = line(".")
  let prev_sign_line_number = s:Vm_get_prev_sign_line(curr_line_number)
"  call Decho("Previous sign line #:  " . prev_sign_line_number)

  if prev_sign_line_number >= 0
    exe prev_sign_line_number 
  endif

  " call Dret("Vm_goto_prev_sign")
endfun

" ---------------------------------------------------------------------