#
# A X11 multi-window text editor based on TK/TCL
#  keybindings are Emacs-like
#
# This is just the basic editor functionality, setup, control, etc.
# are elsewhere.
#
# Dave Clemans, May 1993
# dave_clemans@mentorg.com
#

#
# Set our version numbers
#
set tkeVersion 0.2

#
# Useful parameters
#
set tkeTabWidth 3
set tkeIndentWidth 3
set tkeAutoIndentMode 1
set tkeBracketMatchMode 1
set tkeBracketDelay 300
set tkeDoCHighlighting 0
set tkeHighlighterCmd "$tke_library/$tkutVco/highlighter"

#
# Set up our tkeKillRing
#
tkutKillRingInit
set tkeLastKill 0.0

#
# Stuff for search/replace
#
set tkeReplaceString ""
set tkeSearchMethod "tkutRegexpMatch"
set tkeReplaceMethod "tkeRegexpReplace"

#
# For keeping window names unique
#
set tkeWinNumber 0
set tkeMiscWinNumber 0

#
# Display a text window showing the bindings for a specified window
#
proc tkeShowBindings {w} {
    global tkutWindowRows
    global tkutWindowCols
    global tkeMiscWinNumber
    set b .bindings$tkeMiscWinNumber
    incr tkeMiscWinNumber
    toplevel $b
    wm title $b "TK Editor - Key Bindings"
    wm iconname $b "TKE Bindings"
    set t $b.text
    set s $b.sb
    text $t -relief raised -bd 2 -yscrollcommand "$s set" -setgrid true
    wm geometry $b ${tkutWindowCols}x${tkutWindowRows}
    scrollbar $s -relief flat -command "$t yview"
    pack append $b $s {right filly} $t {expand fill}
    #
    $t insert insert "Keystroke Bindings:\n"
    $t insert insert "--------- --------\n"
    set args [bind $w]
    foreach binding $args {
        $t insert insert [format {\n%-20s => "%s"} $binding [bind $w $binding]]
    }
    $t configure -state disabled
}
#
# Get the name of the file for this window
#
proc tkeGetFileName {w} {
    global tkeFileName
    set num 0
    scan $w .tk%d num
    if {$num < [array size tkeFileName]} {
        return $tkeFileName($num)
    } else {
        return ""
    }
}
#
# Set the name of the file for this window
#
proc tkeSetFileName {w file} {
    global tkeFileName
    set num 0
    scan $w .tk%d num
    set tkeFileName($num) $file
}
#
# Get the modified state of the file for this window
#
proc tkeGetFileModified {w} {
    global tkeFileModified
    set num 0
    scan $w .tk%d num
    if {$num < [array size tkeFileModified]} {
        return $tkeFileModified($num)
    } else {
        return 0
    }
}
#
# Set the modified state of the file for this window
#
proc tkeSetFileModified {w state} {
    global tkeFileModified
    set num 0
    scan $w .tk%d num
    set tkeFileModified($num) $state
}
#
# Put a title string into our top-level window
#
proc tkeSetTitle {w} {
    global tkutLocalSystem
    set fileName [tkeGetFileName $w]
    set fileModified [tkeGetFileModified $w]
    set wt [winfo toplevel $w]
    set tf [file tail $fileName]
    set baseTitle "$tf - TK Editor @ $tkutLocalSystem"
    if {$fileModified > 0} {
        wm title $wt "** $baseTitle **"
    } else {
        wm title $wt "$baseTitle"
    }
    set lines 0
    scan [$w index end] %d lines
    global tkeVersion
    $wt.msg.m configure -text "tke $tkeVersion, editing $fileName: $lines lines"
}
#
# Mark this window as containing a modified file
#
proc tkeModifiedFile {w} {
    if {[tkeGetFileModified $w] == 0} {
        tkeSetFileModified $w 1
        tkeSetTitle $w
    }
}
#
# Copy the contents of the current selection into the tkeKillRing
#
proc tkeSelCopy {w} {
    set rc [catch {set str [selection get]}]
    if {$rc == 0} {
        tkutKillRingPush "$str"
    }
}
#
# Cut the contents of the current selection into the tkeKillRing
#
proc tkeSelCut {w} {
    set rc [catch {set str [selection get]}]
    if {$rc == 0} {
        tkutKillRingPush "$str"
        set r [$w tag nextrange sel 0.0 end]
        while {$r != ""} {
            set i [lindex $r 0]
            set e [lindex $r 1]
            $w delete $i $e
            tkeModifiedFile $w
            set r [$w tag nextrange sel $e end]
        }
    }
}
#
# Paste the top of our tkeKillRing into the current insertion position
#
proc tkePasteTop {w} {
    set str [tkutKillRingGet]
    if {"$str" != ""} {
        $w insert insert [tkutKillRingGet]
        tkeModifiedFile $w
        $w yview -pickplace insert
    }
}
#
# Save the contents of a specified text widget into a specified file
# The previous contents of the file is saved to a ".bak" file before being 
# overwritten
#
proc tkeSave {w file} {
    tkutSave $w "$file"
    set fileName [tkeGetFileName $w]
    if {$file == $fileName} {
        tkeSetFileModified $w 0
        tkeSetTitle $w
    }
    tkeUpdateHighlights $w
    if {[info procs tkeSaveUserHook] != ""} {
      tkeSaveUserHook $file
    }
}
#
# Insert a specified file into a specified text widget at its current 
# insertion position
#
proc tkeInsert {w file} {
    if {![file readable $file]} {
        tkutDialogBox "File $file is not readable."
        return
    }
    set f [open $file r]
    set c [read $f]
    close $f
    $w insert insert $c
    tkeModifiedFile $w
    $w yview -pickplace insert
}
#
# Load text from a specified file into a specified text widget
#
proc tkeLoad {w file} {
    global tkeDoCHighlighting
    if {![file readable $file]} {
        if {[file exists $file]} {
            tkutDialogBox "File $file exists, but isn't readable."
            return
        }
        $w delete 1.0 end
        tkeSetFileName $w $file
        tkeSetFileModified $w 0
        tkeSetTitle $w
        return
    }
    $w delete 1.0 end
    if {$file == ""} {
        return
    }
    set f [open $file r]
    set c [read $f]
    close $f
    $w insert 0.0 $c
    tkeSetFileName $w $file
    tkeSetFileModified $w 0
    tkeSetTitle $w
    set tkeDoCHighlighting 0
    global tkeHighlighterCmd
    if {[file executable $tkeHighlighterCmd]} {
        if {[string match "*.\[cChH\]" "$file"] != 0 ||
            [string match "*.cxx" "$file"] != 0 ||
            [string match "*.hxx" "$file"] != 0} {
            set tkeDoCHighlighting 1
            tkeUpdateHighlights $w
        }
    }
}
#
# Goto a specified line in the file
#
proc tkeGotoLine {w} {
    global tkeMiscWinNumber
    set f .goto$tkeMiscWinNumber
    incr tkeMiscWinNumber
    catch {destroy $f}
    toplevel $f
    wm geometry $f 248x40
    wm title $f "Goto Line Numbered ?"
    wm iconname $f "Goto Line"
    frame $f.line
    label $f.line.label -text "Line Number:" -width 13 -anchor w
    set line 0
    set char 0
    set pos [$w index insert]
    scan $pos %d.%d line char
    entry $f.line.entry -width 14 -relief sunken -bd 3 -textvariable line
    button $f.line.button -text "Goto" -command "$w mark set insert \$line.$char;
        $w yview -pickplace insert;destroy $f"
    pack append $f.line $f.line.label left $f.line.entry left \
        $f.line.button {left pady 10 padx 20}
    bind $f.line.entry <Return> "$w mark set insert \$line.$char;
        $w yview -pickplace insert;destroy $f"
    bind $f <Any-Enter> "focus $f.line.entry"
    pack append $f $f.line {top fill}
    grab $f
    focus $f.line.entry
}
#
# Insert "indent" spaces at the specified point
#
proc tkeIndentHere {w marker} {
    global tkeIndentWidth
    set lmarker $marker
    for {set i 0} {$i < $tkeIndentWidth} {incr i} {
        $w insert $lmarker " "
        set lmarker [$w index "$lmarker+1c"]
    }
    tkeModifiedFile $w
}
#
# Delete "indent" spaces at the specified point
#
proc tkeOutdentHere {w marker} {
    global tkeIndentWidth
    for {set i 0} {$i < $tkeIndentWidth} {incr i} {
        set em [$w index "$marker+1c"]
        set c [$w get $marker $em]
        if {"$c" == " "} {
            $w delete $marker $em
        }
    }
    tkeModifiedFile $w
}
#
# Indent/Outdent a specified area of the file
#
proc tkeShiftArea {w smarker emarker method} {
    set sline 0
    set eline 0
    scan $smarker %d sline
    scan $emarker %d eline
    set cmarker $smarker
    for {set i $sline} {$i < $eline} {incr i} {
        $method $w $cmarker
        set cmarker [expr $i+1].0
    }
}
#
# Indent/Outdent the selected area of the file
#
proc tkeShiftSel {w method} {
    global tkeIndentWidth
    set curr [$w index insert]
    set r [$w tag nextrange sel 0.0 end]
    set f 1
    while {$r != ""} {
        set i [lindex $r 0]
        set e [lindex $r 1]
        tkeShiftArea $w $i $e $method
        if {$f == 1} {
            $w tag add sel $i [$w index "$i + $tkeIndentWidth c"]
            set f 0
        }
        set r [$w tag nextrange sel $e end]
    }
    $w mark set insert $curr
}
#
# Indent/Outdent the entire file
#
proc tkeShiftFile {w method} {
    set curr [$w index insert]
    tkeShiftArea $w 0.0 [$w index end] $method
    $w mark set insert $curr
}
#
# Search the given range of the given text widget for the search string
# Replace it with the substitution string
#
# Search/Replace method is exact string matching
#
proc tkeStringReplace {w smarker emarker search sub} {
    set sline 0
    set eline 0
    scan $smarker %d sline
    scan $emarker %d eline
    set changedLine ""
    set index {0 0}
    for {set cline $sline} {$cline <= $eline} {incr cline} {
        set spos $cline.0
        set epos $cline.100000
        if {$cline == $sline} {
            set spos $smarker
        } elseif {$cline == $eline} {
            set epos $emarker
        }
        set sc 0
        set sl 0
        while 1 {
            set line [$w get $spos $epos]
            set flag [tkutStringMatch $line $search index]
            if {$flag == 0} {
                break
            }
            set s [lindex $index 0]
            set e [lindex $index 1]
            scan $spos %d.%d sl sc
            set sp $cline.[expr $sc+$s]
            set ep $cline.[expr $sc+$e+1]
            $w delete $sp $ep
            $w insert $sp $sub
            tkeModifiedFile $w
            set spos $ep
        }
    }
}
#
# Search the given range of the given text widget for the search string
# Replace it with the substitution string
#
# Search/Replace method is regular expressions
#
proc tkeRegexpReplace {w smarker emarker search sub} {
    set sline 0
    set eline 0
    scan $smarker %d sline
    scan $emarker %d eline
    set changedLine ""
    for {set cline $sline} {$cline <= $eline} {incr cline} {
        set spos $cline.0
        set epos $cline.100000
        if {$cline == $sline} {
            set spos $smarker
        } elseif {$cline == $eline} {
            set epos $emarker
        }
        set line [$w get $spos $epos]
        if {[regsub -all $search $line $sub changedLine]} {
            $w delete $spos $epos
            $w insert $spos $changedLine
            tkeModifiedFile $w
        }
    }
}
#
# Find all occurrences of the text in the file, and feed them through
# the specified search/replace algorithm
#
proc tkeAllReplace {w search sub method} {
    global tkeReplaceMethod
    set tkeReplaceMethod "$method"
    set s [$w index 1.0]
    set e [$w index end]
    $method $w $s $e $search $sub
}
#
# Find all selected ranges of text in this window, and feed them through
# the specified search/replace algorithm
#
proc tkeSelReplace {w search sub method} {
    global tkeReplaceMethod
    set tkeReplaceMethod "$method"
    set r [$w tag nextrange sel 0.0 end]
    while {$r != ""} {
        set i [lindex $r 0]
        set e [lindex $r 1]
        $method $w $i $e $search $sub
        set r [$w tag nextrange sel $e end]
    }
}
#
# Starting from the current position in the text, find the next occurrence
# of the string in the text (if any), and feed it through our replace
# algorithm. A specified search/replace algorithm is used.
#
proc tkeNextReplace {w search sub method} {
    global tkeReplaceMethod
    set tkeReplaceMethod "$method"
    set smethod ""
    if {"$method" == "tkeRegexpReplace"} {
        set smethod "tkutRegexpMatch"
    } else {
        set smethod "tkutStringMatch"
    }
    set l [string length $search]
    set p [$w index {insert + 1 chars}]
    set index {0 0}
    while {[$w compare $p < [$w index end]]} {
        $w mark set smarker $p
        set q [$w index {smarker lineend}]
        set line [$w get $p $q]
        set flag [$smethod $line $search index]
        if {$flag > 0} {
            set ss [lindex $index 0]
            set s [$w index "smarker + $ss chars"]
            set tt [lindex $index 1]
            incr tt
            set t [$w index "smarker + $tt chars"]
            $method $w $s $t $search $sub
            $w mark set insert $s
            $w yview -pickplace insert
            return
        }
        set oldp $p
        set p [$w index "$p+1l linestart"]
        if {[$w compare $p == $oldp]} {
            break
        }
    }
    set p [$w index 0.0]
    while {[$w compare $p <= [$w index insert]]} {
        $w mark set smarker $p
        set q [$w index {smarker lineend}]
        if {[$w compare $q > [$w index insert]]} {
            set q [$w index insert]
        }
        set line [$w get $p $q]
        set flag [$smethod $line $search index]
        if {$flag > 0} {
            set ss [lindex $index 0]
            set s [$w index "smarker + $ss chars"]
            set tt [lindex $index 1]
            incr tt
            set t [$w index "smarker + $tt chars"]
            $method $w $s $t $search $sub
            $w mark set insert $s
            $w yview -pickplace insert
            return
        }
        set oldp $p
        set p [$w index "$p+1l linestart"]
        if {[$w compare $p == $oldp]} {
            break
        }
    }
}
#
# Search the given text widget for all usages of given string and highlight it
# Text insertion cursor is not changed
#
# method defines how to search; either tkutStringMatch or tkutRegexpMatch
#
proc tkeSearchAll {w string tag method} {
    global tkeSearchMethod
    set tkeSearchMethod "$method"
    tkutSearchForAll $w "$string" $tag $method
}
#
# Search from the current insertion point to EOF for a given string and
# highlight the first occurrence found
#
# method defines how to search; either tkutStringMatch or tkutRegexpMatch
#
proc tkeSearchForward {w string tag method} {
    global tkeSearchMethod
    set tkeSearchMethod "$method"
    tkutSearchForNext $w "$string" $tag $method
}
#
# Get a search&replace string from the user and search for it in the
# specified text widget. Then replace those occurrences by the
# replace string.
#
proc tkeReplaceFor {w cmd method} {
    global tkutSearchString
    global tkeReplaceString
    global tkeMiscWinNumber
    set s .repl$tkeMiscWinNumber
    incr tkeMiscWinNumber
    catch {destroy $s}
    toplevel $s
    wm geometry $s 484x80
    wm title $s "Search & Replace"
    wm iconname $s "Search&Replace requestor"

    frame $s.string1
    label $s.string1.label -text "Search string:" -width 13 -anchor w
    entry $s.string1.entry -width 40 -relief sunken -bd 3 -textvariable tkutSearchString
    button $s.string1.button1 -text "Search&Replace" -command "$cmd $w \$tkutSearchString \$tkeReplaceString $method
        destroy $s"
    pack append $s.string1 $s.string1.label left $s.string1.entry left \
        $s.string1.button1 {left pady 10 padx 10}
    bind $s <Any-Enter> "focus $s.string1.entry"

    frame $s.string2
    label $s.string2.label -text "Replace string:" -width 13 -anchor w
    entry $s.string2.entry -width 40 -relief sunken -bd 3 -textvariable tkeReplaceString
    pack append $s.string2 $s.string2.label left $s.string2.entry left
    bind $s.string2.entry <Return> "$cmd $w \$tkutSearchString \$tkeReplaceString $method
        destroy $s"

    bind $s.string1.entry <Tab> "focus $s.string2.entry"
    bind $s.string1.entry <Return> "focus $s.string2.entry"

    pack append $s $s.string1 {top fill} $s.string2 {top fill}
    grab $s
    focus $s.string1.entry
}
#
# If we have a search string, repeat the search; else ask for a search string
#
proc tkeSearchCheck {w} {
    global tkeSearchMethod
    global tkutSearchString
    if {"$tkutSearchString" != ""} {
        tkeSearchForward $w "$tkutSearchString" search "$tkeSearchMethod"
    } else {
        tkutSearchFor $w "Search For" tkeSearchForward "$tkeSearchMethod"
    }
}
#
# If we have a search & replace string, repeat the replace; else ask for
# search & replace strings
#
proc tkeReplaceCheck {w} {
    global tkeReplaceMethod
    global tkutSearchString
    global tkeReplaceString
    if {"$tkutSearchString" != "" & "$tkeReplaceString" != ""} {
        tkeNextReplace $w "$tkutSearchString" "$tkeReplaceString" "$tkeReplaceMethod"
    } else {
        tkeReplaceFor $w tkeNextReplace "$tkeReplaceMethod"
    }
}
#
# The basic insertion routine for characters
#
proc tkeInsertChar {w c} {
    if {$c == ""} {
        return
    }
    $w insert insert $c
    tkeModifiedFile $w
    $w yview -pickplace insert
}
#
# The basic insertion routine for brackets
# If requested, does bracket matching
#
proc tkeInsertBracket {w sb eb} {
    tkeInsertChar $w $sb

    global tkeBracketMatchMode
    global tkeBracketDelay
    if {$tkeBracketMatchMode == 1} {
        $w tag remove bracket 0.0 end
        set wp [winfo toplevel $w]
        set geomx 0
        set geomy 0
        scan [wm geometry $wp] %dx%d geomx geomy
        set smarker [$w index "insert - 2 chars"]
        set emarker [$w index "$smarker - $geomy lines linestart"]
        set nest 0
        while {[$w compare $smarker > $emarker]} {
            set c [$w get $smarker]
            if {"$c" == "$eb" && $nest == 0} {
                $w tag add bracket $smarker [$w index $smarker+1c]
                $w tag add bracket [$w index insert-1c] [$w index insert]
                after $tkeBracketDelay $w tag remove bracket 0.0 end
                return
            }
            if {"$c" == "$eb"} {
                incr nest -1
            }
            if {"$c" == "$sb"} {
                incr nest
            }
            set smarker [$w index $smarker-1c]
        }
    }
}
#
# The basic insertion routine for adding a new line
#
proc tkeNewline {w} {
    global tkeAutoIndentMode
    if {$tkeAutoIndentMode != 1} {
        $w insert insert \n
    } else {
        set pos [$w index "insert linestart"]
        set lp 0
        set cc 0
        set ec 0
        set lpos [$w index "insert lineend"]
        scan $lpos %d.%d lp ec
        scan $pos %d.%d lp cc
        set indentStr "\n"
        set allSpaces 1
        while {$cc < $ec} {
            set c [$w get $lp.$cc]
            if {"$c" == " " || "$c" == "\t"} {
                append indentStr "$c"
            } else {
                set allSpaces 0
                break
            }
            incr cc
        }
        if {$allSpaces == 1} {
            $w delete $pos $lpos
        }
        $w insert insert "$indentStr"
    }
    tkeModifiedFile $w
    $w yview -pickplace insert
}
#
# The basic insertion routine for a tab
#
proc tkeTab {w} {
    global tkeTabWidth
    $w insert insert " "
    set ln 0
    set ch 0
    set pos [$w index insert]
    scan $pos "%d.%d" ln ch
    while {$ch % $tkeTabWidth != 0} {
        $w insert insert " "
        set pos [$w index insert]
        scan $pos "%d.%d" ln ch
    }
    tkeModifiedFile $w
    $w yview -pickplace insert
}
#
# Delete to the end of the line, either making a new entry in the
# kill ring or appending to the current entry, depending on insertion
# point movement between entries
#
proc tkeLineKill {w} {
    global tkeLastKill

    set currpos [$w index insert]
    set func tkutKillRingPush
    if {[$w compare $tkeLastKill == $currpos]} {
        set func tkutKillRingAppend
    } else {
        set tkeLastKill $currpos
    }

    set tokill [$w get insert [$w index "insert lineend"]]
    if {"$tokill" == ""} {
        $w delete insert [$w index "insert lineend+1c"]
        $func "\n"
    } else {
        $w delete insert [$w index "insert lineend"]
        $func "$tokill"
    }
    tkeModifiedFile $w
}
#
# Delete the character next to the insertion point
#
proc tkeDelNext {w} {
    set q [$w index insert+1c]
    $w delete insert $q
    tkeModifiedFile $w
    $w yview -pickplace insert
}
#
# Delete the character previous to the insertion point
#
proc tkeDelPrev {w} {
    $w mark set insert insert-1c
    tkeDelNext $w
}
#
# Scroll forwards half a "page"
#
proc tkeScrollForward {w} {
    set wide 0
    set high 0
    set wt [winfo toplevel $w]
    scan [wm geometry $wt] "%dx%d" wide high
    set high [expr $high/2]
    incr high 2
    $w mark set insert "insert + $high l"
    $w yview -pickplace insert
}
#
# Scroll backwards half a "page"
#
proc tkeScrollBackward {w} {
    set wide 0
    set high 0
    set wt [winfo toplevel $w]
    scan [wm geometry $wt] "%dx%d" wide high
    set high [expr $high/2]
    incr high 2
    $w mark set insert "insert - $high l"
    $w yview -pickplace insert
}
#
# Put a new file into our window
# But display a dialog box for confirmation first, if the file has been modified
#
proc tkeNewFile {w} {
    set fileModified [tkeGetFileModified $w]
    set fileName [tkeGetFileName $w]
    if {$fileModified == 0} {
        tkutFileReq $w "Open File" tkeLoad
        return
    }
    global tkeMiscWinNumber
    set r .req$tkeMiscWinNumber
    incr tkeMiscWinNumber
    catch {destroy $r}
    toplevel $r -class Dialog
    wm title $r "Edit New File..."
    wm iconname $r "Get New File..."

    frame $r.top -relief raised -border 1
    frame $r.bot -relief raised -border 1
    pack append $r $r.top {top fill expand} $r.bot {top fill expand}

    message $r.top.msg -justify center -font -Adobe-times-medium-r-normal--*-180* \
        -text "File $fileName has been modified. Forget the changes and edit a new file?" -aspect 400
    pack append $r.top $r.top.msg {top expand padx 5 pady 5}

    frame $r.bot.0 -relief sunken -border 1
    pack append $r.bot $r.bot.0 {left expand padx 20 pady 20}
    button $r.bot.0.button -text "Keep Editing" -command "destroy $r"
    pack append $r.bot.0 $r.bot.0.button {expand padx 12 pady 12}
    bind $r <Return> "destroy $r"

    button $r.bot.1 -text "Edit New File" \
        -command "destroy $r;tkutFileReq $w \"Open File\" tkeLoad"
    pack append $r.bot $r.bot.1 {left expand padx 20}

    bind $r <Any-Enter> [list focus $r]
    grab $r
    focus $r
}
#
# If this looks like a C or C++ file, run it through the highlighter to
# "highlight" comments, function names, etc.
#
proc tkeUpdateHighlights {w} {
    set fileName [tkeGetFileName $w]

    global tkeDoCHighlighting
    $w tag remove comment 1.0 end
    $w tag remove functionName 1.0 end
    set tkeDoCHighlighting 0
    if {[string match "*.\[cChH\]" "$fileName"] != 0 ||
        [string match "*.cxx" "$fileName"] != 0 ||
        [string match "*.hxx" "$fileName"] != 0} {
        set tkeDoCHighlighting 1
    }
    if {$tkeDoCHighlighting == 0} {
        return
    }
    set feof [$w index end+1c]
    set result ""
    global tkeHighlighterCmd
    catch {set result [exec $tkeHighlighterCmd << [$w get 1.0 $feof]]}
    set result [split "$result" "\n"]
    set len [llength $result]
    for {set i 0} {$i < $len} {incr i} {
        eval $w tag add [lindex $result $i]
    }
}
#
# Go back to the saved, on disk copy of the file
# But display a dialog box for confirmation first, if the file has been modified
#
proc tkeReset {w} {
    set fileModified [tkeGetFileModified $w]
    set fileName [tkeGetFileName $w]
    if {$fileModified == 0} {
        tkeLoad $w $fileName
        return
    }
    global tkeMiscWinNumber
    set r .req$tkeMiscWinNumber
    incr tkeMiscWinNumber
    catch {destroy $r}
    toplevel $r -class Dialog
    wm title $r "Forget Changes & Reload File..."
    wm iconname $r "Get Original File..."

    frame $r.top -relief raised -border 1
    frame $r.bot -relief raised -border 1
    pack append $r $r.top {top fill expand} $r.bot {top fill expand}

    message $r.top.msg -justify center -font -Adobe-times-medium-r-normal--*-180* \
        -text "File $fileName has been modified. Forget the changes and reload the file?" -aspect 400
    pack append $r.top $r.top.msg {top expand padx 5 pady 5}

    frame $r.bot.0 -relief sunken -border 1
    pack append $r.bot $r.bot.0 {left expand padx 20 pady 20}
    button $r.bot.0.button -text "Keep Editing" -command "destroy $r"
    pack append $r.bot.0 $r.bot.0.button {expand padx 12 pady 12}
    bind $r <Return> "destroy $r"

    button $r.bot.1 -text "Reload File" -command "tkeLoad $w $fileName;destroy $r"
    pack append $r.bot $r.bot.1 {left expand padx 20}

    bind $r <Any-Enter> [list focus $r]
    grab $r
    focus $r
}
#
# Our basic exit procedure
# If file is modified, display a dialog box asking for confirmation
# before exiting
#
proc tkeExit {w} {
    set fileModified [tkeGetFileModified $w]
    set fileName [tkeGetFileName $w]
    if {$fileModified == 0} {
        destroy [winfo toplevel $w]
        tkutDelMainWindow
        return
    }
    global tkeMiscWinNumber
    set r .req$tkeMiscWinNumber
    incr tkeMiscWinNumber
    catch {destroy $r}
    toplevel $r -class Dialog
    wm title $r "Exiting TK Editor..."
    wm iconname $r "Exiting TK Editor..."

    frame $r.top -relief raised -border 1
    frame $r.bot -relief raised -border 1
    pack append $r $r.top {top fill expand} $r.bot {top fill expand}

    message $r.top.msg -justify center -font -Adobe-times-medium-r-normal--*-180* \
        -text "File $fileName has been modified. Exit TK Editor anyway?" -aspect 400
    pack append $r.top $r.top.msg {top expand padx 5 pady 5}

    frame $r.bot.0 -relief sunken -border 1
    pack append $r.bot $r.bot.0 {left expand padx 20 pady 20}
    button $r.bot.0.button -text "Keep Editing" -command "destroy $r"
    pack append $r.bot.0 $r.bot.0.button {expand padx 12 pady 12}
    bind $r <Return> "destroy $r"

    button $r.bot.1 -text "Save & Exit" -command "tkeSave $w $fileName;destroy $r;destroy \[winfo toplevel $w\];tkutDelMainWindow"
    pack append $r.bot $r.bot.1 {left expand padx 20}
    button $r.bot.2 -text "Just Exit" -command "destroy $r;destroy \[winfo toplevel $w\];tkutDelMainWindow"
    pack append $r.bot $r.bot.2 {left expand padx 20}

    bind $r <Any-Enter> [list focus $r]
    grab $r
    focus $r
}

