# jedit_cmds.tcl - most user-visible commands for jedit
#   (preference commands are in jedit_prefs.tcl)
# 
# Copyright 1992-1994 by Jay Sekora.  All rights reserved, except 
# that this file may be freely redistributed in whole or in part 
# for non-profit, noncommercial use.

######################################################################
#
# NOTE: these mostly take the arguments "t args", where t is the text
# widget they apply to.  That way they can be used in a j:tkb:mkmap
# table (where %W %K %A will be appended to the command before 
# execution) as well as with just %W in bindings.  In a few cases
# where t would be ignored, they just take "args".
#
######################################################################

######################################################################
# view the help file
######################################################################

proc jedit:cmd:help { t args } {
  exec jdoc jedit &
}

######################################################################
# make the about box
######################################################################

proc jedit:cmd:about { t args } {
  global VERSION
  set about_editor [format {
    j:rt:hl "jedit"
    j:rt:cr
    j:rt:rm "by Jay Sekora, "
    j:rt:tt "js@bu.edu"
    j:rt:par
    j:rt:rm "A customisable text editor for X Windows."
    j:rt:cr
    j:rt:rm "Version %s."
    j:rt:par
    j:rt:rm "Copyright \251 1992-1994 by Jay Sekora.  "
    j:rt:rm "All rights reserved, except that this file may be freely "
    j:rt:rm "redistributed in whole or in part for non\255profit, "
    j:rt:rm "noncommercial use."
    j:rt:par
    j:rt:rm "If you find bugs or have suggestions for improvement, "
    j:rt:rm "please let me know.  "
    j:rt:rm "Feel free to use bits of this code in your own "
    j:rt:tt "wish"
    j:rt:rm " scripts."
  } $VERSION]
  j:about .about $about_editor
  j:about:button .about {About jedit} $about_editor
  j:about:button .about {About the Author} [j:about_jay]
  j:about:button .about {About Tk and Tcl} [j:about_tktcl]
  
  tkwait window .about
}

######################################################################
# open a new editor window
######################################################################

proc jedit:cmd:new_window { args } {
  jedit:jedit
}

######################################################################
# prompt user for mode
######################################################################

proc jedit:cmd:ask_mode { t args } {
  global MODE
  
  set window [jedit:text_to_top $t]
  set prompt_result [j:prompt -text "Editing Mode:"]
  if {$prompt_result != {}} then {
    jedit:set_mode $t $prompt_result
    jedit:apply_mode $window
  }
}

######################################################################
# close a window
######################################################################

proc jedit:cmd:close { t args } {
  global JEDIT_WINDOW_COUNT
  
  if {$JEDIT_WINDOW_COUNT == 1} {
    jedit:cmd:quit $t
  } else {
    set mode [jedit:get_mode $t]
    if {[info procs mode:$mode:pre_close_hook] != {}} {
      mode:$mode:pre_close_hook $t
    }
    
    if {[info procs mode:$mode:close] == {}} {
      if [j:confirm -text "Are you sure you want to close this window?"] {
        incr JEDIT_WINDOW_COUNT -1	;# one fewer window
        destroy [jedit:text_to_top $t]
      }
    } else {
      mode:$mode:close $t
    }
  }
}

######################################################################
# quit the editor
######################################################################

proc jedit:cmd:quit { t args } {
  set mode [jedit:get_mode $t]
  
  if {[info procs mode:$mode:pre_quit_hook] != {}} {
    mode:$mode:pre_quit_hook $t
  }
  
  if {[info procs mode:$mode:quit] == {}} {
    if [j:confirm -text "Are you sure you want to quit?"] {
      exit 0
    }
  } else {
    mode:$mode:quit $t
  }
}

######################################################################
# read in a file
######################################################################

proc jedit:cmd:load { t args } {
  jedit:cmd:save_checkpoint $t	;# save undo information

  set filename [j:fs -prompt "Load:"]
  if {"x$filename" != "x"} then {
    jedit:set_filename $t $filename
    
    # if new filename should have a different mode, set it:
    set old_mode [jedit:get_mode $t]
    set new_mode [jedit:guess_mode $filename]
    if {[string compare $old_mode $new_mode] != 0} {
      jedit:set_mode $t $new_mode
      jedit:apply_mode $t
    }
    
    jedit:read $filename $t
  }
}
  
