#!/usr/local/bin/wish -f
# jedit - Tk-based text editor
# 
# Copyright 1992-1994 by Jay Sekora.  All rights reserved, except 
# that this file may be freely redistributed in whole or in part 
# for nonprofit, noncommercial use.

global VERSION
set VERSION {3.6/2.0}

# TO DO
#   abbrev fixes:
#     abbrev panel
#     maybe some heuristics for things like plurals
#     maybe a syntax for suffixes (e.g., commit;t -> commitment)
#   file_modes panel
#   documentation for keybindings (automatic documentation?)
#   problem with filename getting set when you cancel Save 
#     for the first time on a new unnamed file
#   improve find panel
#     have find wrap around (if last time didn't match)
#     regex search/replace
#     find all at once (mark) and cycle through with tag nextrange
#   gesture commands
#   autobreaking space a problem if you use two spaces betw sentences
#   improve mode handling
#   hooks for line ends etc.
#   deal with load/save better (maybe MODEPREFS(loadcommand), etc.?)
#   vi binding routines

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

######################################################################
# jedit:init - basic initialisation
######################################################################

proc jedit:init {} {
  global PREFS				;# cross-application prefs
  global MODEPREFS			;# mode-specific prefs
  global EDITPREFS			;# editor prefs (all modes)
  
  catch {tk colormodel . color}		;# colour even on a 2-bit display
  
  global tk_library
  global env
  
  global NAME; set NAME $env(USER)
  global HOME; set HOME $env(HOME)
  
  # check for $HOME/.tk/jlibrary.tcl and read it in if it exists.
  # this contains library procedures.  it would normally be in $tk_library,
  # but we check for its presence here for people who can't put things in
  # $tk_library.
  #
  if {[file isfile "$HOME/.tk/jlibrary.tcl"]} then {
    source "$HOME/.tk/jlibrary.tcl"
  }
  # now check for some other library files which might be there (again,
  # these would normally be in $tk_library):
  j:source_config jrichtext.tcl
  j:source_config jabout.tcl
  j:source_config jbindings.tcl
  
  global FILENAME		;# left unset, so we can ask for one if needed
  
  global UNDOPTR		;# current index into undo ring
  set UNDOPTR 0
  
  global MODE			;# mode of file, e.g. C, Tcl, TeX, etc.
  set MODE default		;# default == set based on filename
  
  global FILE_MODES		;# filename patterns for various modes
  				;# only first two sublist elements matter
  set FILE_MODES {
    {*.c		code		{C source}}
    {*.f		code		{Fortran source}}
    {*.h		code		{C header files}}
    {*.jhelp		help		{help text for jhelp app.}}
    {*.p		code		{Pascal source}}
    {*.sh		code		{Bourne shell source}}
    {*.shar		code		{Bourne shell archive}}
    {*.tcl		code		{Tcl scripts}}
    {*.tk		code		{Tk/Tcl scripts}}
    {*.tex		text		{TeX or LaTeX source}}
    {*.latex		text		{LaTeX source}}
    {*/.letter		mail		{Mail from tin}}
    {.letter		mail		{Mail from tin}}
    {.article		text		{tin}}
    {*/.article		text		{tin}}
    {.*			code		{.login, etc.}}
    {*/.*		code		{~/.login, etc.}}
    {*/draft*/[0-9]*	mh		{MH, exmh, xmh, etc.}}
    {*/tmp/snd.[0-9]*	mail		{elm}}
    {*/tmp/R*[0-9]	mail		{UCB Mail}}
  }
  
  global LINE_MODES		;# first-line patterns for various modes
  				;# only first two sublist elements matter
  set LINE_MODES {
    {%!*		code		{PostScript file}}
    {#!*		code		{executable script}}
  }
  
  global CUTBUFFER
  set CUTBUFFER {}
  
  global ABBREV			;# contains last abbrev expanded
  set ABBREV {}
  global ABBREV_POS		;# start of last abbrev
  set ABBREV_POS {}
  global MATCH			;# contains last match found
  set MATCH {}
  global MATCH_POS		;# position last match found
  set MATCH_POS {}
  global ABBREV_LIST		;# list of abbrevs read from file
  set ABBREV_LIST {}
  global ABBREVS		;# text-indexed array of expansions
  
  foreach key {! $ & * ) - _ = + ] \} ; : ' \" , . / ?} {
    bind Text $key {
      jedit:sabbrev_hook %W
      j:tb:insert_nondigit %A %W
    }
  }
}

######################################################################
# jedit:userinit - user customisation
######################################################################

proc jedit:userinit {} {
  global PREFS				;# cross-application prefs
  global MODEPREFS			;# mode-specific prefs
  global EDITPREFS			;# editor prefs (all modes)
  
  global NAME
  global HOME
  
  # read in user's preferences
  #
  j:read_standard_prefs
  j:read_prefs -array EDITPREFS \
    -file jedit-defaults {
    {textbg white}
    {textfg black}
    {textsb black}
    {textsf white}
    {textiw 2}
    {textbw 2}
    {textsbw 2}
    {undolevels 2}
  }
  jedit:read_mode_prefs default
  jedit:cmd:read_abbrevs
  
  j:source_config -directory $HOME .textbindings.tcl
  
  # read in user's .tk/jeditrc.tcl
  j:source_config jeditrc.tcl
}

######################################################################
# jedit:apply_prefs
######################################################################

proc jedit:apply_prefs {} {
  global PREFS				;# cross-application prefs
  global MODEPREFS			;# mode-specific prefs
  global EDITPREFS			;# editor prefs (all modes)
  global NAME
  global HOME
  global tk_strictMotif
  
  # set user's text bindings:
  
  switch -exact $PREFS(bindings) {
    basic {
      j:eb:basic_bind Entry
      j:tb:basic_bind Text
    }
    emacs {
      j:eb:emacs_bind Entry
      j:tb:emacs_bind Text
      # additional application-specific Emacs-style keyboard bindings  
      bind Text <Control-x><Control-s>	{jedit:cmd:save}
      bind Text <Control-x><Control-w>	{jedit:cmd:saveas}
      bind Text <Control-x><Control-f>	{jedit:cmd:load}
      bind Text <Control-x><Control-v>	{jedit:cmd:load}
      bind Text <Control-x><Control-c>	{jedit:cmd:quit}
      bind Text <Control-s>		{j:find .main.t}
      bind Text <Control-percent>	{j:find .main.t}
    }
  }
  jedit:configure_text
}

######################################################################
# jedit:mkmenus - create menubar
######################################################################