#
# A do-nothing procedure for null key bindings
#
proc tkeNULL {} {
}

#
# Create a basic text editor window
#
proc tkeEdit {name file} {
    global tkutTkVersion
    global tkeVersion
    global tkutTclVersion
    global tkutSearchString
    global tkeReplaceString
    global tkutInitialDir
    global tkeAutoIndentMode
    global tkeBracketMatchMode
    global tkutSaveBackFiles
    global tkutKillRingSize
    global tkeTabWidth
    global tkeIndentWidth
    global tkeBracketDelay
    global tkeDoCHighlighting
    global tkutWindowRows
    global tkutWindowCols
    global tkeHighlighterCmd
    set t ""
    set s ""
    if {$name == "."} {
        set name .tk
    }
    catch {destroy $name}
    toplevel $name
    set t $name.tk
    set s $name.stk
    set m $name.menu
    set msg $name.msg
    tkeSetFileName $t $file
    tkeSetFileModified $t 0
    #
    # Put decorations on our window to make it look nice
    #
    wm title $name "TK Editor"
    wm iconname $name [file tail $file]
    #
    # Create our text editor menus
    #
    frame $m -relief raised -borderwidth 1
    menubutton $m.button -text "File" -menu $m.button.m
    menu $m.button.m
    $m.button.m add command -label "About TKE" -command \
        "tkutDialogBox \"TKE: A Multi-Window X11-based Text Editor Version $tkeVersion\nrunning on TCL $tkutTclVersion & TK $tkutTkVersion\nDave Clemans, May 1993\""
    $m.button.m add separator
    $m.button.m add command -label "Open File ..." -accelerator "^X^V" \
        -command {cd $tkutInitialDir;tkfsFileselect dummy tkeEditFile "Select File" .fileselect tkerror}
    $m.button.m add command -label "Replace File ..." -accelerator "^X^F" \
        -command "tkeNewFile $t"
    $m.button.m add command -label "Insert File ..." -command "tkutFileReq $t \"Insert File\" tkeInsert"
    $m.button.m add command -label "Reset File" -command "tkeReset $t"
    $m.button.m add command -label "New Window" -accelerator "^X2" \
        -command "set f [tkeGetFileName $t];tkeEditFile dummy \$f"
    $m.button.m add separator
    $m.button.m add command -label "Show Kill Ring" -command "tkutShowKillRing"
    $m.button.m add separator
    $m.button.m add command -label "Save" -accelerator "^X^S" \
        -command "set ff \[tkeGetFileName $t\];tkeSave $t \$ff"
    $m.button.m add command -label "Save As..." -accelerator "^X^W" \
        -command "tkutFileReq $t \"Save As\" tkeSave"
    $m.button.m add command -label "Save Selection As..." -command "tkutFileReq $t \"Save Selection As\" tkutSelSave"
    $m.button.m add separator
    $m.button.m add command -label "Quit" -accelerator "^X^C" \
         -command "tkeExit $t"
    pack append $m $m.button left
    menubutton $m.edit -text "Edit" -menu $m.edit.m
    menu $m.edit.m
    $m.edit.m add command -label "Copy" -accelerator "Meta w" \
        -command "tkeSelCopy $t"
    $m.edit.m add command -label "Cut" -accelerator "^W" \
        -command "tkeSelCut $t"
    $m.edit.m add command -label "Paste" -accelerator "^Y" \
        -command "tkePasteTop $t"
    $m.edit.m add separator
    $m.edit.m add command -label "Goto Line" -accelerator "^X^L" \
        -command "tkeGotoLine $t"
    $m.edit.m add command -label "Goto File Top" -accelerator "Meta <" \
        -command "$t mark set insert \[$t index {0.0 linestart}\];$t yview -pickplace insert"
    $m.edit.m add command -label "Goto File Bottom" -accelerator "Meta >" \
        -command "$t mark set insert \[$t index {end linestart}\];$t yview -pickplace insert"
    $m.edit.m add separator
    $m.edit.m add command -label "Select All" -command "$t tag add sel 0.0 end"
    $m.edit.m add command -label "Select Top" -command "$t tag add sel 0.0 insert"
    $m.edit.m add command -label "Select Bottom" -command "$t tag add sel insert end"
    $m.edit.m add separator
    $m.edit.m add command -label "Indent Line" -command "tkeIndentHere $t \[$t index {insert linestart}\]"
    $m.edit.m add command -label "Indent Selection" -command "tkeShiftSel $t tkeIndentHere"
    $m.edit.m add command -label "Indent File" -command "tkeShiftFile $t tkeIndentHere"
    $m.edit.m add command -label "Outdent Line" -command "tkeOutdentHere $t \[$t index {insert linestart}\]"
    $m.edit.m add command -label "Outdent Selection" -command "tkeShiftSel $t tkeOutdentHere"
    $m.edit.m add command -label "Outdent File" -command "tkeShiftFile $t tkeOutdentHere"
    pack append $m $m.edit left
    menubutton $m.search -text "Search" -menu $m.search.m
    menu $m.search.m
    $m.search.m add command -label "Regexp Search For..." -command "tkutSearchFor $t \"Search For\" tkeSearchForward tkutRegexpMatch"
    $m.search.m add command -label "Regexp Search Again" \
        -accelerator "^S" \
        -command "tkeSearchForward $t \$tkutSearchString search tkutRegexpMatch"
    $m.search.m add command -label "String Search For..." -command "tkutSearchFor $t \"Search For\" tkeSearchForward tkutStringMatch"
    $m.search.m add command -label "String Search Again" \
        -accelerator "^S" \
        -command "tkeSearchForward $t \$tkutSearchString search tkutStringMatch"
    $m.search.m add separator
    $m.search.m add command -label "Regexp Find All..." -command "tkutSearchFor $t \"Find All\" tkeSearchAll tkutRegexpMatch"
    $m.search.m add command -label "String Find All..." -command "tkutSearchFor $t \"Find All\" tkeSearchAll tkutStringMatch"
    $m.search.m add separator
    $m.search.m add command -label "Clear Search Highlights" -command "tkutSearchClear $t"
    pack append $m $m.search left
    menubutton $m.replace -text "Replace" -menu $m.replace.m
    menu $m.replace.m
    $m.replace.m add command -label "Regexp Search&Replace File For..." \
        -command "tkeReplaceFor $t tkeAllReplace tkeRegexpReplace"
    $m.replace.m add command -label "Regexp Search&Replace Selection For..." \
        -command "tkeReplaceFor $t tkeSelReplace tkeRegexpReplace"
    $m.replace.m add separator
    $m.replace.m add command -label "String Search&Replace File For..." \
        -command "tkeReplaceFor $t tkeAllReplace tkeStringReplace"
    $m.replace.m add command -label "String Search&Replace Selection For..." \
        -command "tkeReplaceFor $t tkeSelReplace tkeStringReplace"
    $m.replace.m add separator
    $m.replace.m add command -label "Regexp Replace Next..." \
        -command "tkeReplaceFor $t tkeNextReplace tkeRegexpReplace"
    $m.replace.m add command -label "Regexp Replace Next Again" \
        -accelerator "^R" \
        -command "tkeNextReplace $t \$tkutSearchString \$tkeReplaceString tkeRegexpReplace"
    $m.replace.m add command -label "String Replace Next..." \
        -command "tkeReplaceFor $t tkeNextReplace tkeStringReplace"
    $m.replace.m add command -label "String Replace Next Again" \
        -accelerator "^R" \
        -command "tkeNextReplace $t \$tkutSearchString \$tkeReplaceString tkeStringReplace"
    pack append $m $m.replace left
    menubutton $m.options -text "Options" -menu $m.options.m
    menu $m.options.m
    $m.options.m add check -label "Auto-Indent Mode" -variable tkeAutoIndentMode
    $m.options.m add check -label "Bracket-Match Mode" -variable tkeBracketMatchMode
    $m.options.m add check -label "Create .bak Files" -variable tkutSaveBackFiles
    set tkeDoCHighlighting 0
    if {[file executable $tkeHighlighterCmd]} {
        $m.options.m add separator
        $m.options.m add command -label "Update \"C\" Highlights" \
            -command "tkeUpdateHighlights $t"
        set tkeDoCHighlighting 1
    }
    $m.options.m add separator
    $m.options.m add command -label "Soft Tab Width" \
        -accelerator "$tkeTabWidth" \
        -command "tkutGetNumber $m.options.m \"Soft Tab Width\" tkeTabWidth"
    $m.options.m add command -label "Auto Indent Width" \
        -accelerator "$tkeIndentWidth" \
        -command "tkutGetNumber $m.options.m \"Auto Indent Width\" tkeIndentWidth"
    $m.options.m add command -label "Bracket Match Delay " \
        -accelerator "$tkeBracketDelay" \
        -command "tkutGetNumber $m.options.m \"Bracket Match Delay \" tkeBracketDelay"
    $m.options.m add command -label "Kill Ring Size" \
        -accelerator "$tkutKillRingSize" \
        -command "tkutGetNumber $m.options.m \"Kill Ring Size\" tkutKillRingSize"
    $m.options.m add command -label "Default Height" \
        -accelerator "$tkutWindowRows" \
        -command "tkutGetNumber $m.options.m \"Default Height\" tkutWindowRows"
    $m.options.m add command -label "Default Width" \
        -accelerator "$tkutWindowCols" \
        -command "tkutGetNumber $m.options.m \"Default Width\" tkutWindowCols"
    pack append $m $m.options left
    menubutton $m.help -text "Help" -menu $m.help.m
    menu $m.help.m
    $m.help.m add command -label "Show Bindings" -command "tkeShowBindings $t"
    pack append $m $m.help right
    pack append $name $m {top fillx}
    frame $msg -relief raised -bd 1
    message $msg.m -justify left -anchor nw -aspect 3000 -text ""
    pack append $msg $msg.m {top fillx}
    pack append $name $msg {top fillx}
    #
    # Create the text editor widget, scrollbars, etc.
    #
    text $t -relief raised -bd 2 -yscrollcommand "$s set" -setgrid true \
        -font *courier-medium-r-*-12-*
    wm geometry $name ${tkutWindowCols}x${tkutWindowRows}
    scrollbar $s -relief flat -command "$t yview"
    pack append $name $s {right filly} $t {expand fill}
    #
    # Stick the contents of the specified file into the text editor widget
    #
    tkeLoad $t $file
    #
    # Set the initial cursor position and keyboard focus
    #
    $t mark set insert 0.0
    bind $name <Any-Enter> "focus $t"
    bind $t <Any-KeyPress> {tkeInsertChar %W "%A"}
    bind $t <Any-space> {tkeInsertChar %W " "}
    bind $t <greater> {tkeInsertBracket %W ">" "<"}
    bind $t \} {tkeInsertBracket %W \} \{}
    bind $t ) {tkeInsertBracket %W ) (}
    bind $t \] {tkeInsertBracket %W \] \[}
    bind $t <Any-Return> {tkeNewline %W}
    bind $t <BackSpace> {tkeDelPrev %W}
    bind $t <Delete> {tkeDelNext %W}
    bind $t <Tab> {tkeTab %W}
    bind $t <Shift-Tab> {tkeInsertChar %W "\t"}
    bind $t <Control-a> {%W mark set insert {insert linestart};%W yview -pickplace insert}
    bind $t <Control-b> {%W mark set insert insert-1c;%W yview -pickplace insert}
    bind $t <Control-c> {tkeNULL}
    bind $t <Control-d> {tkeDelNext %W}
    bind $t <Control-e> {%W mark set insert {insert lineend};%W yview -pickplace insert}
    bind $t <Control-f> {%W mark set insert insert+1c;%W yview -pickplace insert}
    bind $t <Control-g> {tkeNULL}
    bind $t <Control-h> {tkeDelPrev %W}
    bind $t <Control-i> {tkeTab %W}
    bind $t <Shift-Control-i> {tkeInsertChar $W "\t"}
    bind $t <Control-j> {tkeNULL}
    bind $t <Control-k> {tkeLineKill %W}
    bind $t <Control-l> {tkeNULL}
    bind $t <Control-m> {tkeNULL}
    bind $t <Control-n> {%W mark set insert insert+1l;%W yview -pickplace insert}
    bind $t <Control-o> {tkeNULL}
    bind $t <Control-p> {%W mark set insert insert-1l;%W yview -pickplace insert}
    bind $t <Control-q> {tkeNULL}
    bind $t <Control-r> {tkeReplaceCheck %W}
    bind $t <Control-s> {tkeSearchCheck %W}
    bind $t <Control-t> {tkeNULL}
    bind $t <Control-u> {tkeNULL}
    bind $t <Control-v> {tkeScrollForward %W}
    bind $t <Control-w> {tkeSelCut %W}
    bind $t <Control-y> {tkePasteTop %W}
    bind $t <Control-z> {tkeNULL}
    #
    bind $t <Control-x> {tkeNULL}
    bind $t "<Control-x> <Control-c>" {tkeExit %W}
    bind $t "<Control-x> <Control-f>" "tkeNewFile %W"
    bind $t "<Control-x> <Control-l>" "tkeGotoLine %W"
    bind $t "<Control-x> <Control-s>" {set ff [tkeGetFileName %W];tkeSave %W $ff}
    bind $t "<Control-x> <Control-w>" {tkutFileReq %W "Save As" tkeSave}
    bind $t "<Control-x> <Control-v>" {cd $tkutInitialDir;tkfsFileselect dummy tkeEditFile "Select File" .fileselect tkerror}
    bind $t "<Control-x> 2" {set ff [tkeGetFileName %W];tkeEditFile dummy $ff}
    #
    bind $t <Escape> {tkeNULL}
    bind $t "<Escape> w" {tkeSelCopy %W}
    bind $t <Meta-w> {tkeSelCopy %W}
    bind $t "<Escape> b" {%W mark set insert "insert wordstart - 1 c wordstart";%W yview -pickplace insert}
    bind $t <Meta-b> {%W mark set insert "insert wordstart - 1 c wordstart";%W yview -pickplace insert}
    bind $t "<Escape> f" {%W mark set insert "insert wordend + 1 c wordstart";%W yview -pickplace insert}
    bind $t <Meta-f> {%W mark set insert "insert wordend + 1 c wordstart";%W yview -pickplace insert}
    bind $t "<Escape> v" {tkeScrollBackward %W}
    bind $t <Meta-v> {tkeScrollBackward %W}
    bind $t "<Escape> <less>" "%W mark set insert \[%W index {0.0 linestart}\];%W yview -pickplace insert"
    bind $t "<Meta-less>" "%W mark set insert \[%W index {0.0 linestart}\];%W yview -pickplace insert"
    bind $t "<Escape> <greater>" "%W mark set insert \[%W index {end linestart}\];%W yview -pickplace insert"
    bind $t "<Meta-greater>" "%W mark set insert \[%W index {end linestart}\];%W yview -pickplace insert"
    #
    bind $t <1> {
        set tk_priv(selectMode) char
        %W mark set insert @%x,%y
        %W mark set anchor insert
        if {[lindex [%W config -state] 4] == "normal"} {focus %W}
    }
    bind $t <Double-1> {
        set tk_priv(selectMode) word
        %W mark set insert "@%x,%y wordstart"
        tk_textSelectTo %W insert
    }
    bind $t <Triple-1> {
        set tk_priv(selectMode) line
        %W mark set insert "@%x,%y linestart"
        tk_textSelectTo %W insert
    }
    bind $t <B1-Motion> {tk_textSelectTo %W @%x,%y}
    bind $t <Shift-1> {
        tk_textResetAnchor %W @%x,%y
        tk_textSelectTo %W @%x,%y
    }
    bind $t <Shift-B1-Motion> {tk_textSelectTo %W @%x,%y}
    bind $t <2> "tkeSelCopy $t;tkePasteTop $t"
    bind $t <B2-Motion> {tkeNULL}
    bind $t <3> {%W scan mark %y}
    bind $t <B3-Motion> {%W scan dragto %y}
    #
    focus $t
    wm protocol $name WM_SAVE_YOURSELF "set ff \[tkeGetFileName $t\];tkeSave $t \$ff"
    wm protocol $name WM_DELETE_WINDOW "tkeExit $t"
    tkutSearchInit $t
    if {[tk colormodel $t] == "color"} {
        $t tag configure bracket -background DodgerBlue -foreground white
    } else {
        $t tag configure bracket -background black -foreground white
    }

    $t tag configure comment -font *courier-medium-o-*-12-*
    $t tag configure functionName -font *courier-bold-r-*-12-*
    tkeUpdateHighlights $t

    return $t
}

#
# Do the housekeeping so we can open a file, and call tkeEdit to actually
# do the editing
#
proc tkeEditFile {dummy file} {
    global tkeWinNumber
    global tkeFileName
    global tkeFileModified

    set tkeFileName($tkeWinNumber) "$file"
    set tkeFileModified($tkeWinNumber) 0
    set name .tk$tkeWinNumber
    tkeEdit $name $file
    incr tkeWinNumber
    tkutNewMainWindow
    return $name
}
