# jtextvi.tcl - additional procedures for vi-like Text bindings
#
######################################################################
# Copyright 1992-1995 by Jay Sekora.  This file may be freely        #
# distributed, modified or unmodified, for any purpose, provided     #
# that this copyright notice is retained verbatim in all copies and  #
# no attempt is made to obscure the authorship of this file.  If you #
# distribute any modified versions, I ask, but do not require, that  #
# you clearly mark any changes you make as such and that you provide #
# your users with instructions for getting the original sources.     #
######################################################################
# 
# Essentially, call
#   j:tb:vi_init Text       ---   for vi-like Text bindings
# You can check $j_tb(modified,$WIDGET) to see if a text widget has been
# modified.  (Set it to false when you save the text widget.)

# TO DO:
# ^L
# sentence-manipulation stuff
# case change commands, transposition commands
# commands to do with mark?
# word deletion - fix to use buffer
# generalise movement to copying-to-cutbuffer and deletion
# IMPROVE ENTRY BINDINGS
# literal-insert for entry

######################################################################
# j:tb:vi_init t - set vi bindings up for widget $t (possibly "Text")
######################################################################

proc j:tb:vi_init { {t Text} } {
  global j_teb
  set j_teb(cutbuffer) {}
  set j_teb(dragscroll,txnd) 0
  set j_teb(dragscroll,delay) 50
  set j_teb(scanpaste_time) 0
  set j_teb(scanpaste_paste) 1
  
  set j_teb(keymap,$t) vi-command
  
  j:tb:key_bind $t
  j:tb:mouse_bind $t

  
  j:tkb:mkmap Text vi-command vi-command {
    {Control-slash		j:tb:select_all}
    {Control-backslash		j:tb:clear_selection}
    
    {Delete			j:tkb:vi:left}
    {BackSpace			j:tkb:vi:left}
    {space			j:tkb:vi:right}
    {minus			j:tkb:vi:minus}
    {plus			j:tkb:vi:plus}
    {Return			j:tkb:vi:plus}
    
    {Up				j:tkb:vi:up}
    {Down			j:tkb:vi:down}
    {Left			j:tkb:vi:left}
    {Right			j:tkb:vi:right}
    
    {Control-p			j:tkb:vi:up}
    {Control-n			j:tkb:vi:down}
    
    {k				j:tkb:vi:up}
    {j				j:tkb:vi:down}
    {h				j:tkb:vi:left}
    {l				j:tkb:vi:right}

    {Control-k			j:tkb:vi:up}
    {Control-j			j:tkb:vi:down}
    {Control-h			j:tkb:vi:left}
    {Control-l			j:tkb:vi:right}
    
    {Control-b			j:tkb:scroll_up}
    {Control-f			j:tkb:scroll_down}
    
    {x				j:tkb:delete_right}
    {X				j:tkb:delete_left}
    {b				j:tkb:word_left}
    {B				j:tkb:word_left}
    {e				j:tkb:vi:word_end}
    {w				j:tkb:word_right}
    {W				j:tkb:word_right}
    {G				j:tkb:eof}
    
    {i				j:tkb:vi:insert}
    {I				j:tkb:vi:Insert}
    {a				j:tkb:vi:append}
    {A				j:tkb:vi:Append}
    {o				j:tkb:vi:open}
    {O				j:tkb:vi:Open}
    
    {d				j:tkb:new_mode vi-delete}
    {c				j:tkb:new_mode vi-change}
    
    {D				j:tkb:vi:d-eol}
    {C				j:tkb:vi:c-eol}

    {Home			j:tkb:bol}
    {End			j:tkb:eol}
    
    {asciicircum		j:tkb:vi:bol}
    {dollar			j:tkb:vi:eol}
    
    {Next			j:tkb:scroll_down}
    {Prior			j:tkb:scroll_up}
    
    {Escape			j:tkb:vi:clear_count}
    {Control-bracketleft	j:tkb:vi:clear_count}
    
    {1				j:tkb:continue_number}
    {2				j:tkb:continue_number}
    {3				j:tkb:continue_number}
    {4				j:tkb:continue_number}
    {5				j:tkb:continue_number}
    {6				j:tkb:continue_number}
    {7				j:tkb:continue_number}
    {8				j:tkb:continue_number}
    {9				j:tkb:continue_number}
    {0				j:tkb:continue_number}
    
    {Control-DEFAULT		j:tb:beep}
    {DEFAULT			j:tb:beep}
    {Shift-DEFAULT		j:tb:beep}
  }

  j:tkb:mkmap Text vi-insert vi-insert {
    {Delete			j:tkb:delete_left}
    {BackSpace			j:tkb:delete_left}
    {Return			j:tkb:insert_newline}
    
    {Control-i			j:tkb:self_insert}
    {Control-j			j:tkb:self_insert}
    {Control-h			j:tkb:delete_left}
    
    {Control-w			j:tkb:delete_left_word}
    {Control-u			j:tkb:vi:delete_left_line}
    
    {Escape			j:tkb:vi:command}
    {Control-bracketleft	j:tkb:vi:command}
    
    {Control-v			j:tkb:new_mode vi-literal}
    
    {Control-DEFAULT		j:tb:beep}
    {DEFAULT			j:tkb:self_insert}
    {Shift-DEFAULT		j:tkb:self_insert}
  }
  
  j:tkb:mkmap Text vi-literal vi-insert {
    {DEFAULT			j:tkb:self_insert}
    {Shift-DEFAULT		j:tkb:self_insert}
    {Control-DEFAULT		j:tkb:self_insert}
    {Meta-DEFAULT		j:tkb:self_insert}
  }
  
  # The following maps are a hideous kludge until generalised command
  # structure can be implemented:
  
  j:tkb:mkmap Text vi-delete vi-command {
    {d				j:tkb:vi:dd}
    {w				j:tkb:vi:dw}
    {W				j:tkb:vi:dw}
    {dollar			j:tkb:vi:d-eol}
    
    {DEFAULT			j:tb:beep}
    {Shift-DEFAULT		j:tb:beep}
    {Control-DEFAULT		j:tb:beep}
    {Meta-DEFAULT		j:tb:beep}
  }
  
  j:tkb:mkmap Text vi-change vi-command {
    {c				j:tkb:vi:cd}
    {w				j:tkb:vi:cw}
    {W				j:tkb:vi:cw}
    {dollar			j:tkb:vi:c-eol}
    
    {DEFAULT			j:tb:beep}
    {Shift-DEFAULT		j:tb:beep}
    {Control-DEFAULT		j:tb:beep}
    {Meta-DEFAULT		j:tb:beep}
  }
}