######################################################################
# write out a file, using window's filename if defined
######################################################################

proc jedit:cmd:save { t args } {
  set filename [jedit:get_filename $t]
  if {"x$filename" != "x"} then {
    jedit:write $filename $t
  } else {
    set filename [j:fs -prompt "Save as:"]
    if {"x$filename" != "x"} then {
      jedit:set_filename $t $filename
      jedit:set_label [jedit:text_to_top $t]
      jedit:write $filename $t
    }
  }
}

######################################################################
# write out a file, prompting for a filename
######################################################################

proc jedit:cmd:saveas { t args } {
  set filename [j:fs -prompt "Save as:"]
  if {"x$filename" != "x" && \
     ( ! [file exists $filename] || \
      [j:confirm -text \
      "File \"$filename\" exists; replace it?"] )} then {
    jedit:set_filename $t $filename
    jedit:set_label [jedit:text_to_top $t]
    jedit:write $filename $t
  }
}

######################################################################
# print the file using lpr
######################################################################

proc jedit:cmd:print { t args } {
  global J_PREFS
  if [j:confirm -priority 24 \
    -text "Print using `lpr' to printer `$J_PREFS(printer)'?"] {
    exec lpr -P$J_PREFS(printer) << [$t get 1.0 end]
  }
}

######################################################################
# print rich-text as postscript using lpr
######################################################################

proc jedit:cmd:print_postscript { t args } {
  global J_PREFS
  if [j:confirm -priority 24 \
    -text "Print as PostScript using `lpr' to printer `$J_PREFS(printer)'?"] {
    exec lpr -P$J_PREFS(printer) << [j:tc:ps:convert_text $t]
  }
}

######################################################################
# read in a file and insert it at the insert mark
######################################################################

proc jedit:cmd:insfile { t args } {
  jedit:cmd:save_checkpoint $t			;# save undo information
  set prompt_result [j:fs -prompt "Insert:"]
  if {$prompt_result != {}} then {
    j:text:insert_string $t [exec cat $prompt_result]
    j:text:insert_string $t "\n"
  }
}

######################################################################
# delete the selection and copy it to CUTBUFFER
######################################################################

proc jedit:cmd:cut { t args } {
  global CUTBUFFER

  jedit:cmd:save_checkpoint $t			;# save undo information

  set CUTBUFFER [$t get sel.first sel.last]
  j:text:delete $t sel.first sel.last
}

######################################################################
# copy the selection into CUTBUFFER
######################################################################

proc jedit:cmd:copy { t args } {
  global CUTBUFFER

  set CUTBUFFER [$t get sel.first sel.last]
}

######################################################################
# insert CUTBUFFER
######################################################################

proc jedit:cmd:paste { t args } {
  global CUTBUFFER
  
  set mode [jedit:get_mode $t]
  
  jedit:cmd:save_checkpoint $t			;# save undo information

  if {[info procs mode:$mode:pre_paste_hook] != {}} {
    mode:$mode:pre_paste_hook $t
  }

  j:text:insert_string $t $CUTBUFFER

  if {[info procs mode:$mode:post_paste_hook] != {}} {
    mode:$mode:post_paste_hook $t
  }
}

######################################################################
# copy the selection into a text panel (as a note)
######################################################################

proc jedit:cmd:note { t args } {
  j:more -title Note -text [$t get sel.first sel.last]
}

######################################################################
# mark the entire text as selected
######################################################################

proc jedit:cmd:select_all { t args } {
  $t tag add sel 1.0 end
}

######################################################################
# prompt for a Unix command to run on the selection
######################################################################

proc jedit:cmd:run_pipe { t args } {
  global UNIX_PIPE; append UNIX_PIPE {}

  set prompt_result [j:prompt -text "Unix Filter:" -default $UNIX_PIPE]
  if {$prompt_result != {}} then {
    set UNIX_PIPE $prompt_result
    jedit:pipe $t $UNIX_PIPE		;# handles checkpointing
  }
}

######################################################################
# prompt for a Unix command to insert
######################################################################

proc jedit:cmd:run_command { t args } {
  global UNIX_COMMAND; append UNIX_COMMAND {}

  set prompt_result [j:prompt -text "Unix Command:" -default $UNIX_COMMAND]
  if {$prompt_result != {}} then {
    set UNIX_COMMAND $prompt_result
    catch { eval exec $UNIX_COMMAND } result
    if {$result != {}} {
      append result "\n"
      jedit:cmd:save_checkpoint $t			;# save undo information
      j:text:insert_string $t $result
    }
  }
}