proc jedit:mkmenus {} {
  global MODEPREFS
  frame .menu -borderwidth 2 -relief raised
  menubutton .menu.editor -text {Editor} -menu .menu.editor.m
  menubutton .menu.file -text {File} -menu .menu.file.m
  menubutton .menu.edit -text {Edit} -menu .menu.edit.m
  menubutton .menu.abbrev -text {Abbrev} -menu .menu.abbrev.m
  menubutton .menu.pipe -text {Pipe} -menu .menu.pipe.m
  
  menu .menu.editor.m
  .menu.editor.m add command -label {Help} -accelerator {[h]} \
    -command {jedit:cmd:help}
  .menu.editor.m add command -label {About the Editor . . .} \
    -command {jedit:cmd:about}
  .menu.editor.m add command -label {Global Preferences . . .} \
    -command {j:global_pref_panel}
  .menu.editor.m add command -label {Editor Preferences . . .} \
    -command {jedit:cmd:edit_prefs}
  .menu.editor.m add command -label {Mode Preferences . . .} \
    -command {jedit:cmd:mode_prefs}
  .menu.editor.m add command -label {Mode . . .} -accelerator {[m]} \
    -command {jedit:cmd:ask_mode}
  .menu.editor.m add separator
  .menu.editor.m add command -label {Issue Tcl Command . . .} \
    -accelerator {[T]} -command {j:prompt_tcl}
  .menu.editor.m add command -label {Issue Unix Command . . .} \
    -accelerator {[U]} -command {j:prompt_unix}
  .menu.editor.m add separator
  .menu.editor.m add command -label {New Editor} -accelerator {[n]} \
    -command {exec jedit &}
  .menu.editor.m add command -label {Quit . . .} -accelerator {[q]} \
    -command {jedit:cmd:quit}
  
  menu .menu.file.m
  .menu.file.m add command -label {Load . . .} -accelerator {[l]} \
    -command {jedit:cmd:load}
  .menu.file.m add command -label {Save} -accelerator {[s]} \
    -command {jedit:cmd:save}
  .menu.file.m add command -label {Save As . . .} -accelerator {[S]} \
    -command {jedit:cmd:saveas}
  .menu.file.m add command -label {Print . . .} -accelerator {[p]} \
    -command {jedit:cmd:print}
  .menu.file.m add separator
  .menu.file.m add command -label {Insert File . . .} -accelerator {[i]} \
    -command {jedit:cmd:insfile}
  menu .menu.edit.m
  .menu.edit.m add command -label {Cut} -accelerator {[x]} \
    -command {jedit:cmd:cut}
  .menu.edit.m add command -label {Copy} -accelerator {[c]} \
    -command {jedit:cmd:copy}
  .menu.edit.m add command -label {Paste} -accelerator {[v]} \
    -command {jedit:cmd:paste}
  .menu.edit.m add command -label {Note} -accelerator {[N]} \
    -command {jedit:cmd:note}
  .menu.edit.m add command -label {Insert X Selection} -accelerator {[V]} \
    -command {jedit:cmd:xpaste}
  .menu.edit.m add command -label {Select All} -accelerator {[a]} \
    -command {jedit:cmd:select_all}
  .menu.edit.m add separator
  .menu.edit.m add command -label {Find . . .} -accelerator {[f]} \
    -command {jedit:cmd:save_checkpoint; j:find .main.t}
  .menu.edit.m add command -label {Find Again} -accelerator {[g]} \
    -command {jedit:cmd:save_checkpoint; j:find:again .main.t}
  .menu.edit.m add separator
  .menu.edit.m add command -label {Go to Line . . .} -accelerator {[L]} \
    -command {jedit:cmd:go_to_line}
  .menu.edit.m add command \
    -label {Show Current Position . . .} -accelerator {[C]} \
    -command {jedit:cmd:current_line}
  .menu.edit.m add separator
  #
  # the following three are affected by later configuration, and a trace
  # on EDITPREFS(undolevel)
  #
  .menu.edit.m add command -label {Checkpoint} \
    -command {jedit:cmd:save_checkpoint} -state disabled
  .menu.edit.m add command -label {Undo} -accelerator {[z]} \
    -command {jedit:cmd:undo} -state disabled
  .menu.edit.m add command -label {Redo} -accelerator {[Z]} \
    -command {jedit:cmd:redo} -state disabled
  
  menu .menu.abbrev.m
  .menu.abbrev.m add checkbutton -label {Static Abbreviation} \
    -accelerator {[Space]} \
    -variable MODEPREFS(sabbrev) -onvalue 1 -offvalue 0
  .menu.abbrev.m add checkbutton -label {Dynamic Abbreviation} \
    -accelerator {[Tab]} \
    -variable MODEPREFS(dabbrev) -onvalue 1 -offvalue 0
  .menu.abbrev.m add separator
  .menu.abbrev.m add command -label {Expand Static Abbreviation} \
    -command {
    jedit:cmd:sabbrev
  }
  .menu.abbrev.m add command -label {Expand Dynamic Abbreviation} \
    -command {
    jedit:cmd:dabbrev
  }
  .menu.abbrev.m add separator
  .menu.abbrev.m add command -label {Edit Static Abbreviations} -command {
    jedit:cmd:edit_abbrevs
  }
  .menu.abbrev.m add command -label {Reread Static Abbreviations} -command {
    jedit:cmd:read_abbrevs
  }
  
  menu .menu.pipe.m
  .menu.pipe.m add command -label {Format Lines with `fmt'} -command {
    jedit:pipenl {fmt}
  }
  .menu.pipe.m add separator
  .menu.pipe.m add command -label {Indent} -command {
    jedit:pipenl {sed "s/^/  /"}
  }
  .menu.pipe.m add command -label {Quote Email} -command {
    jedit:pipenl {sed "s/^/> /"}
  }
  .menu.pipe.m add command -label {Unindent/Unquote} -command {
    jedit:pipenl {sed "s/^\[ >:|\}\] //
  s/^\t/      /"}
  }
  .menu.pipe.m add separator
  .menu.pipe.m add command -label {Capitalise} -command {
    jedit:pipe {tr \[a-z\] \[A-Z\]}
  }
  .menu.pipe.m add command -label {Lowercase} -command {
    jedit:pipe {tr \[A-Z\] \[a-z\]}
  }
  .menu.pipe.m add command -label {Toggle Case} -command {
    jedit:pipe {tr \[A-Z\]\[a-z\] \[a-z\]\[A-Z\]}
  }
  .menu.pipe.m add separator
  .menu.pipe.m add command -label {Sort by ASCII Sequence} -command {
    jedit:pipenl {sort}
  }
  .menu.pipe.m add command -label {Sort Numerically} -command {
    jedit:pipenl {sort -n}
  }
  .menu.pipe.m add command -label {Sort Alphabetically} -command {
    jedit:pipenl {sort -if}
  }
  .menu.pipe.m add separator
  .menu.pipe.m add command -label {Pipe Through . . .} -accelerator {[|]} \
     -command {jedit:cmd:run_pipe}
  .menu.pipe.m add command -label {Insert Output of . . .} -accelerator {[!]} \
     -command {jedit:cmd:run_command}
  
  pack .menu.editor .menu.file .menu.edit .menu.abbrev .menu.pipe \
    -in .menu -side left
  pack .menu -in . -side top -fill x
  
  tk_menuBar .menu .menu.editor .menu.file .menu.edit .menu.abbrev .menu.pipe
}

######################################################################
# jedit:mkmain - create text window with scrollbar
######################################################################

proc jedit:mkmain {} {
  global MODEPREFS
  global PREFS
  if {[lsearch [array names PREFS] {scrollbarside}] == -1} {
    set PREFS(scrollbarside) right ;# make sure it's defined
  }
  frame .main
  frame .main.status
  label .main.status.name -relief flat -text {(No file yet)}
  label .main.status.mode -relief flat -textvariable MODE
  scrollbar .main.s -relief flat -command {.main.t yview}
  # text widget is configured near end, after user's config file is read
  text .main.t -yscroll {.main.s set} -setgrid true
  
  pack .main.status.name -in .main.status -side left -fill x
  pack .main.status.mode -in .main.status -side right -fill x
  pack .main.status [j:rule .main] -in .main -side top -fill x
  pack .main.s [j:rule .main] -in .main -side $PREFS(scrollbarside) -fill y
  pack .main.t -in .main -side $PREFS(scrollbarside) -expand yes -fill both
  pack .main -in . -side top -expand yes -fill both
  
  focus .main.t
  focus default .main.t
  tk_bindForTraversal .main.t
}

######################################################################
# jedit:mkbindings - set keyboard shortcuts
######################################################################

proc jedit:mkbindings { { widgets .main.t } } {
  global MODEPREFS
  # the following can't be redefined in the above; if you want to change
  # them, do so in $HOME/.tk/jeditrc.tcl
  #
  foreach w $widgets {
    bind $w <Meta-space>	\
      {set MODEPREFS(sabbrev) [expr {! $MODEPREFS(sabbrev)}]}
    bind $w <Meta-Tab>		\
      {set MODEPREFS(dabbrev) [expr {! $MODEPREFS(dabbrev)}]}
    bind $w <Meta-bar>		{jedit:cmd:run_pipe}
    bind $w <Meta-bar>		{jedit:cmd:run_pipe}
    bind $w <Meta-exclam>	{jedit:cmd:run_command}
    bind $w <Meta-a>		{jedit:cmd:select_all}
    bind $w <Meta-c>		{jedit:cmd:copy}
    bind $w <Meta-C>		{jedit:cmd:current_line}
    bind $w <Meta-f>		\
      {jedit:cmd:save_checkpoint; j:find .main.t}
    bind $w <Meta-g>		\
      {jedit:cmd:save_checkpoint; j:find:again .main.t}
    bind $w <Meta-h>		{jedit:cmd:help}
    bind $w <Meta-i>		{jedit:cmd:insfile}
    bind $w <Meta-l>		{jedit:cmd:load}
    bind $w <Meta-L>		{jedit:cmd:go_to_line}
    bind $w <Meta-m>		{jedit:cmd:ask_mode}
    bind $w <Meta-n>		{exec jedit &}
    bind $w <Meta-N>		{jedit:cmd:note}
    bind $w <Meta-p>		{jedit:cmd:print}
    bind $w <Meta-s>		{jedit:cmd:save}
    bind $w <Meta-S>		{jedit:cmd:saveas}
    bind $w <Meta-T>		{j:prompt_tcl}
    bind $w <Meta-U>		{j:prompt_unix}
    bind $w <Meta-v>		{jedit:cmd:paste}
    bind $w <Meta-V>		{jedit:cmd:xpaste}
    bind $w <Meta-x>		{jedit:cmd:cut}
    bind $w <Meta-q>		{jedit:cmd:quit}
    bind $w <Meta-z>		{jedit:cmd:undo}
    bind $w <Meta-Z>		{jedit:cmd:redo}
    
    bind $w <Tab>		{jedit:tabkey}
    bind $w <Shift-Tab>		{j:tb:insert_nondigit "\t" %W}
    bind $w <space>		{jedit:spacebar}
    bind $w <Shift-space>	{j:tb:insert_nondigit " " %W}
    bind $w <Return>		{jedit:returnkey}
    bind $w <Shift-Return>	{j:tb:insert_nondigit "\n" %W}
  }
}

######################################################################
# jedit:set_label - set the label at the top of the window to $FILENAME
######################################################################

proc jedit:set_label {{title USE_FILENAME} {l .main.status.name}} {
  global FILENAME; append FILENAME {}	;# make sure it's defined

  if {$title == "USE_FILENAME"} then {set title [file tail $FILENAME]}
  if {$title == "FULL_FILENAME"} then {set title $FILENAME}
  $l configure -text $title
  wm title . $title
  wm iconname . $title
}

######################################################################
# jedit:configure_undo - enable or disable menu items depending whether
#   undo is turned on
######################################################################
# this allows and ignores arguments so it can be used with "trace"

proc jedit:configure_undo { args } {
  global EDITPREFS
  catch {
    if $EDITPREFS(undolevels) {
      .menu.edit.m entryconfigure {Undo} -state normal
      .menu.edit.m entryconfigure {Redo} -state normal
      .menu.edit.m entryconfigure {Checkpoint} -state normal
    } else {
      .menu.edit.m entryconfigure {Undo} -state disabled
      .menu.edit.m entryconfigure {Redo} -state disabled
      .menu.edit.m entryconfigure {Checkpoint} -state disabled
    }
  }
}

######################################################################
# abbrev - set an abbreviation (used by .tk/abbrevs.tcl
######################################################################

proc abbrev {{abbrev} {expansion}} {
  global ABBREVS
  
  set ABBREVS($abbrev) $expansion
}

######################################################################
# jedit:pipenl command - pipe selection through command (and replace)
#   adds a newline
######################################################################

proc jedit:pipenl { command } {
  jedit:cmd:save_checkpoint			;# save undo information
  catch { eval exec $command << {[j:selection_if_any]} } result
  append result "\n"

  if [j:no_selection] {return 0}
  # save current position of selection:
  set selfirst [.main.t index sel.first]
  # delete old selection:
  .main.t delete sel.first sel.last
  # insert result:
  .main.t insert $selfirst $result
  # find end of result:
  set sellast [.main.t index "$selfirst +[string length $result]chars"]
  # select result:
  .main.t tag add sel $selfirst $sellast
}

######################################################################
# jedit:pipe command - pipe selection through command (and replace)
#   does not add a newline
######################################################################

proc jedit:pipe { command } {
  jedit:cmd:save_checkpoint			;# save undo information
  catch { eval exec $command << {[j:selection_if_any]} } result

  if [j:no_selection] {return 0}
  # save current position of selection:
  set selfirst [.main.t index sel.first]
  # delete old selection:
  .main.t delete sel.first sel.last
  # insert result:
  .main.t insert $selfirst $result
  # find end of result:
  set sellast [.main.t index "$selfirst +[string length $result]chars"]
  # select result:
  .main.t tag add sel $selfirst $sellast
}

######################################################################
# jedit:capitalise string - return string with first char capitalised
######################################################################

proc jedit:capitalise {string} {
  set cap [format {%s%s} \
    [string toupper [string range $string 0 0]] \
    [string range $string 1 end]]
  return $cap
}

######################################################################
# jedit:uncapitalise string - return string with first char lowercased
######################################################################

proc jedit:uncapitalise {string} {
  set lc [format {%s%s} \
    [string tolower [string range $string 0 0]] \
    [string range $string 1 end]]
  return $lc
}

######################################################################
# jedit:read filename ?t? - "Load..." with supplied filename
######################################################################

proc jedit:read { filename {t .main.t}} {
  global MODEPREFS
  if {! [info exists MODEPREFS(savestate)]} {
    set MODEPREFS(savestate) 0
  }

  if {[info procs jedit:pre_read_hook] != {}} {
    jedit:pre_read_hook $filename $t
  }
  if {[info procs jedit:mode_read_proc] != {}} {
    jedit:mode_read_proc $filename $t
  } else {
    if {! [file exists $filename]} then {
      $t delete 1.0 end
      $t mark set insert 1.0
      jedit:set_label "$filename (new file)"
    } else {
      # should do error checking
      set file [open $filename {r}]
      $t delete 1.0 end
      $t insert end  [read $file]
      $t mark set insert 1.0
      close $file
      #
      if $MODEPREFS(savestate) {
        jedit:read_state $filename .main.t
        jedit:yview_insert .main.t .main.s
      }   
    }
  }
  jedit:set_label			;# display name over text field
  if {[info procs jedit:post_read_hook] != {}} {
    jedit:post_read_hook $filename $t
  }
}

######################################################################
# jedit:write filename ?t? - write out a file
######################################################################

proc jedit:write {filename {t .main.t}} {
  global MODEPREFS
  if {! [info exists MODEPREFS(savestate)]} {
    set MODEPREFS(savestate) 0
  }

  if {[info procs jedit:pre_write_hook] != {}} {
    jedit:pre_write_hook $filename $t
  }
  if {[info procs jedit:mode_write_proc] != {}} {
    jedit:mode_write_proc $filename $t
  } else {
    # should do error checking
    set file [open $filename {w}]
    puts $file [$t get 1.0 end] nonewline
    jedit:set_label
    close $file
    #
    if $MODEPREFS(savestate) {
      jedit:write_state $filename .main.t
    }   
  }
  if {[info procs jedit:post_write_hook] != {}} {
    jedit:post_write_hook $filename $t
  }
}

######################################################################
# jedit:get_state ?t? - return non-text content of a text widget
######################################################################

proc jedit:get_state { {t .main.t} } {
  set tags {}
  set marks {}
  foreach tag [$t tag names] {
    set ranges [$t tag ranges $tag]
    if {"x$ranges" != "x"} {
      lappend tags [list $tag $ranges]
    }
  }
  foreach mark [$t mark names] {
    lappend marks [list $mark [$t index $mark]]
  }
  return [list $tags $marks]
  close $file
}

######################################################################
# jedit:write_state filename ?t? - write out non-text content of a file
######################################################################

proc jedit:write_state { filename  {t .main.t}} {
  set dirname [file dirname $filename]
  set tail [file tail $filename]
  set filename "$dirname/.state.$tail"
  # should do error checking
  set file [open $filename {w}]
  puts $file [jedit:get_state $t]
  close $file
}

######################################################################
# jedit:set_state t state - set non-text content of a file
### DOCUMENT "state"
######################################################################

proc jedit:set_state { {t} {state} } {
  set tags [lindex $state 0]
  set marks [lindex $state 1]
  foreach pair $marks {
    $t mark set [lindex $pair 0] [lindex $pair 1]
  }
  foreach pair $tags {
    set tag [lindex $pair 0]
    set ranges [lindex $pair 1]
    set length [llength $ranges]
    for {set i 0; set i1 1} {$i1 < $length} {incr i 2; incr i1 2} {
      $t tag add $tag [lindex $ranges $i] [lindex $ranges $i1]
    }
  }
  $t yview -pickplace insert
}

######################################################################
# jedit:yview_insert text scrollbar - position insert comfortably
######################################################################

proc jedit:yview_insert { {t .main.t} {s .main.s} } {
  set insertline [lindex [split [$t index insert] .] 0]
  $t yview -pickplace {insert -5 lines}
  set lastline [lindex [$s get] 3]		;# last visible line in $t
  if {$lastline >= $insertline} {
    $t yview -pickplace insert
  }
}

######################################################################
# jedit:read_state filename ?t? - restore non-text content of a file
######################################################################

proc jedit:read_state { filename  {t .main.t}} {
### NEEDS ERROR CHECKING!
  set dirname [file dirname $filename]
  set tail [file tail $filename]
  set filename "$dirname/.state.$tail"
  catch {
    set file [open $filename {r}]
    set state [read $file]
    close $file
    jedit:set_state $t $state
  }
}

######################################################################
# jedit:tabkey - do whatever tab does (abbrev, indent, whatever)
######################################################################

proc jedit:tabkey {{t .main.t}} {
  global MODEPREFS

  if {[info procs jedit:pre_tabkey_hook] != {}} {
    jedit:pre_tabkey_hook $t
  }
  #
  if {[info procs jedit:mode_tabkey_proc] != {}} {
    jedit:mode_tabkey_proc $t
  } else {
    if $MODEPREFS(dabbrev) {
      jedit:cmd:dabbrev
    } else {
      j:tb:insert_nondigit "\t" $t
    }
  }
  #
  if {[info procs jedit:post_tabkey_hook] != {}} {
    jedit:post_tabkey_hook $t
  }
}

######################################################################
# jedit:spacebar - do whatever space does (abbrev, line breaking, whatever)
######################################################################

proc jedit:spacebar {{t .main.t}} {
  if {[info procs jedit:pre_spacebar_hook] != {}} {
    jedit:pre_spacebar_hook $t
  }
  #
  if {[info procs jedit:mode_spacebar_proc] != {}} {
    jedit:mode_spacebar_proc $t
  } else {
    jedit:sabbrev_hook $t
    j:tb:insert_nondigit " " $t
    jedit:autobreak_hook $t
  }
  #
  if {[info procs jedit:post_spacebar_hook] != {}} {
    jedit:post_spacebar_hook $t
  }
}

######################################################################
# jedit:sabbrev_hook t - jedit:cmd:sabbrev if it's on
######################################################################

proc jedit:sabbrev_hook {{t .main.t}} {
  global MODEPREFS

  if $MODEPREFS(sabbrev) {
    jedit:cmd:sabbrev
  }
}

######################################################################
# jedit:dabbrev_hook t - jedit:cmd:dabbrev if it's on
######################################################################
# (not used by plain mode - Tab does dabbrev *INSTEAD OF* inserting
#   tab, not *before* inserting tab)

proc jedit:dabbrev_hook {{t .main.t}} {
  global MODEPREFS

  if $MODEPREFS(dabbrev) {
    jedit:cmd:dabbrev
  }
}

######################################################################
# jedit:autobreak_hook - insert a cr if line is long enough
######################################################################

proc jedit:autobreak_hook {{t .main.t}} {
  global MODEPREFS

  if $MODEPREFS(autobreak) {
    set length [string length [$t get {insert linestart} insert]]
    if {$length > ($MODEPREFS(textwidth) - 15)} {
      jedit:returnkey
    }
  }
}

######################################################################
# jedit:returnkey - do whatever return does (indentation, etc.)
######################################################################

proc jedit:returnkey {{t .main.t}} {
  if {[info procs jedit:pre_returnkey_hook] != {}} {
    jedit:pre_returnkey_hook $t
  }
  #
  if {[info procs jedit:mode_returnkey_proc] != {}} {
    jedit:mode_returnkey_proc $t
  } else {
    jedit:sabbrev_hook $t
    j:tb:insert_nondigit "\n" $t
    $t yview -pickplace insert
    jedit:autoindent_hook
  }
  #
  if {[info procs jedit:post_returnkey_hook] != {}} {
    jedit:post_returnkey_hook $t
  }
}

######################################################################
# jedit:autoindent_hook - insert same indentation as previous line
######################################################################

proc jedit:autoindent_hook {{t .main.t}} {
  global MODEPREFS

  if $MODEPREFS(autoindent) {
    set prevline [$t get {insert -1lines linestart} {insert -1lines lineend}]
    if [regexp "^\[ \t\]\[ \t\]*" $prevline indentation] {
      j:tb:insert_nondigit $indentation $t
    }
  }
}

######################################################################
# jedit:guess_mode f - set MODE variable based on filename f
######################################################################

proc jedit:guess_mode {f} {
  global MODE FILE_MODES LINE_MODES
  
  set MODE default
  #
  # first, try matching on name
  #
  foreach i $FILE_MODES {
    if [string match [lindex $i 0] $f] {
      set MODE [lindex $i 1]
      return 0				;# done!
    }
  }
  #
  # then, check first line (might be a script)
  #
  if {[file exists $f]} {
    set file [open $f {r}]
    set line1 [read $file 32]		;# (unix only sees 32 chars)
    close $file
    foreach i $LINE_MODES {
      if [string match [lindex $i 0] $line1] {
        set MODE [lindex $i 1]
        return 0			;# done!
      }
    }
  }
  #
  # no matches - just use `plain'
  #
  if {$MODE == "default"} {
    set MODE plain
  }
}

######################################################################
# jedit:read_mode_prefs m - read prefs in $MODEPREFS for mode m
######################################################################

proc jedit:read_mode_prefs {{m plain}} {
  global MODEPREFS HOME
  j:read_prefs -array MODEPREFS \
    -directory $HOME/.tk/jeditmodes -file ${m}-defaults {
    {textfont default}
    {textwidth 80}
    {textheight 24}
    {textwrap char}
    {sabbrev 0}
    {dabbrev 0}
    {autobreak 0}
    {autoindent 0}
    {savestate 0}
  }
}

######################################################################
# jedit:set_mode - set mode based on $MODE
######################################################################

proc jedit:set_mode {} {
  global MODE HOME tk_library
  
  # do any cleanup necessary to exit current mode
  if {[info procs jedit:mode_cleanup] != {}} {
    jedit:mode_cleanup
    rename jedit:mode_cleanup {}
  }
  foreach proc {
    jedit:pre_read_hook
    jedit:mode_read_proc 
    jedit:post_read_hook
    
    jedit:pre_write_hook
    jedit:mode_write_proc
    jedit:post_write_hook
    
    jedit:pre_quit_hook
    
    jedit:pre_spacebar_hook
    jedit:mode_spacebar_proc
    jedit:post_spacebar_hook
    
    jedit:pre_tabkey_hook
    jedit:mode_tabkey_proc
    jedit:post_tabkey_hook
    
    jedit:pre_returnkey_hook
    jedit:mode_returnkey_proc
    jedit:post_returnkey_hook
    
    jedit:pre_paste_hook
    jedit:post_paste_hook
    
    jedit:pre_xpaste_hook
    jedit:post_xpaste_hook
  } {
    catch {
      rename $proc {}
    }
  }
  
  # "plain" is special-cased, so there doesn't need to be a
  # plain-mode.tcl file.  this means you can change plain-mode defaults,
  # but you can't customise plain mode with a plain-mode.tcl file.
  
  if {"x$MODE" == "xplain"} {
    j:read_prefs -array MODEPREFS \
      -directory $HOME/.tk/jeditmodes -file ${MODE}-defaults {
      {textfont default}
      {textwidth 80}
      {textheight 24}
      {textwrap char}
      {sabbrev 0}
      {dabbrev 0}
      {autobreak 0}
      {autoindent 0}
      {savestate 0}
    }
  } else {
    #
    # look through a list of directories searching for the mode file:
    #
    set file ${MODE}-mode.tcl
    foreach directory [list \
      $HOME/.tk/jeditmodes \
      $tk_library/jedit/jeditmodes \
      $tk_library/jeditmodes \
    ] {
      if [file isfile $directory/$file] {
        j:source_config -directory $directory $file
        break
      }
    }
  }
  jedit:configure_text
}

######################################################################
# jedit:configure_text - set text for current preferences
######################################################################

proc jedit:configure_text {} {
  global MODEPREFS EDITPREFS
  
  catch {.main.t configure \
    -background $EDITPREFS(textbg) \
    -foreground $EDITPREFS(textfg) \
    -insertbackground $EDITPREFS(textfg) \
    -selectbackground $EDITPREFS(textsb) \
    -selectforeground $EDITPREFS(textsf) \
    -selectborderwidth $EDITPREFS(textsbw) \
    -borderwidth $EDITPREFS(textbw) \
    -insertwidth $EDITPREFS(textiw) \
    -width $MODEPREFS(textwidth) \
    -height $MODEPREFS(textheight) \
    -wrap $MODEPREFS(textwrap)
  }
  j:configure_font .main.t $MODEPREFS(textfont)	;# knows about `default'
}

######################################################################
# jedit:cmd:help - view the help file
######################################################################

proc jedit:cmd:help {} {
  exec jhelp jedit &
}

######################################################################
# jedit:cmd:about - make the about box
######################################################################

proc jedit:cmd:about {} {
  global VERSION
  set about_editor [format {
    j:rt:hl "jedit"
    j:rt:cr
    j:rt:rm "by Jay Sekora, "
    j:rt:tt "js@princeton.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
}

######################################################################
# jedit:cmd:edit_prefs - editor-wide preferences panel
######################################################################

proc jedit:cmd:edit_prefs {} {
  global EDITPREFS
  
  set w .edit_prefs
  toplevel $w
  wm title $w "Editor Preferences"
  
  j:colour_chooser $w.textbg -variable EDITPREFS(textbg) \
    -label "Normal Background:"
  j:colour_chooser $w.textfg -variable EDITPREFS(textfg) \
    -label "Normal Foreground:"
  j:colour_chooser $w.textsb -variable EDITPREFS(textsb) \
    -label "Selected Background:"
  j:colour_chooser $w.textsf -variable EDITPREFS(textsf) \
    -label "Selected Foreground:"
  
  label $w.textiw-label -text "Insert Width:" -anchor w
  scale $w.textiw -from 1 -to 25 -orient horizontal \
    -command {set EDITPREFS(textiw)}
  $w.textiw set $EDITPREFS(textiw)
  
  label $w.textsbw-label -text "Selection Border Width:" -anchor w
  scale $w.textsbw -from 0 -to 25  -orient horizontal \
    -command {set EDITPREFS(textsbw)}
  $w.textsbw set $EDITPREFS(textsbw)
  
  label $w.textbw-label -text "Text Border Width:" -anchor w
  scale $w.textbw -from 0 -to 50  -orient horizontal \
    -command {set EDITPREFS(textbw)}
  $w.textbw set $EDITPREFS(textbw)
  
  label $w.undolevels-label -text "Undo Levels:" -anchor w
  scale $w.undolevels -from 0 -to 10  -orient horizontal \
    -command {set EDITPREFS(undolevels)}
  $w.undolevels set $EDITPREFS(undolevels)
  
  j:buttonbar $w.b -default save -buttons [format {
    {
      save Save {
        j:write_prefs -array EDITPREFS -directory $env(HOME)/.tk \
          -file jedit-defaults
        %s.b.done invoke
      }
    } {
      done Done {
        jedit:configure_text
        destroy %s
      }
    }
  } $w $w]
  
  pack \
    $w.textbg \
    [j:rule $w] \
    $w.textfg \
    [j:rule $w] \
    $w.textsb \
    [j:rule $w] \
    $w.textsf \
    [j:rule $w] \
    $w.textiw-label $w.textiw \
    [j:filler $w] \
    $w.textsbw-label $w.textsbw \
    [j:filler $w] \
    $w.textbw-label $w.textbw \
    [j:rule $w] \
    $w.undolevels-label $w.undolevels \
    [j:rule $w] \
    $w.b \
    -fill x
  
  j:dialogue $w
  j:default_button $w.b.save $w
  focus $w
  tkwait window $w
}
######################################################################
# jedit:cmd:mode_prefs - mode-specific preferences panel
######################################################################

proc jedit:cmd:mode_prefs {} {
  global MODEPREFS env tk_strictMotif MODE

  toplevel .mode_prefs
  wm title .mode_prefs "Mode\255Specific Preferences"

  label .mode_prefs.mode -text "Preferences for mode \"$MODE\""
  frame .mode_prefs.savestate
  checkbutton .mode_prefs.savestate.cb -relief flat -anchor w \
    -text {Save/load highlighting and position} \
    -variable MODEPREFS(savestate)
  frame .mode_prefs.autobreak
  checkbutton .mode_prefs.autobreak.cb -relief flat -anchor w \
    -text {Break long lines with <Space>} \
    -variable MODEPREFS(autobreak)
  frame .mode_prefs.autoindent
  checkbutton .mode_prefs.autoindent.cb -relief flat -anchor w \
    -text {Preserve indentation with <Return>} \
    -variable MODEPREFS(autoindent)
  frame .mode_prefs.sabbrev
  checkbutton .mode_prefs.sabbrev.cb -relief flat -anchor w \
    -text {Expand static abbreviations with <Space>} \
    -variable MODEPREFS(sabbrev)
  frame .mode_prefs.dabbrev
  checkbutton .mode_prefs.dabbrev.cb -relief flat -anchor w \
    -text {Expand dynamic abbreviations with <Tab>} \
    -variable MODEPREFS(dabbrev)
  frame .mode_prefs.wrap
  radiobutton .mode_prefs.wrap.none -relief flat -anchor w \
    -text {Don't wrap lines} \
    -variable MODEPREFS(textwrap) -value none
  radiobutton .mode_prefs.wrap.char -relief flat -anchor w \
    -text {Wrap lines on character boundaries} \
    -variable MODEPREFS(textwrap) -value char
  radiobutton .mode_prefs.wrap.word -relief flat -anchor w \
    -text {Wrap lines at word boundaries} \
    -variable MODEPREFS(textwrap) -value word
  frame .mode_prefs.font
  frame .mode_prefs.font.top
  label .mode_prefs.font.top.l -text {Font:}
  button .mode_prefs.font.top.default -width 8 -text {Default} -command {
    set MODEPREFS(textfont) {default}
  }
  button .mode_prefs.font.top.choose -text {Choose . . .} -command {
    set MODEPREFS(textfont) [j:prompt_font]
  }
  frame .mode_prefs.font.bot
  entry .mode_prefs.font.bot.e -relief sunken -width 50 \
    -textvariable MODEPREFS(textfont)
  frame .mode_prefs.size
  label .mode_prefs.size.wl -text {Width:}
  entry .mode_prefs.size.we -relief sunken -width 5 \
    -textvariable MODEPREFS(textwidth)
  label .mode_prefs.size.hl -text {Height:}
  entry .mode_prefs.size.he -relief sunken -width 5 \
    -textvariable MODEPREFS(textheight)

  j:buttonbar .mode_prefs.b -default save -buttons {
    {
      save Save {
        j:write_prefs -array MODEPREFS -directory $env(HOME)/.tk/jeditmodes \
          -file ${MODE}-defaults
        .mode_prefs.b.done invoke
      }
    } {
      done Done {
        if {$MODEPREFS(textwidth) < 20} {set MODEPREFS(textwidth) 20}
        if {$MODEPREFS(textheight) < 4} {set MODEPREFS(textheight) 4}
        jedit:configure_text
        destroy .mode_prefs
      }
    }
  }
  
  pack append .mode_prefs.savestate \
    .mode_prefs.savestate.cb {left expand fillx}
  pack append .mode_prefs.autobreak \
    .mode_prefs.autobreak.cb {left expand fillx}
  pack append .mode_prefs.autoindent \
    .mode_prefs.autoindent.cb {left expand fillx}
  pack append .mode_prefs.sabbrev \
    .mode_prefs.sabbrev.cb {left expand fillx}
  pack append .mode_prefs.dabbrev \
    .mode_prefs.dabbrev.cb {left expand fillx}
  pack append .mode_prefs.wrap \
    .mode_prefs.wrap.none {top expand fillx} \
    .mode_prefs.wrap.char {top expand fillx} \
    .mode_prefs.wrap.word {top expand fillx}
  pack append .mode_prefs.font.top \
    .mode_prefs.font.top.l {left} \
    .mode_prefs.font.top.choose {right padx 10 pady 5} \
    .mode_prefs.font.top.default {right pady 5}
  pack append .mode_prefs.font.bot \
    .mode_prefs.font.bot.e {left padx 10 pady 5}
  pack append .mode_prefs.font \
    .mode_prefs.font.top {top expand fillx} \
    .mode_prefs.font.bot {top expand fillx}
  pack append .mode_prefs.size \
    .mode_prefs.size.wl {left fillx} \
    .mode_prefs.size.we {left} \
    .mode_prefs.size.hl {left fillx} \
    .mode_prefs.size.he {left}

  pack append .mode_prefs \
    .mode_prefs.mode {top expand fillx} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.savestate {top fillx} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.autobreak {top fillx} \
    .mode_prefs.autoindent {top fillx} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.sabbrev {top expand fill} \
    .mode_prefs.dabbrev {top expand fill} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.wrap {top expand fill} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.font {top fillx} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.size {top expand fill} \
    [j:rule .mode_prefs] {top fillx} \
    .mode_prefs.b {top expand fillx}

  j:dialogue .mode_prefs		;# position in centre of screen

  focus .mode_prefs
  j:default_button .mode_prefs.b.save \
    .mode_prefs.font.bot.e \
    .mode_prefs.size.we \
    .mode_prefs.size.he \
    .mode_prefs

  j:tab_ring \
    .mode_prefs.font.bot.e \
    .mode_prefs.size.we \
    .mode_prefs.size.he
  
  bind .mode_prefs <Key-Tab> {focus .mode_prefs.font.bot.e}
  tkwait window .mode_prefs
}

######################################################################
# jedit:cmd:ask_mode - prompt user for mode
######################################################################

proc jedit:cmd:ask_mode {} {
  global MODE
  
  set prompt_result [j:prompt -text "Editing Mode:"]
  if {$prompt_result != {}} then {
    set MODE $prompt_result
    jedit:set_mode		;# uses global var. MODE
  }
}

######################################################################
# jedit:cmd:quit - quit the editor
######################################################################

proc jedit:cmd:quit {} {
  if {[info procs jedit:pre_quit_hook] != {}} {
    jedit:pre_quit_hook .main.t
  }
  if [j:confirm -text "Are you sure you want to quit?"] {
    destroy .
  }
}

######################################################################
# jedit:cmd:load - read in a file
######################################################################

proc jedit:cmd:load {} {
  jedit:cmd:save_checkpoint		;# save undo information
  global FILENAME		;# so it can be default
  append FILENAME {}		;# make sure it's defined

  set prompt_result [j:fs -prompt "Load:"]
  if {$prompt_result != {}} then {
    set FILENAME $prompt_result
    jedit:read $FILENAME
  }
}
  
######################################################################
# jedit:cmd:save - write out a file, using $FILENAME if defined
######################################################################

proc jedit:cmd:save {{t .main.t}} {
  global FILENAME		;# so it can be default
  append FILENAME {}		;# make sure it's defined

  if {[string length $FILENAME] > 0} then {
    jedit:write $FILENAME $t
  } else {
    set prompt_result [j:fs -prompt "Save as:"]
    if {$prompt_result != {}} then {
      set FILENAME $prompt_result
      jedit:write $FILENAME $t
    }
  }
}

######################################################################
# jedit:cmd:saveas - write out a file, prompting for a filename
######################################################################

proc jedit:cmd:saveas {{t .main.t}} {
  global FILENAME		;# so it can be default
  append FILENAME {}		;# make sure it's defined

  set prompt_result [j:fs -prompt "Save as:"]
  if {$prompt_result != {} && \
     ( ! [file exists $prompt_result] || \
      [j:confirm -text \
      "File \"$prompt_result\" exists; replace it?"] )} then {
    set FILENAME $prompt_result
    jedit:write $FILENAME $t
  }
}

######################################################################
# jedit:cmd:print - print the file using lpr
######################################################################

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

######################################################################
# jedit:cmd:insfile - read in a file and insert it at the insert mark
######################################################################

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

######################################################################
# jedit:cmd:save_checkpoint - save current state in undo ring
######################################################################

proc jedit:cmd:save_checkpoint {} {
  global CKPT_TEXT CKPT_STATE EDITPREFS UNDOPTR

  if $EDITPREFS(undolevels) {
    set CKPT_TEXT($UNDOPTR) [.main.t get 1.0 end]
    set CKPT_STATE($UNDOPTR) [jedit:get_state .main.t]
    incr UNDOPTR
    set old [expr {$UNDOPTR - $EDITPREFS(undolevels)}]
    catch {
      unset CKPT_TEXT($old)			;# forget a previous checkpoint
      unset CKPT_STATE($old)
    }
  } else {
    j:alert -text "You don't have undo turned on."
  }
}

######################################################################
# jedit:cmd:undo - restore from saved state (moving backwards in undo ring)
######################################################################

proc jedit:cmd:undo {} {
  global CKPT_TEXT CKPT_STATE EDITPREFS UNDOPTR

  if $EDITPREFS(undolevels) {
    set CKPT_TEXT($UNDOPTR) [.main.t get 1.0 end]
    set CKPT_STATE($UNDOPTR) [jedit:get_state .main.t]
    incr UNDOPTR -1
    if {![info exists CKPT_TEXT($UNDOPTR)]} {
      incr UNDOPTR
      j:alert -text "No more undo information available."
    }
    .main.t delete 1.0 end
    .main.t insert end $CKPT_TEXT($UNDOPTR)
    jedit:set_state .main.t $CKPT_STATE($UNDOPTR)
    .main.t yview -pickplace insert
  } else {
    j:alert -text "You aren't saving any undo information."
  }
}

######################################################################
# jedit:cmd:redo - restore from saved state (moving forwards in undo ring)
######################################################################

proc jedit:cmd:redo {} {
  global CKPT_TEXT CKPT_STATE EDITPREFS UNDOPTR

  if $EDITPREFS(undolevels) {
    set CKPT_TEXT($UNDOPTR) [.main.t get 1.0 end]
    set CKPT_STATE($UNDOPTR) [jedit:get_state .main.t]
    incr UNDOPTR
    if {![info exists CKPT_TEXT($UNDOPTR)]} {
      incr UNDOPTR -1
      j:alert -text "No more redo information available."
    }
    .main.t delete 1.0 end
    .main.t insert end $CKPT_TEXT($UNDOPTR)
    jedit:set_state .main.t $CKPT_STATE($UNDOPTR)
    .main.t yview -pickplace insert
  } else {
    j:alert -text "You aren't saving any undo information."
  }
}

######################################################################
# jedit:cmd:cut - delete the selection and copy it to CUTBUFFER
######################################################################

proc jedit:cmd:cut {} {
  global CUTBUFFER

  jedit:cmd:save_checkpoint			;# save undo information

  set CUTBUFFER [.main.t get sel.first sel.last]
  .main.t delete sel.first sel.last
}

######################################################################
# jedit:cmd:copy - copy the selection into CUTBUFFER
######################################################################

proc jedit:cmd:copy {} {
  global CUTBUFFER

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

######################################################################
# jedit:cmd:paste - insert CUTBUFFER
######################################################################

proc jedit:cmd:paste {} {
  global CUTBUFFER

  jedit:cmd:save_checkpoint			;# save undo information

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

  .main.t insert insert $CUTBUFFER

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

######################################################################
# jedit:cmd:note - copy the selection into a text panel (as a note)
######################################################################

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

######################################################################
# jedit:cmd:select_all - mark the entire text as selected
######################################################################

proc jedit:cmd:select_all {} {
  .main.t tag add sel 1.0 end
}

######################################################################
# jedit:cmd:run_pipe - prompt for a Unix command to run on the selection
######################################################################

proc jedit:cmd:run_pipe {} {
  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:pipenl $UNIX_PIPE			;# handles checkpointing
  }
}

######################################################################
# jedit:cmd:run_command - prompt for a Unix command to insert
######################################################################

proc jedit:cmd:run_command {} {
  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			;# save undo information
      .main.t insert insert $result
    }
  }
}

######################################################################
# jedit:cmd:dabbrev - expand abbreviation before insert
######################################################################

proc jedit:cmd:dabbrev {{t .main.t}} {
  # 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 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]

  $t delete abbrevstart insert
  $t insert insert $MATCH
  return 1
}

######################################################################
# dabbrev_again - search earlier in the text for abbrevs
######################################################################

proc dabbrev_again {{t .main.t}} {
  # 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]

  $t delete $ABBREV_POS insert
  $t insert insert $MATCH
  $t insert insert " "
}

######################################################################
# jedit:cmd:sabbrev - look up an abbrev in a static table
######################################################################

proc jedit:cmd:sabbrev {{t .main.t}} {
  # following don't really need to be global (shared with dabbrev):
  global ABBREV ABBREV_POS ABBREVS

  $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
    }
  }

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

  # first try regular version:
  set ABBREV [$t get abbrevstart insert]
  if {[info exists ABBREVS($ABBREV)]} {
    $t delete $ABBREV_POS insert
    $t insert insert $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)]} {
      $t delete $ABBREV_POS insert
      $t insert insert [jedit:capitalise $ABBREVS($lcabbrev)]
      return 1
    }
  }
  return 0
}