######################################################################


proc j:tkb:vi:command { W K A } {
  if { ! [j:tb:is_bol $W]} {
    j:tb:move $W {insert - 1 char}
  }
  j:tkb:new_mode vi-command $W $K $A
}

proc j:tkb:vi:insert { W K A } {
  j:tkb:new_mode vi-insert $W $K $A
}

######################################################################
# based on procedures from Achim Bohnet <ach@rosat.mpe-garching.mpg.de>
# TO DO:  should beep ONCE if repeating would hit edge

proc j:tkb:vi:left { W K A } {
  if [j:tb:is_bol $W] {
    j:beep $W
  } else {
    j:tkb:repeatable {
      if {![j:tb:is_bol $W]} {j:tb:move $W {insert - 1 char}}
    } $W
  }
}

proc j:tkb:vi:right { W K A } {
  if [j:tb:is_eol $W] {
    j:beep $W
  } else {
    j:tkb:repeatable {
      if {![j:tb:is_eol $W]} {j:tb:move $W {insert + 1 char}}
    } $W
  }
}

proc j:tkb:vi:up { W K A } {
  if [j:tb:is_first_line $W] {
    j:beep $W
  } else {
    j:tkb:repeatable {
      if {![j:tb:is_first_line $W]} {j:tb:move $W {insert - 1 line}}
    } $W
  }
}

proc j:tkb:vi:down { W K A } {
  if [j:tb:is_last_line $W] {
    j:beep $W
  } else {
    j:tkb:repeatable {
      if {![j:tb:is_last_line $W]} {j:tb:move $W {insert + 1 line}}
    } $W
  }
}

proc j:tkb:vi:word_end { W K A } {
  j:tb:move $W {insert + 1 char}
  j:tkb:repeatable {
    while {[$W compare insert != end] &&
           [string match "\[ \t\n\]" [$W get insert]]} {
      j:tb:move $W {insert + 1 char}
    }
    while {[$W compare insert != end] &&
           ![string match "\[ \t\n\]" [$W get insert]]} {
      j:tb:move $W {insert + 1 char}
    }
    j:tb:move $W {insert - 1 char}
  } $W
}