######################################################################
# expand dynamic abbreviation before insert
######################################################################

proc jedit:cmd:dabbrev { t args } {
  # THIS COULD BE SIMPLIFIED: do i need both match... and abbrev... vars?
  # PROBLEM: this depends on the Text widget's notion of words.
  # it would be nice to be able to expand, say, $tk_l to $tk_library.

  global ABBREV ABBREV_POS MATCH MATCH_POS

  $t mark set abbrevend insert
  $t mark set abbrevstart insert
  while {[$t compare abbrevstart != 1.0] &&
         [string match {[a-zA-Z0-9']} [$t get {abbrevstart - 1 char}]]} {
    $t mark set abbrevstart {abbrevstart -1char}
  }

  set ABBREV_POS [$t index abbrevstart]	;# for dabbrev_again

  set ABBREV [$t get abbrevstart insert]

  set context [$t get 0.0 abbrevstart]

  while {1} {
    set matchpos [string last $ABBREV $context]
  
    if {$matchpos == -1} {return 0}	;# not found

    $t mark set matchstart [$t index "0.0 +$matchpos chars"]
    if {[$t compare matchstart == {matchstart wordstart}]} {
      $t mark set matchend [$t index {matchstart wordend}]
      break				;# sort of an `until'
    }
    set context [$t get 0.0 matchstart]
  }

  set MATCH [$t get matchstart matchend]

  set MATCH_POS [$t index matchstart]

  j:text:replace $t abbrevstart abbrevend $MATCH
  return 1
}

# ######################################################################
# # dabbrev_again - search earlier in the text for abbrevs
# #   CURRENTLY NOT USED
# ######################################################################
# 
# proc dabbrev_again { t args } {
#   # THIS COULD BE SIMPLIFIED: do i need both match... and abbrev... vars?
#   # PROBLEM: this depends on the Text widget's notion of words.
#   # it would be nice to be able to expand, say, $tk_l to $tk_library.
# 
#   global ABBREV ABBREV_POS MATCH MATCH_POS
# 
#   set context [$t get 0.0 $MATCH_POS]
# 
#   while {1} {
#     set matchpos [string last $ABBREV $context]
#   
#     if {$matchpos == -1} {
#       return [sabbrev]			;# try the static table
#     }
#     $t mark set matchstart [$t index "0.0 +$matchpos chars"]
#     if {[$t compare matchstart == {matchstart wordstart}]} {
#       $t mark set matchend [$t index {matchstart wordend}]
#       break				;# sort of an `until'
#     }
#     set context [$t get 0.0 matchstart]
#   }
# 
#   set MATCH [$t get matchstart matchend]
# 
#   set MATCH_POS [$t index matchstart]
# 
#   j:text:replace $t $ABBREV_POS abbrevend "$MATCH "
# }

######################################################################
# look up and expand static abbrev before insert
######################################################################

proc jedit:cmd:sabbrev { t args } {
  $t mark set abbrevend insert
  # following don't really need to be global (shared with dabbrev):
  global ABBREV ABBREV_POS ABBREVS

  $t mark set abbrevend insert
  $t mark set abbrevstart insert
  while {[$t compare abbrevstart != 1.0] &&
         [string match {[a-zA-Z0-9_']} [$t get {abbrevstart - 1 char}]]} {
    $t mark set abbrevstart {abbrevstart -1char}
  }

  # avoid expanding things like \def, .PP, file.c, etc.:
  set prefix [$t get {abbrevstart -2chars} {abbrevstart}]
  if {[string length $prefix] > 0} {
    if {[string match {?[@$%&+=\:~.]} $prefix]} {
      return 0
    }
    # don't expand "l" in "ls -l", but do expand "this---l"
    if {[string match "\[ \t\n\]-" $prefix]} {	;# don't expand "ls -l"
      return 0
    }
    # don't expand "s" in "house(s)", but do expand "so (s) of"
    if {[string match "\[a-zA-Z](" $prefix]} {	;# don't expand "house(s)"
      return 0
    }
  }

  set ABBREV_POS [$t index abbrevstart]	;# for dabbrev_again

  # first try regular version:
  set ABBREV [$t get abbrevstart insert]
  if {[info exists ABBREVS($ABBREV)]} {
    j:text:replace $t $ABBREV_POS abbrevend $ABBREVS($ABBREV)
    return 1
  }
  # else try capitalised version
  if {[string match {[A-Z][a-z]*} $ABBREV]} {
    set lcabbrev [jedit:uncapitalise $ABBREV]
    if {[info exists ABBREVS($lcabbrev)]} {
      j:text:replace $t $ABBREV_POS abbrevend \
        [jedit:capitalise $ABBREVS($lcabbrev)]
      return 1
    }
  }
  return 0
}

######################################################################
# edit your abbrevs file
######################################################################

proc jedit:cmd:edit_abbrevs { args } {
  global HOME
  if {! [file isdirectory "$HOME/.tk"]} then {
    exec mkdir "$HOME/.tk"
    # above should have error-checking
  }
  exec jabbrevs "$HOME/.tk/abbrevs.tcl" &	;# doesn't currently use arg
}

######################################################################
# read abbrevs file
######################################################################

proc jedit:cmd:read_abbrevs { args } {
  j:source_config abbrevs.tcl
}

######################################################################
# toggle static abbrevs
######################################################################

proc jedit:cmd:toggle_sabbrev { t args } {
  global JEDIT_MODEPREFS
  
  set mode [jedit:get_mode $t]
  
  set JEDIT_MODEPREFS($mode,sabbrev) \
    [expr {! $JEDIT_MODEPREFS($mode,sabbrev)}]
}

######################################################################
# toggle dynamic abbrevs
######################################################################

proc jedit:cmd:toggle_dabbrev { t args } {
  global JEDIT_MODEPREFS
  
  set mode [jedit:get_mode $t]
  
  set JEDIT_MODEPREFS($mode,dabbrev) \
    [expr {! $JEDIT_MODEPREFS($mode,dabbrev)}]
}

######################################################################
# go to a particular line
######## NEED TO CHECK THAT AN INDEX WAS TYPED!
######################################################################

proc jedit:cmd:go_to_line { t args } {
  set prompt_result [j:prompt -text "Go to line number:"]
  if {$prompt_result != {}} then {
    jedit:go_to_line $t $prompt_result
  }
}

######################################################################
# display which line the cursor is on
######################################################################

proc jedit:cmd:current_line { t args } {
  set insertindex [split [$t index insert] {.}]
  set line [lindex $insertindex 0]
  set column [lindex $insertindex 1]
  j:alert -title "Notice" \
    -text "The insertion point is at line $line, column $column."
}

######################################################################
# insert X selection
######################################################################

proc jedit:cmd:xpaste { t args } {
  set mode [jedit:get_mode $t]
  
  jedit:cmd:save_checkpoint $t			;# save undo information
  
  if {[info procs mode:$mode:pre_xpaste_hook] != {}} {
    mode:$mode:pre_xpaste_hook $t
  }

  j:text:insert_string $t [j:selection_if_any]

  if {[info procs mode:$mode:post_xpaste_hook] != {}} {
    mode:$mode:post_xpaste_hook $t
  }
}

######################################################################
# front end for j:find to match jedit:cmd argument convention
######################################################################

proc jedit:cmd:find { t args } {
  jedit:cmd:save_checkpoint $t
  j:find $t
}

######################################################################
# find same string again (same kind of search)
######################################################################

proc jedit:cmd:find_again { t args } {
  jedit:cmd:save_checkpoint $t
  j:find:again $t
}

######################################################################
# hacks for more-specific kinds of finds (for vi/emacs bindings)
### BOGUS!  jedit should not need to know about the internals of j:find!
######################################################################

proc jedit:cmd:find_forward { t args } {
  global j_find
  set j_find(backwards) 0
  j:find $t
}

proc jedit:cmd:find_backward { t args } {
  global j_find
  set j_find(backwards) 1
  j:find $t
}

######################################################################
# save all windows and quit
######################################################################

# we need to make sure there's a filename before calling save, because
# a cancel in the saveas file selector box will cancel the save, but
# not the quit!

proc jedit:cmd:done { t args } {
  set mode [jedit:get_mode $t]
  
  if {[info procs mode:$mode:done] == {}} {
    set filename [jedit:get_filename $t]
    if {"x$filename" == "x"} then {
      set filename [j:fs -prompt "Save as:"]
      if {"x$filename" == "x"} {			;# user clicked cancel
        return
      } else {
        jedit:set_filename $t $filename
      }
    }

    jedit:cmd:save $t
    jedit:cmd:close $t
  } else {
    mode:$mode:done $t
  }
}

######################################################################
# panel to let user insert any iso-8859 character
######################################################################

proc jedit:cmd:char_panel { t args } {
  set tl [j:new_toplevel .high_bit]
  wm title $tl "Characters"
  
  message $tl.m -aspect 350 \
    -text "Click on a character to insert it."
  text $tl.t -width 16 -height 12 -wrap none \
    -cursor top_left_arrow \
    -font -*-courier-bold-r-normal-*-*-140-* \
    -borderwidth 2 -relief groove
  
  # using j:buttonbar for visual consistency:  
  j:buttonbar $tl.b -buttons {
    {ok Done {}}
  }
  $tl.b.ok configure -command "destroy $tl"
  
  for {set i 32} {$i < 112} {incr i 16} {
    for {set j 0} {$j < 16} {incr j} {
      $tl.t insert end [format %c [expr {$i + $j}]]
    }
    $tl.t insert end "\n"
  }
  for {set j 112} {$j < 127} {incr j} {
    $tl.t insert end [format %c $j]
  }
  $tl.t insert end " \n "
  for {set i 160} {$i < 256} {incr i 16} {
    for {set j 0} {$j < 16} {incr j} {
      $tl.t insert end [format %c [expr {$i + $j}]]
    }
    $tl.t insert end "\n"
  }
  $tl.t configure -state disabled
  
  pack $tl.m -fill x
  pack $tl.t -padx 10
  pack $tl.b -anchor e
  
  bind $tl.t <ButtonRelease-1> "
    j:text:insert_string $t \[%W get @%x,%y\]
  "
  foreach event {
    <ButtonRelease-3> <B3-Motion> <Button-3> <ButtonRelease-2>
    <ButtonRelease-1><B2-Motion> <Button-2> <Shift-B1-Motion>
    <Shift-Button-1> <B1-Motion> <Triple-Button-1> <Double-Button-1>
    <Button-1>
    } {
    bind $tl.t $event {;}
  }
}

######################################################################
# insert a hyphen
######################################################################

proc jedit:cmd:hyphen { t args } {
  j:text:insert_string $t "\xad"
}

######################################################################
# insert a copyright symbol
######################################################################

proc jedit:cmd:copyright { t args } {
  j:text:insert_string $t "\xa9"
}

######################################################################
# rich-text cut
######################################################################

proc jedit:cmd:rich_cut { t } {
  jedit:cmd:rich_copy $t			;# (saves checkpoint)
  $t delete sel.first sel.last
}

######################################################################
# rich-text copy
######################################################################

proc jedit:cmd:rich_copy { t } {
  global RICHBUFFER
  
  jedit:cmd:save_checkpoint $t			;# save undo information
  
  set RICHBUFFER {}
  set curstring {}
  set curtags [$t tag names sel.first]
  
  $t mark set richptr sel.first
  
  while {[$t compare richptr < sel.last]} {
    set tags [$t tag names richptr]
    set char [$t get richptr]
    if {"x$tags" != "x$curtags"} {	;# new "range" of text
      lappend RICHBUFFER [list $curstring $curtags]
      set curstring $char
      set curtags $tags
    } else {
      append curstring $char
    }
    $t mark set richptr {richptr+1c}
  }
  lappend RICHBUFFER [list $curstring $curtags]
  return
}

######################################################################
# rich-text paste
#   partly lifted from insertWithTags in mkStyles.tcl demo
######################################################################

proc jedit:cmd:rich_paste { t } {
  global RICHBUFFER
  
  jedit:cmd:save_checkpoint $t			;# save undo information
  
  lappend RICHBUFFER {}			;# make sure it's defined
  
  foreach pair $RICHBUFFER {
    set text [lindex $pair 0]
    set tags [lindex $pair 1]
    
    set start [$t index insert]
    $t insert insert $text
    foreach tag [$t tag names $start] {
      $t tag remove $tag $start insert	;# clear tags inherited from left
    }
    foreach tag $tags {	
      $t tag add $tag $start insert	;# add new tags
    }
  }
  $t tag remove sel 1.0 end		;# clear selection (guaranteed in text)
  return
}