######################################################################
# jedit:cmd:edit_abbrevs - edit your abbrevs file
######################################################################

proc jedit:cmd:edit_abbrevs {} {
  global HOME
  if {! [file isdirectory "$HOME/.tk"]} then {
    exec mkdir "$HOME/.tk"
    # above should have error-checking
  }
  if {! [file isfile "$HOME/.tk/abbrevs.tcl"]} then {
    # create it, with examples
  }
  exec jedit "$HOME/.tk/abbrevs.tcl" &
}

######################################################################
# jedit:cmd:read_abbrevs - read abbrevs file
######################################################################

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

######################################################################
# jedit:cmd:go_to_line - go to a particular line
######## NEED TO CHECK THAT AN INDEX WAS TYPED!
######################################################################

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

######################################################################
# jedit:go_to_line lineno - go to a particular line
######################################################################

proc jedit:go_to_line {{lineno 0}} {
  set result [catch {
    .main.t mark set insert $lineno.0
    .main.t yview -pickplace insert
  }]
  if $result then {j:alert -text "`$lineno' is not a valid line number."}
}

######################################################################
# jedit:cmd:current_line lineno - display which line the cursor is on
######################################################################

proc jedit:cmd:current_line {} {
  set insertindex [split [.main.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."
}

######################################################################
# jedit:cmd:xpaste - insert X selection
######################################################################

proc jedit:cmd:xpaste {} {
  jedit:cmd:save_checkpoint			;# save undo information

  if {[info procs jedit:pre_xpaste_hook] != {}} {
    jedit:pre_xpaste_hook $t
  }

  .main.t insert insert [j:selection_if_any]

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

######################################################################
# FINAL SETUP
######################################################################

jedit:init
jedit:userinit

jedit:mkmenus
jedit:mkmain				;# (sets focus)

jedit:apply_prefs

jedit:configure_undo			;# enable/disable menu entries
trace variable EDITPREFS(undolevels) w jedit:configure_undo

jedit:mkbindings .main.t
if {[info procs jedit:userhook] == "jedit:userhook"} {
  jedit:userhook
}

# process arguments, if any
#
set MODE default

if {$argc > 0} then {
  if [string match {-m*} [lindex $argv 0]] {
    if {$argc < 2} {
      puts stdout {No argument provided to `-mode' option.}
      exit 1
    }
    set MODE [lindex $argv 1]
    set argv [lreplace $argv 0 1]
    set argc [expr {$argc - 2}]
  }
  
  if {$argc > 0} {
    # get filename
    set FILENAME [lindex $argv 0]
  
    # auto-select MODE if not specified explicitly
    if {$MODE == "default"} {
      jedit:guess_mode $FILENAME
    }
    
    jedit:set_mode
    
    jedit:read $FILENAME
  } else {
    if {$MODE == "default"} {
      set MODE plain
    }
    jedit:set_mode
  }
  # if more than one filename was specified, call jedit again
  # with a list formed by deleting the first element
  # to process the remainder
  if {$argc > 1} then {
    foreach i [lreplace $argv 0 0] {
      exec jedit -mode $MODE $i &
    }
  }
} else {
  set MODE plain
  jedit:set_mode
}