proc j:tkb:vi:open { W K A } {
  global j_ed
  
  $W mark set insert "insert lineend "
  $W insert insert "\n"
  set j_ed(vi,$W,insert-begin) [$W index insert]	;# NOT YET USED
  j:tkb:vi:insert $W $K $A
  j:text:mark_dirty $W
}

proc j:tkb:vi:Open { W K A } {
  global j_ed
  
  $W mark set insert "insert linestart"
  $W insert insert "\n"
  $W mark set insert "insert - 1line"
  set j_ed(vi,$W,insert-begin) [$W index insert]	;# NOT YET USED
  j:tkb:vi:insert $W $K $A
  j:text:mark_dirty $W
}

######################################################################

# vi beeps on escape:
proc j:tkb:vi:clear_count { W K A } {
  j:beep $W
  j:tkb:clear_count $W $K $A
}

# vi bol is first _printing_ character (non-whitespace):
proc j:tkb:vi:bol { W K A } {
  j:tkb:bol $W $K $A
  while {[string match "\[ \t\]" [$W get insert]]} {
    j:tb:move $W {insert + 1 char}
  }
}

# vi eol leaves cursor on last character _before_ newline:
proc j:tkb:vi:eol { W K A } {
  j:tkb:eol $W $K $A
  if { ! [j:tb:is_bol $W]} {
    j:tb:move $W {insert - 1 char}
  }
}

proc j:tkb:vi:plus { W K A } {
  global j_teb
  j:tkb:vi:down $W $K $A
  j:tkb:vi:bol $W $K $A
}

proc j:tkb:vi:minus { W K A } {
  global j_teb
  j:tkb:vi:up $W $K $A
  j:tkb:vi:bol $W $K $A
}

proc j:tkb:vi:append { W K A } {
  global j_teb
  if { ! [j:tb:is_bol $W]} {
    j:tb:move $W {insert + 1 char}
  }
  j:tkb:vi:insert $W $K $A
}

proc j:tkb:vi:Insert { W K A } {
  global j_teb
  j:tkb:vi:bol $W $K $A
  j:tkb:vi:insert $W $K $A
}

proc j:tkb:vi:Append { W K A } {
  global j_teb
  j:tkb:eol $W $K $A
  j:tkb:vi:insert $W $K $A
}

######################################################################
# used for Control-u in insert mode:

proc j:tkb:vi:delete_left_line { W K A } {
  if [$W compare {insert linestart} < insert] {
    global j_teb
    set j_teb(modified,$W) 1
    
    j:text:delete $W [$W index {insert linestart}] [$W index insert]
  }
}

######################################################################
# the following are probably temporary kludges; see jbindtext.tcl:

proc j:tkb:vi:dd { W K A } {
  set start [$W index "insert linestart"]
  j:tkb:vi:down $W $K $A		;# repeatable
  set end [$W index "insert linestart"]
  j:text:delete $W $start $end
}

proc j:tkb:vi:dw { W K A } {
  set start [$W index insert]
  j:tkb:word_right $W $K $A		;# repeatable
  set end [$W index insert]
  j:text:delete $W $start $end
  if { ! [j:tb:is_bol $W]} {
    j:tb:move $W {insert - 1 char}
  }
}

proc j:tkb:vi:d-eol { W K A } {
  j:text:delete $W [$W index insert] [$W index "insert lineend"]
  j:tkb:clear_count $W $K $A
  if { ! [j:tb:is_bol $W]} {
    j:tb:move $W {insert - 1 char}
  }
}

proc j:tkb:vi:cc { W K A } {
  j:tkb:vi:dd $W $K $A			;# repeatable
  j:tkb:vi:insert $W $K $A
}

proc j:tkb:vi:cw { W K A } {
  j:tkb:vi:dw $W $K $A			;# repeatable
  j:tkb:vi:append $W $K $A
}

proc j:tkb:vi:c-eol { W K A } {
  j:tkb:vi:d-eol $W $K $A		;# repeatable
  j:tkb:vi:append $W $K $A
}

######################################################################
# deprecated alias for backwards-compatibility:
######################################################################

proc j:tb:vi_bind { W } {
  j:tb:vi_init $W
}
