# e93 startup file
# This is used to define all the menus, and commands
# that are active when the editor begins.
# WARNING: be careful when editing this file,
# e93 will stop processing this file at the FIRST error!

# Globals
set			tcl_precision			16;					# increase precision
set			untitledCounter			0;					# used to give each new untitled window a unique name
set			lineWrap				0;					# no line wrapping currently
set			lineWrapColumn			70;					# column where lines should be wrapped

# ---------------------------------------------------------------------------------------------------------------------------------
# Defaults

# Set checkboxes in the search dialog to default values.
# Change these to have them default whatever way you would like when e93 starts.

set			searchBackwardsFlag		FALSE;				# do not search backwards by default at startup
set			wrapAroundFlag			FALSE;				# do not wrap around by default at startup
set			selectionExpressionFlag	FALSE;				# do not use selection expressions by default at startup
set			ignoreCaseFlag			FALSE;				# do not ignore case by default
set			limitScopeFlag			FALSE;				# do not limit scope by default
set			replaceProcFlag			FALSE;				# do not treat replace text as procedure

# The following list of lists is used to set font, tabsize, and colors of windows
# based on the name of the window.
# The first element of each sublist is a regular expression, which is
# matched against the window name. If a match is found, the remaining four
# elements are used as the fontName, tabSize, foregroundColor, and backgroundColor
# respectively, for the window.
# If no match is located, the defaults (see below) are used.
# NOTE: the empty string (or any invalid font name) forces e93 to choose a reasonable font.

set			windowParameterList		\
				{
				{{(.*\.c)$|(.*\.h)$|(.*\.cpp)$|(.*\.C)$|(.*\.c\+\+)$}	"" 4 white darkslateblue}
				{{(\.e93rc$)|(\.e93rcl$)}								"" 4 white darkslateblue}
				{{(.*\.a)$|(.*\.asm)$|(.*\.s)$}							"" 8 white darkslateblue}
				{{(.*\.asc)$}											"" 8 white darkslateblue}
				}

# The default attributes for windows whose names are not recognized above.
set			defaultFont				"";					# default font name (NOTE: the empty string (or any invalid font name) forces e93 to choose a reasonable font)
set			defaultTabSize			4;					# default tab size
set			defaultForegroundColor	white;				# default foreground color for windows
set			defaultBackgroundColor	darkslateblue;		# default background color for windows

# Set the characters considered parts of words when double clicking.
setwordchars {ABCDEFGHJIKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@_}

# ---------------------------------------------------------------------------------------------------------------------------------
# Setup

# Determine screen and window sizes.
set			screenWidth				[lindex [screensize] 0]
set			screenHeight			[lindex [screensize] 1]
set			windowWidth				[expr $screenWidth*5/6];	# set the initial default width for new windows
set			windowHeight			[expr $screenHeight*3/4];	# set the initial default height for new windows

# Determine how windows stagger when they are opened.
set			staggerInitialX			[expr $screenWidth/24];		# set the initial default X position for new windows
set			staggerInitialY			[expr $screenHeight/16];	# set the initial default Y position for new windows
set			staggerIncrementX		[expr $screenWidth/160];	# amount to increment by
set			staggerIncrementY		[expr $screenHeight/160];
set			staggerMaxX				[expr $screenWidth*3/24]
set			staggerOpenX			$staggerInitialX;			# set the current X position for new windows
set			staggerOpenY			$staggerInitialY;			# set the current Y position for new windows

# Clipboard buffers
newbuffer	clip0; setbuffervariable clip0 lowpriority "";		# set the lowpriority variable which tells us to treat windows on this buffer specially
newbuffer	clip1; setbuffervariable clip1 lowpriority "";
newbuffer	clip2; setbuffervariable clip2 lowpriority "";
newbuffer	clip3; setbuffervariable clip3 lowpriority "";
newbuffer	clip4; setbuffervariable clip4 lowpriority "";
newbuffer	clip5; setbuffervariable clip5 lowpriority "";
newbuffer	clip6; setbuffervariable clip6 lowpriority "";
newbuffer	clip7; setbuffervariable clip7 lowpriority "";
newbuffer	clip8; setbuffervariable clip8 lowpriority "";
newbuffer	clip9; setbuffervariable clip9 lowpriority "";

setclipboard clip0;										# set this as the current clipboard

# Search buffers
newbuffer	findBuffer; setbuffervariable findBuffer lowpriority "";				# create default buffer to use for "find"
newbuffer	replaceBuffer; setbuffervariable replaceBuffer lowpriority "";			# create default buffer to use for "replace"
newbuffer	tempFindBuffer; setbuffervariable tempFindBuffer lowpriority "";		# these buffers are for commands which are implemented with search/replace, but do not want to overwrite the find/replaceBuffers
newbuffer	tempReplaceBuffer; setbuffervariable tempReplaceBuffer lowpriority "";

# ---------------------------------------------------------------------------------------------------------------------------------
# Procedures

# Return TRUE if the passed window name is one of the low-priority windows.
proc LowPriority {theWindow} \
{
	expr {[catch {getbuffervariable $theWindow lowpriority} errorMessage]==0}
}

# When the interpreter does not understand a given command, send it to a shell.
# NOTE: no interactive commands can be executed this way, because Tcl would
# be suspended (waiting for the interactive command to finish). If Tcl is
# suspended, then e93 is also suspended (waiting for the Tcl command to finish)
# So there is no way that e93 could provide input to an interactive command.
# Interactive commands should be run as tasks.
proc unknown args \
{
	return [uplevel exec </dev/null $args]
}

# If there is no selection, select the current line
proc SelectLineWhenNoSelection {theBuffer} \
{
	set ends [getselectionends $theBuffer]
	set start [lindex $ends 0]
	set end [lindex $ends 1]
	if {$start==$end} \
		{
		selectline $theBuffer [lindex [positiontolineoffset $theBuffer $start] 0];	# select the line the cursor is on
		}
}

# Copy the selection in theBuffer to the current clipboard.
# If there is no selection in theBuffer, then select the line the cursor is on.
proc SmartCopy {theBuffer} \
{
	SelectLineWhenNoSelection $theBuffer
	copy $theBuffer
}

# Cut the selection in theBuffer to the current clipboard.
# If there is no selection in theBuffer, then select the line the cursor is on.
proc SmartCut {theBuffer} \
{
	SelectLineWhenNoSelection $theBuffer
	cut $theBuffer
}

# Replace the entire contents of a buffer with the passed text, and clear undos on the buffer.
# NOTE: it would be bad if this were called on a document that had been edited for hours and not
# saved! -- It would blow away the entire contents.
proc TextToBuffer {theBuffer theText} \
{
	selectall $theBuffer;								# select everything, so insert will remove it
	insert $theBuffer $theText;							# write over everything in theBuffer
	flushundos $theBuffer;								# get rid of any undo information for this buffer
}

# Move the stagger position to the next spot for a new staggered window.
proc UpdateStaggerPosition {} \
{
	uplevel #0 \
		{
		incr staggerOpenX $staggerIncrementX;
		incr staggerOpenY $staggerIncrementY;
		if {$staggerOpenX>$staggerMaxX} \
			{
			set staggerOpenX $staggerInitialX;
		 	set staggerOpenY $staggerInitialY
			}
		}
}

# Tile the windows on the display.
proc TileWindows {} \
{
	set horizontalNumber 4;								# number of windows we want across
	set verticalNumber 4;								# number we want down
	set horizontalBorder 30;							# number of pixels to leave around edges of screen
	set verticalBorder 30;
	set screendimensions [screensize]
	set width [lindex $screendimensions 0]
	set height [lindex $screendimensions 1]
	set windowWidth [expr ($width-($horizontalBorder*2))/$horizontalNumber];	# get the width of each individual window
	set windowHeight [expr ($height-($verticalBorder*2))/$verticalNumber];		# get the height of each individual window
	set horizontalIndex 0; set verticalIndex 0;			# init counters that tell us where we are
	foreach theWindow [windowlist] \
		{
		if {![LowPriority $theWindow]} \
			{
			setrect $theWindow [expr $horizontalBorder+$horizontalIndex*$windowWidth] [expr $verticalBorder+$verticalIndex*$windowHeight] $windowWidth $windowHeight
			incr horizontalIndex
			if {$horizontalIndex>=$horizontalNumber} \
				{
				set horizontalIndex 0;
				incr verticalIndex;
				if {$verticalIndex>=$verticalNumber} \
					{
					set verticalIndex 0;
					}
				}
			}
		}
}

# Stack the windows on the display.
proc StackWindows {} \
{
	global staggerInitialX staggerInitialY staggerOpenX staggerOpenY windowWidth windowHeight

	set staggerOpenX $staggerInitialX;					# set the initial default X
	set staggerOpenY $staggerInitialY;					# set the initial default Y
	foreach theWindow [windowlist] \
		{
		if {![LowPriority $theWindow]} \
			{
			setrect $theWindow $staggerOpenX $staggerOpenY $windowWidth $windowHeight
			UpdateStaggerPosition
			}
		}
}

# Create a window onto theBuffer, using default values for position,size,font,tabSize,color.
proc OpenDefaultWindow {theBuffer} \
{
	global staggerOpenX staggerOpenY screenWidth screenHeight windowWidth windowHeight defaultFont defaultTabSize defaultForegroundColor defaultBackgroundColor windowParameterList

	if {[haswindow $theBuffer]} \
		{
		settopwindow $theBuffer;						# if it was already open, then just put it to the top
		} \
	else \
		{
		if {[LowPriority $theBuffer]} \
			{
			set openWidth [expr $screenWidth*3/4]
			set openHeight [expr $screenHeight*1/8]
			openwindow $theBuffer [expr ($screenWidth-$openWidth)/2] [expr $screenHeight-$openHeight] $openWidth $openHeight $defaultFont $defaultTabSize black lightskyblue
			} \
		else \
			{
			set hadMatch 0
			set numItems [llength $windowParameterList]
			set theItem 0
			while {$theItem<$numItems && !$hadMatch} \
				{
				set theList [lindex $windowParameterList $theItem]
				incr theItem
				if {[regexp [lindex $theList 0] $theBuffer]} \
					{
					openwindow $theBuffer $staggerOpenX $staggerOpenY $windowWidth $windowHeight [lindex $theList 1] [lindex $theList 2] [lindex $theList 3] [lindex $theList 4]
					set hadMatch 1
					}
				}
			if {!$hadMatch} \
				{
				openwindow $theBuffer $staggerOpenX $staggerOpenY $windowWidth $windowHeight $defaultFont $defaultTabSize $defaultForegroundColor $defaultBackgroundColor
				}
			UpdateStaggerPosition;
			}
		homewindow $theBuffer;							# go to the cursor/selection
		}
}

# Return a good name for a new window.
proc NewWindowName {} \
{
	global untitledCounter;								# reference the global
	set theName "Untitled-$untitledCounter";			# get new name
	incr untitledCounter;								# next time, make the count one larger
	return $theName;									# return the name of the new window
}

# Create a new window with an "Untitled-n" name, return the name.
proc NewWindow {} \
{
	OpenDefaultWindow [set theName [newbuffer [NewWindowName]]];	# open a new window with new name
	return $theName;									# return the name of the new window
}

# Open the passed file name into a buffer, and record the modification time
# of the file into the variable "mtime" attached to the buffer
# if there is a problem, fail just like openbuffer would
proc OpenBufferRecordMtime {theFile} \
{
	if {[catch {openbuffer $theFile} message]==0} \
		{
		# see if there is ALREADY a modification time tag associated with this file, if so, check against current mtime and complain if they differ
		# NOTE: there could already be a variable assigned if the file was open already
		if {[catch {getbuffervariable $message mtime} oldmtime]==0} \
			{
			if {[catch {file mtime $message} newmtime]==0} \
				{
				if {$oldmtime!=$newmtime} \
					{
					okdialog "WARNING!\n\nIt looks like another application modified:\n$message\nwhile it was open for editing.\n"
					}
				} \
			else \
				{
				okdialog "WARNING!\n\nA buffer exists for the requested file:\n$message\nBut could not read mtime.\n"
				}
			} \
		else \
			{
			catch {setbuffervariable $message mtime [file mtime $message]};
			}
		return $message;
		} \
	else \
		{
		return -code error $message;
		}
}

# Attempt to open a list of files one at a time.
# If any fails to open, report why, and give the user a chance to cancel.
proc OpenList {theList} \
{
	set numItems [llength $theList]
	set theItem 0
	while {$theItem<$numItems} \
		{
		set theFile [lindex $theList $theItem]
		incr theItem
		if {[catch {OpenDefaultWindow [OpenBufferRecordMtime $theFile]} errorMessage]!=0} \
			{
			if {$theItem==$numItems} \
				{
				okdialog "Failed to open '$theFile'\n$errorMessage"
				} \
			else \
				{
				okcanceldialog "Failed to open '$theFile'\n$errorMessage\n\nContinue?"
				}
			}
		}
}

# Just like OpenList, but tries to identify filenames with line numbers.
# If it can find a line number, then it opens the file to that line.
# The forms that are checked for are:
# filename:nnnn
# filename(nnnn)
# File filename;Line nnnn
# #include <filename>	(looks in /usr/include)
# #include "filename"	(looks in current directory)
# filename\n			(ignores newlines)
proc SmartOpenList {theList} \
{
	set numItems [llength $theList]
	set theItem 0
	while {$theItem<$numItems} \
		{
		set theFile [lindex $theList $theItem]
		set theLine 0
		if {[regexp {^([^:]+):([0-9]+)} $theFile whole tempFile tempLine]||\
			[regexp {^([^ (]+) *\(([0-9]+)\)} $theFile whole tempFile tempLine]||\
			[regexp {^File *'?([^' ]+)'? *; *Line *([0-9]+)} $theFile whole tempFile tempLine]}\
			{
			set theFile $tempFile; set theLine $tempLine
			} \
		else \
			{
			if {[regexp "^\[ \t\]*#include\[ \t\]+<(\[^>\]+)>" $theFile whole tempFile]} \
				{
				set theFile "/usr/include/$tempFile"
				} \
			else \
				{
				if {[regexp "^\[ \t\]*#include\[ \t\]+\"(\[^\"\]+)\"" $theFile whole tempFile]} \
					{
					set theFile $tempFile
					} \
				else \
					{
					if {[regexp "^(\[^\n\]+)" $theFile whole tempFile]} \
						{
						set theFile $tempFile
						} \
					}
				}
			}
		incr theItem
		# see if the file name part globs to anything (if so, open those files)
		if {[catch {glob $theFile} globList]==0} \
			{
			set globItems [llength $globList]
			set globItem 0
			while {$globItem<$globItems} \
				{
				set theFile [lindex $globList $globItem]
				if {[catch {OpenDefaultWindow [set newBuffer [OpenBufferRecordMtime $theFile]]} errorMessage]!=0} \
					{
					if {$globItem==$globItems} \
						{
						okdialog "Failed to open '$theFile'\n$errorMessage"
						} \
					else \
						{
						okcanceldialog "Failed to open '$theFile'\n$errorMessage\n\nContinue?"
						}
					} \
				else \
					{
					if {$theLine!=0} {selectline $newBuffer $theLine;homewindow $newBuffer}
					}
				incr globItem
				}
			} \
		else \
			{
			if {[catch {OpenDefaultWindow [set newBuffer [OpenBufferRecordMtime $theFile]]} errorMessage]!=0} \
				{
				if {$theItem==$numItems} \
					{
					okdialog "Failed to open '$theFile'\n$errorMessage"
					} \
				else \
					{
					okcanceldialog "Failed to open '$theFile'\n$errorMessage\n\nContinue?"
					}
				} \
			else \
				{
				if {$theLine!=0} {selectline $newBuffer $theLine;homewindow $newBuffer}
				}
			}
		}
}

# Attempt to include a list of files one at a time, into the given buffer.
# If any fails to include, report why, and give the user a chance to cancel.
proc IncludeList {theBuffer theList} \
{
	set numItems [llength $theList]
	set theItem 0
	while {$theItem<$numItems} \
		{
		set theFile [lindex $theList $theItem]
		incr theItem
		if {[catch {insertfile $theBuffer $theFile} errorMessage]!=0} \
			{
			if {$theItem==$numItems} \
				{
				okdialog "Failed to include '$theFile'\n$errorMessage"
				} \
			else \
				{
				okcanceldialog "Failed to include '$theFile'\n$errorMessage\n\nContinue?"
				}
			}
		}
}

# Swap the top two windows on the screen.
proc SwapWindows {} \
{
	set windowList [windowlist]
	if {[llength $windowList]>=2} \
		{
		settopwindow [lindex $windowList 1]
		} \
	else \
		{
		beep
		}
}

# Bring the bottom window to the top.
proc RotateWindows {} \
{
	set windowList [windowlist]
	set numWindows [llength $windowList]
	if {$numWindows>=1} \
		{
		incr numWindows -1
		settopwindow [lindex $windowList $numWindows]
		} \
	else \
		{
		beep
		}
}

# Return the active window if there is one, otherwise
# just beep, and return an error.
proc ActiveWindowOrBeep {} \
{
	if {[catch {activewindow} message]==0} \
		{
		return $message
		}
	beep
	return -code error
}

# Report the current clipboard, or a message that indicates that
# there is none
proc ShowCurrentClipboard {} \
{
	if {[catch {getclipboard} message]==0} \
		{
		okdialog "Current clipboard:\n\n$message"
		} \
	else \
		{
		okdialog "There is no current clipboard"
		}
}

# Open the current clipboard, or show a message that indicates that
# there is none
proc OpenCurrentClipboard {} \
{
	if {[catch {getclipboard} message]==0} \
		{
		OpenDefaultWindow $message
		} \
	else \
		{
		okdialog "There is no current clipboard"
		}
}

# Quiz user to find out if he really wants to "revert".
proc AskRevert {theBuffer} \
{
	if {[isdirty $theBuffer]} \
		{
		okcanceldialog "Do you really want to discard changes to:\n'$theBuffer'"
		revertbuffer $theBuffer
		} \
	else \
		{
		revertbuffer $theBuffer;			# may be reverting because the file was changed, so just do it without asking
		}
}

# Get name of file to "save as", then do it for the given buffer.
# NOTE: this returns the new name of the buffer if it completes successfully.
proc AskSaveAs {theBuffer} \
{
	set newPath [savedialog "Save File:" $theBuffer]
	if {[file exists $newPath]} \
		{
		okcanceldialog "File:\n'$newPath'\nalready exists. Overwrite?"
		}
	if {[catch {savebufferas $theBuffer $newPath} message]==0} \
		{
		catch {setbuffervariable $message mtime [file mtime $message]};		# update the modification time so we can look at it when saving
		} \
	else \
		{
		okdialog "Failed to save:\n'$newPath'\n$message"
		return -code error
		}
	return $message;					# return the new name
}

# Get name of file to "save to", then do it for the given buffer.
proc AskSaveTo {theBuffer} \
{
	set newPath [savedialog "Save Copy To:" $theBuffer]
	if {[file exists $newPath]} \
		{
		okcanceldialog "File:\n'$newPath'\nalready exists. Overwrite?"
		}
	if {[catch {savebufferto $theBuffer $newPath} errorMessage]!=0} \
		{
		okdialog "Failed to save:\n'$newPath'\n$errorMessage"
		return -code error
		}
}

# See if theBuffer is not linked to a file, and if not, ask for
# a file name to save it to, else just save it.
# Either way, return the name of the buffer after it is saved,
# as it may have changed during the save process.
proc AskSave {theBuffer} \
{
	if {[fromfile $theBuffer]} \
		{
		# see if there is a modification time tag associated with this file, if so, check against current mtime and complain if they differ
		if {[catch {getbuffervariable $theBuffer mtime} oldmtime]==0} \
			{
			if {[catch {file mtime $theBuffer} newmtime]==0} \
				{
				if {$oldmtime!=$newmtime} \
					{
					okcanceldialog "WARNING!\n\nIt looks like another application modified:\n$theBuffer\nwhile it was open for editing.\n\nWould you like to save anyway?"
					}
				} \
			else \
				{
				okcanceldialog "WARNING!\n\nIt looks like another application deleted:\n$theBuffer\nwhile it was open for editing.\n\nWould you like to save anyway?"
				}
			}

		if {[catch {savebuffer $theBuffer} message]==0} \
			{
			catch {setbuffervariable $theBuffer mtime [file mtime $theBuffer]};		# update the modification time so we can look at it next time we save
			} \
		else \
			{
			okdialog "Failed to save:\n'$theBuffer'\n$message"
			return -code error
			}
		} \
	else \
		{
		set theBuffer [AskSaveAs $theBuffer];				# pick up the new name of the buffer
		}
	return $theBuffer
}

# Ask the user if he really wants to save all the dirty windows, if so, do it.
proc AskSaveAll {} \
{
	ActiveWindowOrBeep;										# make sure there is a window, if not, just beep and leave
	okcanceldialog "Really save all modified windows?"
	foreach theWindow [windowlist] \
		{
		if {[isdirty $theWindow]} \
			{
			if {[catch {AskSave $theWindow} errorMessage]!=0} \
				{
				okdialog "Save All aborted\n"
				return -code error
				}
			}
		}
}

# See if theBuffer is dirty before closing.
# If it is, give user a chance to save.
proc AskClose {theBuffer} \
{
	# See if theBuffer points to a something low-priority, we just close the windows on them, not the buffer
	if {[LowPriority $theBuffer]} \
		{
		closewindow $theBuffer
		} \
	else \
		{
		if {[isdirty $theBuffer]} \
			{
			if {[yesnodialog "Save Changes To:\n'$theBuffer'\nBefore Closing?"]} \
				{
				set theBuffer [AskSave $theBuffer];				# if it is saved, get the new name so we can close it
				}
			}
		closebuffer $theBuffer
		}
}

# Attempt to get lpr to output this document to the local printer
proc PrintBuffer {theBuffer} \
{
	set command [textdialog "If needed, modify the print command below:" "lpr -#1 -J$theBuffer"]
	setmark $theBuffer temp;							# remember what was selected, we will not disturb it
	selectall $theBuffer;								# make a selection of all of the text in the passed buffer
	if {[catch {eval exec $command << {[lindex [selectedtextlist $theBuffer] 0]}} theResult]!=0} \
		{
		okdialog "Print failed:\n$theResult\n"
		}
	gotomark $theBuffer temp;							# put selection back
	clearmark $theBuffer temp;							# get rid of temp selection
}

# print the first selection to the local printer
proc PrintSelection {theBuffer} \
{
	if {[lindex [selectioninfo $theBuffer] 6]} \
		{
		set command [textdialog "If needed, modify the print command below:" "lpr -#1 -J$theBuffer"]
		if {[catch {eval exec $command << {[lindex [selectedtextlist $theBuffer] 0]}} theResult]!=0} \
			{
			okdialog "Print failed:\n$theResult\n"
			}
		} \
	else \
		{
		beep
		}
}

# Attempt to close all open windows, asking to save any that are dirty.
# If the user cancels, then back out, otherwise, quit.
proc TryToQuit {} \
{
	foreach theWindow [windowlist] \
		{
		AskClose $theWindow
		}
	forceQUIT
}

# Just like find, but will place the result in a dialog if it fails, and home if it succeeds, or beep if it finds nothing.
proc FindBeep {theWindow theBuffer backwardsFlag wrapAroundFlag selectionExpressionFlag ignoreCaseFlag} \
{
	if {[catch {find $theWindow $theBuffer $backwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag} message]==0} \
		{
		if {$message!=-1} {homewindow $theWindow $message} else {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Just like findall, but will place the result in a dialog if it fails, and home if it succeeds, or beep if it finds nothing.
proc FindAllBeep {theWindow theBuffer backwardsFlag wrapAroundFlag selectionExpressionFlag ignoreCaseFlag limitScopeFlag} \
{
	if {[catch {findall $theWindow $theBuffer $backwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $limitScopeFlag} message]==0} \
		{
		if {$message!=-1} {homewindow $theWindow $message} else {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Just like replace, but will place the result in a dialog if it fails, and home if it succeeds, or beep if it finds nothing.
proc ReplaceBeep {theWindow theFindBuffer theReplaceBuffer backwardsFlag wrapAroundFlag selectionExpressionFlag ignoreCaseFlag replaceProcFlag} \
{
	if {[catch {replace $theWindow $theFindBuffer $theReplaceBuffer $backwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $replaceProcFlag} message]==0} \
		{
		if {$message!=-1} {homewindow $theWindow $message} else {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Just like replaceall, but will place the result in a dialog if it fails, and home if it succeeds, or beep if it finds nothing.
proc ReplaceAllBeep {theWindow theFindBuffer theReplaceBuffer backwardsFlag wrapAroundFlag selectionExpressionFlag ignoreCaseFlag limitScopeFlag replaceProcFlag} \
{
	if {[catch {replaceall $theWindow $theFindBuffer $theReplaceBuffer $backwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $limitScopeFlag $replaceProcFlag} message]==0} \
		{
		if {$message!=-1} {homewindow $theWindow $message} else {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Ask the user just what he wishes to search for, and how, then do it if he does not cancel.
proc AskSearch {theWindow} \
{
	global searchBackwardsFlag wrapAroundFlag selectionExpressionFlag ignoreCaseFlag limitScopeFlag replaceProcFlag
	set searchType [searchdialog "" findBuffer replaceBuffer searchBackwardsFlag wrapAroundFlag selectionExpressionFlag ignoreCaseFlag limitScopeFlag replaceProcFlag]
	switch $searchType \
		{
		find
			{
			FindBeep $theWindow findBuffer $searchBackwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag
			}
		findall
			{
			FindAllBeep $theWindow findBuffer $searchBackwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $limitScopeFlag
			}
		replace
			{
			ReplaceBeep $theWindow findBuffer replaceBuffer $searchBackwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $replaceProcFlag
			}
		replaceall
			{
			ReplaceAllBeep $theWindow findBuffer replaceBuffer $searchBackwardsFlag $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $limitScopeFlag $replaceProcFlag
			}
		}
}

# Used for find same backwards/forwards.
proc FindNext {theWindow backward} \
{
	global wrapAroundFlag selectionExpressionFlag ignoreCaseFlag
	FindBeep $theWindow findBuffer $backward $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag
}

# Used for replace same backwards/forwards.
proc ReplaceNext {theWindow backward} \
{
	global wrapAroundFlag selectionExpressionFlag ignoreCaseFlag replaceProcFlag
	ReplaceBeep $theWindow findBuffer replaceBuffer $backward $wrapAroundFlag $selectionExpressionFlag $ignoreCaseFlag $replaceProcFlag
}

# Replace all selections with some given text.
proc ReplaceSelections {theWindow} \
{
	TextToBuffer tempFindBuffer {([^]+)};							# load up the expression (find anything, mark as \0)
	TextToBuffer tempReplaceBuffer [textdialog "Replacement string?"];	# load up the replacement
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 0;	# do the replacement
}

# Align text left, removing all tabs or spaces at the start of lines in the selection.
proc AlignLeft {theBuffer} \
{
	TextToBuffer tempFindBuffer {^[\t ]+(.*)|(.+)};				# load up the expression
	TextToBuffer tempReplaceBuffer {\0\1};						# load up the replacement
	if {[catch {replaceall $theBuffer tempFindBuffer tempReplaceBuffer 0 0 1 0 1 0} message]==0} \
		{
		if {$message==-1} {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Shift text left, removing tabs or spaces at the start of lines in the selection.
proc ShiftLeft {theBuffer} \
{
	TextToBuffer tempFindBuffer {^[\t ](.*)|(.+)};				# load up the expression (expression is slightly strange, so that lines which are not altered are not deselected)
	TextToBuffer tempReplaceBuffer {\0\1};						# load up the replacement
	if {[catch {replaceall $theBuffer tempFindBuffer tempReplaceBuffer 0 0 1 0 1 0} message]==0} \
		{
		if {$message==-1} {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Shift text right, adding tabs at the start of lines in the selection.
proc ShiftRight {theBuffer} \
{
	TextToBuffer tempFindBuffer {^(.+)};						# load up the expression
	TextToBuffer tempReplaceBuffer {	\0};					# load up the replacement
	if {[catch {replaceall $theBuffer tempFindBuffer tempReplaceBuffer 0 0 1 0 1 0} message]==0} \
		{
		if {$message==-1} {beep}
		} \
	else \
		{
		okdialog $message
		}
}

# Report interesting information about the passed buffer
proc BufferInfo {theBuffer} \
{
	set textInfo [textinfo $theBuffer]
	set textLines [lindex $textInfo 0]
	set textChars [lindex $textInfo 1]

	set selectionInfo [selectioninfo $theBuffer]
	set startPosition [lindex $selectionInfo 0]
	set endPosition [lindex $selectionInfo 1]
	set startLine [lindex $selectionInfo 2]
	set endLine [lindex $selectionInfo 3]
	set startLinePosition [lindex $selectionInfo 4]
	set endLinePosition [lindex $selectionInfo 5]
	set totalSegments [lindex $selectionInfo 6]
	set totalSpan [lindex $selectionInfo 7]

	if {$endLinePosition&&$totalSpan} \
		{
		set totalLines [expr $endLine-$startLine+1];	# this is kind of screwy, but it gives the results that are easiest for the user to understand
		} \
	else \
		{
		set totalLines [expr $endLine-$startLine]
		}

	okdialog "Total lines              $textLines\nTotal bytes              $textChars\nSelection start position $startPosition ($startLine:$startLinePosition)\nSelection end position   $endPosition ($endLine:$endLinePosition)\nTotal selection segments $totalSegments\nTotal selected chars     $totalSpan\nTotal lines spanned      $totalLines\n"
}

# Pipe the selections through a unix command, collect output, and replace selections.
proc PipeSelection {theWindow} \
{
	set command [textdialog "Enter command to pipe selections through:"]
	TextToBuffer tempFindBuffer {[^]+};							# load up the expression (find absolutely anything)
	TextToBuffer tempReplaceBuffer "catch \{exec -keepnewline $command <<\$found\} message; set message";	# load up the replacement, return results of command
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
}

# Convert all letters of the given selection to upper case.
proc UppercaseSelection theWindow \
{
	TextToBuffer tempFindBuffer {[^]+};							# load up the expression (find anything)
	TextToBuffer tempReplaceBuffer "string toupper \$found";	# load up the replacement
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
}

# Convert all letters of the given selection to lower case.
proc LowercaseSelection theWindow \
{
	TextToBuffer tempFindBuffer {[^]+};							# load up the expression (find anything)
	TextToBuffer tempReplaceBuffer "string tolower \$found";	# load up the replacement
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
}

# Locate all the numbers in the current selection, and increment them by the given amount.
proc IncrementSelection {theWindow amount} \
{
	TextToBuffer tempFindBuffer {-?[0-9]+};						# load up the expression (find any number including negative ones)
	TextToBuffer tempReplaceBuffer "scan \$found %d found;expr \$found + $amount";	# load up the replacement, return the result of adding amount
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
}

# Replace each selection by an incrementing number.
proc EnumerateSelection {theWindow startAt amount} \
{
	global enumStart
	set enumStart $startAt
	TextToBuffer tempFindBuffer {.+};							# load up the expression (find anything)
	TextToBuffer tempReplaceBuffer "global enumStart;set temp \$enumStart;incr enumStart $amount;format %d \$temp";		# load up the replacement, return an incremented number
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
}

# Report the sum of all selected numbers.
proc SumSelection {theWindow} \
{
	setmark $theWindow temp;									# remember what was selected, we will not disturb it
	TextToBuffer tempFindBuffer {-?[0-9]+};						# load up the expression (find any number including negative ones)
	if {[catch {findall $theWindow tempFindBuffer 0 0 1 0 1} message]==0} \
		{
		if {$message!=-1} \
			{
			set total 0
			foreach theNumber [selectedtextlist $theWindow] \
				{
				scan $theNumber %d theNumber;					# convert number with leading 0's to DECIMAL, not octal!
				set total [expr $total+$theNumber]
				}
			okdialog "Sum = $total"
			} \
		else \
			{
			beep
			}
		} \
	else \
		{
		okdialog $message
		}
	gotomark $theWindow temp;									# put back the user's selection
	clearmark $theWindow temp;									# get rid of temp selection
}

# Sort the selections, and replace them in sorted order.
proc SortSelection {theWindow} \
{
	global sortedArray sortedIndex
	catch {unset sortedArray}
	set sortedIndex 0
	foreach theElement [lsort [selectedtextlist $theWindow]] \
		{
		set sortedArray($sortedIndex) $theElement;				# copy elements into an array to speed things up
		incr sortedIndex
		}
	set sortedIndex 0
	TextToBuffer tempFindBuffer {[^]+};							# load up the expression (find absolutely anything)
	TextToBuffer tempReplaceBuffer {global sortedArray sortedIndex;incr sortedIndex;set sortedArray([expr $sortedIndex-1])};		# load up the replacement, return an entry from the array
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
	catch {unset sortedArray}
	unset sortedIndex
}

# Replace the selections in reverse order.
proc ReverseSelection {theWindow} \
{
	global reverseArray reverseIndex
	catch {unset reverseArray}
	set reverseIndex 0
	foreach theElement [selectedtextlist $theWindow] \
		{
		set reverseArray($reverseIndex) $theElement;			# copy elements into an array to speed things up
		incr reverseIndex
		}
	TextToBuffer tempFindBuffer {[^]+};							# load up the expression (find absolutely anything)
	TextToBuffer tempReplaceBuffer {global reverseArray reverseIndex;incr reverseIndex -1;set reverseArray($reverseIndex)};	# load up the replacement, return an entry from the array
	ReplaceAllBeep $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 1 1;	# do the replacement
	catch {unset reverseArray}
	unset reverseIndex
}

# Get a manual page, and dump it into a window.
proc ManPage theParams \
{
	global staggerOpenX staggerOpenY screenWidth screenHeight windowWidth windowHeight

	set theName [newbuffer [NewWindowName]];			# make a buffer to hold the man page
	execute $theName "catch \{exec man $theParams\} theResult; set theResult";
	setselectionends $theName 0 0;						# move to the top of the man page buffer
# man thinks it is outputting to a printer where it can backspace over stuff to get effects
# like BOLD, or dot points. The replace strips out the mess
	TextToBuffer tempFindBuffer {.\x08};				# load up the expression
	TextToBuffer tempReplaceBuffer {};					# replace with nothing
	catch {replaceall $theName tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message;	# do this, ignore any errors
	setselectionends $theName 0 0;						# move to the top of the buffer again
# get rid of any huge page gaps
	TextToBuffer tempFindBuffer {^\n{2,}};				# load up the expression
	TextToBuffer tempReplaceBuffer "\n";				# replace with nothing
	catch {replaceall $theName tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message;	# do this, ignore any errors
	setselectionends $theName 0 0;						# move to the top of the buffer again

	flushundos $theName;								# get rid of any undos we made here, just to be nice
	cleardirty $theName;								# make it non-modified
	openwindow $theName $staggerOpenX $staggerOpenY [expr 8*80] $windowHeight "" 8 black lightyellow;	# make a new window, set width to roughly 80 columns of an 7 pixel wide font
	UpdateStaggerPosition;
}

# Refill a selected paragraph, so that the lines of text do not extend past the given column.
# This is a little ugly, but it does the job quite nicely
proc RefillSelection theBuffer \
{
	set limit [expr [textdialog "Max column:" 70]];		# get column limit from user

	set ends [getselectionends $theBuffer]
	set start [lindex $ends 0]
	set end [lindex $ends 1]
	if {$start!=$end} \
		{
		setselectionends $theBuffer $start $end;		# make only one selection, even if more than one

		newbuffer tempBuffer;							# create a place to do some messing around in
		copy $theBuffer tempBuffer;						# move text in question to temp buffer

		TextToBuffer tempFindBuffer "^\[ \\t\]+";		# remove white space at starts of lines
		TextToBuffer tempReplaceBuffer {};
		setselectionends tempBuffer 0 0;				# move to top for replace
		catch {replaceall tempBuffer tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message;	# do this, ignore any errors

		TextToBuffer tempFindBuffer "\[ \\t\]+";		# make tabs, or multiple spaces into single spaces
		TextToBuffer tempReplaceBuffer { };
		setselectionends tempBuffer 0 0;				# move to top for replace
		catch {replaceall tempBuffer tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message;	# do this, ignore any errors

		TextToBuffer tempFindBuffer "(^\\n)|(\\n)";		# make all newlines at the ends of lines into spaces
		TextToBuffer tempReplaceBuffer {if {[string length $1] > 0} {set found { }}; set found};
		selectall tempBuffer;							# select all so replace will start at the bottom (replacing backwards)
		catch {replaceall tempBuffer tempFindBuffer tempReplaceBuffer 1 0 1 0 0 1} message;	# do this, ignore any errors

		TextToBuffer tempFindBuffer "(.\{1,$limit\})((\[ \\t\]+)|(.$))";	# cut lines to length at whitespace
		TextToBuffer tempReplaceBuffer {if {[string length $2] > 0} {format "%s\n" $0} else {format "%s%s" $0 $3}};
		setselectionends tempBuffer 0 0;				# move to top for replace
		catch {replaceall tempBuffer tempFindBuffer tempReplaceBuffer 0 0 1 0 0 1} message;	# do this, ignore any errors

		paste $theBuffer tempBuffer;					# place results back, write over old selection

		closebuffer tempBuffer;
		} \
	else \
		{
		beep;
		}
}

# Check spelling in theBuffer, make a new window containing the misspelled words.
proc SpellDocument theBuffer \
{
	global staggerOpenX staggerOpenY screenWidth screenHeight windowWidth windowHeight

	set theName [newbuffer [NewWindowName]];			# make a buffer to hold the output of the spell command
	setmark $theBuffer temp;							# remember what was selected, we will not disturb it
	selectall $theBuffer;								# make a selection of all of the text in the passed buffer
	execute $theName {catch {exec spell << [lindex [selectedtextlist $theBuffer] 0]} theResult; set theResult};
	gotomark $theBuffer temp;							# put selection back
	setselectionends $theName 0 0;						# move to the top of the output buffer

	flushundos $theName;								# get rid of any undos we made here, just to be nice
	cleardirty $theName;								# make it non-modified
	openwindow $theName $staggerOpenX $staggerOpenY [expr 7*50] $windowHeight "" 8 black lightyellow;	# make a new window, set width to roughly 50 columns of an 7 pixel wide font
	UpdateStaggerPosition;
	clearmark $theBuffer temp;							# get rid of temp selection
}

# Get an address, and mail the contents of theBuffer
# to that address.
proc MailDocument theBuffer \
{
	set recipient [textdialog "Mail $theBuffer To:"]
	setmark $theBuffer temp;							# remember what was selected, we will not disturb it
	selectall $theBuffer;								# make a selection of all of the text in the passed buffer
	if {[catch {exec mail "$recipient" << [lindex [selectedtextlist $theBuffer] 0]} theResult]!=0} \
		{
		okdialog "Mail failed:\n$theResult\n"
		}
	gotomark $theBuffer temp;							# put selection back
	clearmark $theBuffer temp;							# get rid of temp selection
}

# Strip the white space at the ends of lines, attempt to leave the selection as it was.
# return the number of characters removed
proc StripWhite {theWindow} \
{
	set initialChars [lindex [textinfo $theWindow] 1];			# find out how many characters we have at the start
	set numRemoved 0;
	setmark $theWindow temp;									# remember what was selected, we will not disturb it
	TextToBuffer tempFindBuffer {[ \t]+$};						# load up the expression
	TextToBuffer tempReplaceBuffer {};							# replace with nothing
	setselectionends $theWindow 0 0;							# move to the top of the buffer to perform the search
	if {[catch {replaceall $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message]==0} \
		{
		set finalChars [lindex [textinfo $theWindow] 1];		# find out how many characters we have now that we are done
		set numRemoved [expr $initialChars-$finalChars];
		} \
	else \
		{
		okdialog $message
		}
	gotomark $theWindow temp;									# put back the user's selection
	clearmark $theWindow temp;									# get rid of temp selection
	return $numRemoved
}

# Set line termination to the given sequence
proc SetLineTermination {theWindow theTermination} \
{
	setmark $theWindow temp;									# remember what was selected, we will not disturb it
	TextToBuffer tempFindBuffer {\r\n|\r|\n};					# load up the expression
	TextToBuffer tempReplaceBuffer $theTermination;				# replace with given characters
	setselectionends $theWindow 0 0;							# move to the top of the buffer to perform the search
	catch {replaceall $theWindow tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message
	gotomark $theWindow temp;									# put back the user's selection
	clearmark $theWindow temp;									# get rid of temp selection
}

# Inserts newline if the cursor position is above a given value
# This is a simple way to implement wrapping lines at word boundaries
# Thanks to Juergen Reiss
proc WrapLine theBuffer \
{
	global lineWrap;
	global lineWrapColumn;
	set ends [getselectionends $theBuffer];						# get end of selection
	set start [lindex $ends 0];									# line number
	set pos [lindex [positiontolineoffset $theBuffer $start] 1];# get column number
	if {$lineWrap && $pos>$lineWrapColumn} \
		{
		insert $theBuffer "\n";									# insert newline
		} \
	else \
		{
		insert $theBuffer " ";									# insert space
		}
	homewindow $theBuffer;
}

# Changes the status of line-wrapping
proc WrapOnOff {} \
{
	global lineWrap;									
	set temp "off";
	if {$lineWrap} \
		{
		set temp "on";
		}
	set lineWrap [yesnodialog "Line wrapping is currently '$temp'\nDo you want to have line wrapping on?"];
}

# Changes the column at which lines wrap
proc WrapNewColumn {} \
{
	global lineWrapColumn;									
	set lineWrapColumn [expr [textdialog "New line wrap column:" $lineWrapColumn]];		# get new column limit from user
}

# Locate all things which look like function definitions in C, collect them up, and
# dump them into a new window as prototypes.
proc GetCPrototypes {theWindow} \
{
	setmark $theWindow temp;									# remember what was selected, we will not disturb it
	TextToBuffer tempFindBuffer {^[A-Za-z][^(;,\n]+\([^\n]*\)$};	# load up the expression
	setselectionends $theWindow 0 0;							# move to the top of the buffer to perform the search
	if {[catch {findall $theWindow tempFindBuffer 0 0 1 0 0} message]==0} \
		{
		if {$message!=-1} \
			{
			set theName [newbuffer [NewWindowName]];			# make a buffer to hold the prototypes
			copy $theWindow $theName;							# get selected prototypes into a buffer
			setselectionends $theName 0 0;						# move to the top of the prototypes buffer
			TextToBuffer tempFindBuffer {^static.*\n?};			# get rid of any static functions
			TextToBuffer tempReplaceBuffer {};
			catch {replaceall $theName tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message;	# do this, ignore any errors
			setselectionends $theName 0 0;						# move to the top of the prototypes buffer
			TextToBuffer tempFindBuffer {\)[\t ]*$};			# make all parens at end of line have ;'s
			TextToBuffer tempReplaceBuffer {);};
			catch {replaceall $theName tempFindBuffer tempReplaceBuffer 0 0 1 0 0 0} message;	# do this, ignore any errors

			set end [lindex [textinfo $theName] 1];				# get position of end of buffer
			# see if the last line is terminated with a new-line, if not, add one
			if {[lindex [positiontolineoffset $theName $end] 1]!=0} \
				{
				setselectionends $theName $end $end;			# move to the end of the prototypes buffer
				insert $theName "\n";							# terminate the last line
				}

			setselectionends $theName 0 0;						# move to the top of the prototypes buffer
			flushundos $theName;								# get rid of any undos we made here, just to be nice
			cleardirty $theName;								# make it non-modified
			OpenDefaultWindow $theName;							# give it to the user
			} \
		else \
			{
			beep
			}
		} \
	else \
		{
		okdialog $message
		}
	gotomark $theWindow temp;									# put back the user's selection
	clearmark $theWindow temp;									# get rid of temp selection
}

# Locate all function like lines, bring them up in a list box, allowing the
# user to choose one, then move to the chosen one.
# NOTE: this breaks relatively easily, and does not work for some people's preferred
# function syntax.... It could probably stand to be redone.
# If you make a better version, send it to squirest@icomsim.com, and I will include
# it in the next release.
proc SelectCFunction {theWindow} \
{
	setmark $theWindow temp;									# remember what was selected, we will not disturb it
	TextToBuffer tempFindBuffer {^[A-Za-z][^(;,\n]+\([^\n]*\)$};	# load up the expression
	setselectionends $theWindow 0 0;							# move to the top of the buffer to perform the search
	if {[catch {findall $theWindow tempFindBuffer 0 0 1 0 0} message]==0} \
		{
		if {$message!=-1} \
			{
			catch {listdialog "Choose a Function:" [selectedtextlist $theWindow]} theChoice
			if {[llength $theChoice]>=1} \
				{
				TextToBuffer tempFindBuffer [lindex $theChoice 0]
				setselectionends $theWindow 0 0;				# move to the top of the buffer to perform the search
				catch {find $theWindow tempFindBuffer 0 0 0 0} message
				homewindow $theWindow STRICT
				} \
			else \
				{
				gotomark $theWindow temp;						# put back the user's selection
				}
			} \
		else \
			{
			beep
			gotomark $theWindow temp;							# put back the user's selection
			}
		} \
	else \
		{
		okdialog $message
		gotomark $theWindow temp;								# put back the user's selection
		}
	clearmark $theWindow temp;									# get rid of temp selection
}

# Paste all of the selections in the clipboard as case statements in 'C'
# into theWindow.
# This is handy when columnar selecting a bunch of enumerations, or
# defines to build a large switch statement from.
proc CasePaste {theWindow} \
{
	breakundo $theWindow
	foreach theItem [selectedtextlist [getclipboard]] \
		{
		insert $theWindow "case $theItem:\n\tbreak;\n"
		}
	breakundo $theWindow
}

# Get text to evaluate from theWindow, then stick in a newline.
proc EvalText {theWindow} \
{
	set ends [getselectionends $theWindow]
	set start [lindex $ends 0]
	set end [lindex $ends 1]
	if {$start==$end} \
		{
		set theLine [lindex [positiontolineoffset $theWindow $start] 0];			# get line we want to select
		set start [lineoffsettoposition $theWindow $theLine 0];						# point to the start
		set end [lineoffsettoposition $theWindow [expr $theLine+1] 0];				# point to the end
		}
	if {[lindex [positiontolineoffset $theWindow $end] 1]==0} \
		{
		if {$start!=$end} \
			{
			incr end -1;															# move back past last newline
			}
		}
	setselectionends $theWindow $start $end;										# make only one selection, even if more than one
	set theResult [lindex [selectedtextlist $theWindow] 0]\n;						# get contents of selection to return (add in new line)
	setselectionends $theWindow $end $end;											# move to the end
	breakundo $theWindow;															# force a break in the undo stream for this window
	insert $theWindow "\n";															# add newline
	homewindow $theWindow;															# make sure its on the screen
	set theResult;																	# return this
}

# When a shell command arrives, this handles it
# NOTE: ShellCommand is a procedure that the editor expects to be defined.
# The editor calls this procedure in response to certain events.
# The following is a list of the commands that are currently defined:
# initialargs	-- sent when the editor begins, argv is the list of command line arguments
# timer			-- sent approx once per second, allows for timed saves, or anything else you may want
# open			-- editor is asked to open documents, argv is path name list
# close			-- editor is asked to close documents, argv is buffer list
# quit			-- editor is asked to quit, argv is not used
proc ShellCommand {theCommand args} \
{
	switch $theCommand \
		{
		initialargs
			{
			OpenList $args;							# later, if we want, we can interpret this for command line switches, or whatever
			}
		timer
			{
			}
		open
			{
			OpenList $args
			}
		close
			{
			foreach theWindow $args \
				{
				AskClose $theWindow
				}
			}
		quit
			{
			TryToQuit
			}
		}
}

# Add another directory path to the directory menu.
proc AddDirectoryMenu {thePath} \
{
	addmenu {Directory} LASTCHILD 1 "$thePath" {} "cd \{$thePath\}"
}

# Define the menus.
addmenu {} LASTCHILD 1 "File" "" ""
	addmenu {File} LASTCHILD 1 "New"						{\Kn}				{NewWindow}
	addmenu {File} LASTCHILD 1 "Open..."					{\Ko}				{OpenList [opendialog "Open File:"]}
	addmenu {File} LASTCHILD 1 "Open Selection"				{\Kd}				{SmartOpenList [SelectLineWhenNoSelection [set theWindow [ActiveWindowOrBeep]]; selectedtextlist $theWindow]}
	addmenu {File} LASTCHILD 1 "Include..."					{}					{IncludeList [set theWindow [ActiveWindowOrBeep]] [opendialog "Include:"];homewindow $theWindow}
	addmenu {File} LASTCHILD 0 "space0"						{\S}				{}
	addmenu {File} LASTCHILD 1 "Close"						{\Kw}				{AskClose [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 1 "Close All"					{\KW}				{foreach theWindow [windowlist] {AskClose $theWindow}}
	addmenu {File} LASTCHILD 1 "Save"						{\Ks}				{AskSave [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 1 "Save As..."					{\KS}				{AskSaveAs [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 1 "Save To..."					{}					{AskSaveTo [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 1 "Save All"					{}					{AskSaveAll}
	addmenu {File} LASTCHILD 1 "Revert To Saved"			{}					{AskRevert [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 0 "space1"						{\S}				{}
	addmenu {File} LASTCHILD 1 "Print..."					{\Kp}				{PrintBuffer [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 1 "Print Selection..."			{\KP}				{PrintSelection [ActiveWindowOrBeep]}
	addmenu {File} LASTCHILD 0 "space2"						{\S}				{}
	addmenu {File} LASTCHILD 1 "Quit"						{\Kq}				{TryToQuit}

addmenu {} LASTCHILD 1 "Edit" "" ""
	addmenu {Edit} LASTCHILD 1 "Undo/Redo Toggle"			{\Kz}				{if {[undotoggle [set theWindow [ActiveWindowOrBeep]]]!=0} {homewindow $theWindow} else {beep}}
	addmenu {Edit} LASTCHILD 1 "Undo"						{\Ku}				{if {[undo [set theWindow [ActiveWindowOrBeep]]]!=0} {homewindow $theWindow} else {beep}}
	addmenu {Edit} LASTCHILD 1 "Redo"						{\Ky}				{if {[redo [set theWindow [ActiveWindowOrBeep]]]!=0} {homewindow $theWindow} else {beep}}
	addmenu {Edit} LASTCHILD 1 "Flush Undo/Redo Buffer"		{}					{okcanceldialog "Really flush undos for:\n'[set theWindow [ActiveWindowOrBeep]]'?";flushundos $theWindow}
	addmenu {Edit} LASTCHILD 0 "space0" 					{\S}				{}
	addmenu {Edit} LASTCHILD 1 "Cut"						{\Kx}				{SmartCut [set theWindow [ActiveWindowOrBeep]];homewindow $theWindow;flushundos [getclipboard]}
	addmenu {Edit} LASTCHILD 1 "Copy"						{\Kc}				{SmartCopy [ActiveWindowOrBeep];flushundos [getclipboard]}
	addmenu {Edit} LASTCHILD 1 "Paste"						{\Kv}				{homewindow [set theWindow [ActiveWindowOrBeep]];columnarpaste $theWindow;homewindow $theWindow}
	addmenu {Edit} LASTCHILD 1 "Clear"						{}					{clear [set theWindow [ActiveWindowOrBeep]];homewindow $theWindow}
	addmenu {Edit} LASTCHILD 0 "space1" 					{\S}				{}
	addmenu {Edit} LASTCHILD 1 "Clipboards"					{}					{}
		addmenu {Edit Clipboards} LASTCHILD 1 "Show Current Clipboard..."	{}		{ShowCurrentClipboard}
		addmenu {Edit Clipboards} LASTCHILD 1 "Open Current Clipboard..."	{}		{OpenCurrentClipboard}
		addmenu {Edit Clipboards} LASTCHILD 0 "space0"			{\S}				{setclipboard clip0}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 0"		{\K0}				{setclipboard clip0}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 1"		{\K1}				{setclipboard clip1}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 2"		{\K2}				{setclipboard clip2}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 3"		{\K3}				{setclipboard clip3}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 4"		{\K4}				{setclipboard clip4}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 5"		{\K5}				{setclipboard clip5}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 6"		{\K6}				{setclipboard clip6}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 7"		{\K7}				{setclipboard clip7}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 8"		{\K8}				{setclipboard clip8}
		addmenu {Edit Clipboards} LASTCHILD 1 "Clipboard 9"		{\K9}				{setclipboard clip9}
	addmenu {Edit} LASTCHILD 0 "space2" 					{\S}				{}
	addmenu {Edit} LASTCHILD 1 "Select All"					{\Ka}				{selectall [ActiveWindowOrBeep]}
	addmenu {Edit} LASTCHILD 0 "space3" 					{\S}				{}
	addmenu {Edit} LASTCHILD 1 "Align Left"					{\Kbraceleft}		{AlignLeft [ActiveWindowOrBeep]}
	addmenu {Edit} LASTCHILD 1 "Shift Left"					{\Kbracketleft}		{ShiftLeft [ActiveWindowOrBeep]}
	addmenu {Edit} LASTCHILD 1 "Shift Right"				{\Kbracketright}	{ShiftRight [ActiveWindowOrBeep]}

addmenu {} LASTCHILD 1 "Find" "" ""
	addmenu {Find} LASTCHILD 1 "Find/Replace..."			{\Kf}				{AskSearch [ActiveWindowOrBeep]}
	addmenu {Find} LASTCHILD 1 "Find Same Backwards"		{\KG}				{FindNext [ActiveWindowOrBeep] TRUE}
	addmenu {Find} LASTCHILD 1 "Find Same"					{\Kg}				{FindNext [ActiveWindowOrBeep] FALSE}
	addmenu {Find} LASTCHILD 1 "Find Selection Backwards"	{\KH}				{copy [set theWindow [ActiveWindowOrBeep]] findBuffer;FindNext $theWindow TRUE}
	addmenu {Find} LASTCHILD 1 "Find Selection"				{\Kh}				{copy [set theWindow [ActiveWindowOrBeep]] findBuffer;FindNext $theWindow FALSE}
	addmenu {Find} LASTCHILD 0 "space0" 					{\S}				{}
	addmenu {Find} LASTCHILD 1 "Replace Same Backwards"		{\KT}				{ReplaceNext [ActiveWindowOrBeep] TRUE}
	addmenu {Find} LASTCHILD 1 "Replace Same"				{\Kt}				{ReplaceNext [ActiveWindowOrBeep] FALSE}
	addmenu {Find} LASTCHILD 0 "space1" 					{\S}				{}
	addmenu {Find} LASTCHILD 1 "Replace Selections With..."	{\KR}				{ReplaceSelections [ActiveWindowOrBeep]}
	addmenu {Find} LASTCHILD 0 "space2" 					{\S}				{}
	addmenu {Find} LASTCHILD 1 "Go To Line..."				{\Kl}				{selectline [set theWindow [ActiveWindowOrBeep]] [expr [textdialog "Go to line:"]];homewindow $theWindow}
	addmenu {Find} LASTCHILD 1 "Locate Selection"			{\KL}				{homewindow [ActiveWindowOrBeep] STRICT}
	addmenu {Find} LASTCHILD 0 "space3" 					{\S}				{}
	addmenu {Find} LASTCHILD 1 "Grep -n For..."				{\KF}				{set theData "grep -n [textdialog "Enter pattern, and file list: (eg. void *.c)"]"; set theWindow [NewWindow]; task $theWindow $theData}

addmenu {} LASTCHILD 1 "Window" "" ""
	addmenu {Window} LASTCHILD 1 "Get Information"			{\Ki}				{BufferInfo [ActiveWindowOrBeep]}
	addmenu {Window} LASTCHILD 1 "Set Font..."				{\KE}				{setfont [set theWindow [ActiveWindowOrBeep]] [fontdialog "Choose Font:" [getfont $theWindow]]}
	addmenu {Window} LASTCHILD 1 "Set Tab Size..."			{\Ke}				{settabsize [set theWindow [ActiveWindowOrBeep]] [expr [textdialog "New tab size:" [gettabsize $theWindow]]]}
	addmenu {Window} LASTCHILD 0 "space0"					{\S}				{}
	addmenu {Window} LASTCHILD 1 "Swap Top Windows"			{\Kspace}			{SwapWindows}
	addmenu {Window} LASTCHILD 1 "Rotate Windows"			{\Kgrave}			{RotateWindows}
	addmenu {Window} LASTCHILD 1 "Choose Window..."			{\KO}				{foreach theWindow [listdialog "Choose a Window:" [windowlist]] {settopwindow $theWindow}}
	addmenu {Window} LASTCHILD 0 "space1"					{\S}				{}
	addmenu {Window} LASTCHILD 1 "Stack Windows"			{}					{StackWindows}
	addmenu {Window} LASTCHILD 1 "Tile Windows"				{}					{TileWindows}
	addmenu {Window} LASTCHILD 0 "space2"					{\S}				{}
	addmenu {Window} LASTCHILD 1 "Colors"					{}					{}
		addmenu {Window Colors} LASTCHILD 1 "White on Black"		{}				{setcolors [ActiveWindowOrBeep] white black}
		addmenu {Window Colors} LASTCHILD 1 "Black on White"		{}				{setcolors [ActiveWindowOrBeep] black white}
		addmenu {Window Colors} LASTCHILD 1 "Black on Off-White"	{}				{setcolors [ActiveWindowOrBeep] black lightyellow}
		addmenu {Window Colors} LASTCHILD 1 "Black on Grey"			{}				{setcolors [ActiveWindowOrBeep] black grey}
		addmenu {Window Colors} LASTCHILD 1 "Black on Light Blue"	{}				{setcolors [ActiveWindowOrBeep] black lightblue}
		addmenu {Window Colors} LASTCHILD 1 "Black on Light Green"	{}				{setcolors [ActiveWindowOrBeep] black palegreen}
		addmenu {Window Colors} LASTCHILD 1 "Green on Black"		{}				{setcolors [ActiveWindowOrBeep] green black}
		addmenu {Window Colors} LASTCHILD 1 "Amber on Black"		{}				{setcolors [ActiveWindowOrBeep] gold black}
		addmenu {Window Colors} LASTCHILD 1 "White on Blue"			{}				{setcolors [ActiveWindowOrBeep] white darkslateblue}

addmenu {} LASTCHILD 1 "Mark" "" ""
	addmenu {Mark} LASTCHILD 1 "Mark..."					{}					{setmark [ActiveWindowOrBeep] [textdialog "Mark selection with what name?"]}
	addmenu {Mark} LASTCHILD 1 "Unmark..."					{}					{foreach theMark [listdialog "Delete which mark(s):" [marklist [set theWindow [ActiveWindowOrBeep]]]] {clearmark $theWindow $theMark}}
	addmenu {Mark} LASTCHILD 1 "Goto Mark..."				{}					{foreach theMark [listdialog "Go to which mark:" [marklist [set theWindow [ActiveWindowOrBeep]]]] {gotomark $theWindow $theMark; homewindow $theWindow strict}}

addmenu {} LASTCHILD 1 "Directory" "" ""
	addmenu {Directory} LASTCHILD 1 "Show Current Directory"	{}				{okdialog "Current directory:\n\n[pwd]"}
	addmenu {Directory} LASTCHILD 1 "Set Directory..."		{}					{set thePath [pathdialog "Choose directory:"]; cd "$thePath"; AddDirectoryMenu "$thePath"}
	addmenu {Directory} LASTCHILD 1 "space0"				{\S}				{}

addmenu {} LASTCHILD 1 "Misc" "" ""
	addmenu {Misc} LASTCHILD 1 "About..."					{}					{okdialog "[version]\nTcl: [info tclversion]\nStartup Script: $SCRIPTPATH\n\n[uname -a]\nProcess ID: [pid]\n\nNumber of open windows: [llength [windowlist]]\nNumber of open buffers: [llength [bufferlist]]"}
	addmenu {Misc} LASTCHILD 0 "space0"						{\S}				{}
	addmenu {Misc} LASTCHILD 1 "Strip EOL Whitespace"		{}					{set numRemoved [StripWhite [set theWindow [ActiveWindowOrBeep]]];okdialog "$theWindow\n\nCharacters removed: $numRemoved"}
	addmenu {Misc} LASTCHILD 1 "Set Unix LF Line Termination"		{}			{SetLineTermination [ActiveWindowOrBeep] "\n"}
	addmenu {Misc} LASTCHILD 1 "Set Mac CR Line Termination"		{}			{SetLineTermination [ActiveWindowOrBeep] "\r"}
	addmenu {Misc} LASTCHILD 1 "Set PC CRLF Line Termination"		{}			{SetLineTermination [ActiveWindowOrBeep] "\r\n"}

	addmenu {Misc} LASTCHILD 1 "In All Open Windows ..."	{}					{}
	addmenu {Misc "In All Open Windows ..."} LASTCHILD 1 "Strip EOL Whitespace"			{}	{ActiveWindowOrBeep;set numRemoved 0;foreach theWindow [windowlist] {incr numRemoved [StripWhite $theWindow]};okdialog "Total characters removed: $numRemoved"}
	addmenu {Misc "In All Open Windows ..."} LASTCHILD 1 "Set Unix Line Termination"	{}	{ActiveWindowOrBeep;foreach theWindow [windowlist] {SetLineTermination $theWindow "\n"};okdialog "All windows completed"}
	addmenu {Misc "In All Open Windows ..."} LASTCHILD 1 "Set Mac Line Termination"		{}	{ActiveWindowOrBeep;foreach theWindow [windowlist] {SetLineTermination $theWindow "\r"};okdialog "All windows completed"}
	addmenu {Misc "In All Open Windows ..."} LASTCHILD 1 "Set PC Line Termination"		{}	{ActiveWindowOrBeep;foreach theWindow [windowlist] {SetLineTermination $theWindow "\r\n"};okdialog "All windows completed"}

	addmenu {Misc} LASTCHILD 1 "Uppercase Selection"		{}					{UppercaseSelection [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Lowercase Selection"		{}					{LowercaseSelection [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Increment Selected Numbers..."	{}				{IncrementSelection [ActiveWindowOrBeep] [textdialog "How much to increment:" 1]}
	addmenu {Misc} LASTCHILD 1 "Enumerate Selections..."	{}					{EnumerateSelection [ActiveWindowOrBeep] [textdialog "Starting value:" 1] [textdialog "How much to increment:" 1]}
	addmenu {Misc} LASTCHILD 1 "Sum of Selected Numbers"	{}					{SumSelection [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Sort Selections"			{}					{SortSelection [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Reverse Selections"			{}					{ReverseSelection [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Refill Selection..."		{\Kr}				{RefillSelection [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Line Wrapping..."			{}					{WrapOnOff}
	addmenu {Misc} LASTCHILD 1 "Get man Page..."			{}					{ManPage [textdialog "Enter man page subject:"]}
	addmenu {Misc} LASTCHILD 1 "Spell"						{}					{SpellDocument [ActiveWindowOrBeep]}
	addmenu {Misc} LASTCHILD 1 "Mail To..."					{}					{MailDocument [ActiveWindowOrBeep]}

addmenu {} LASTCHILD 1 "Tasks" "" ""
	addmenu {Tasks} LASTCHILD 1 "Execute Shell Task"		{\KKP_Enter}		{task [set theWindow [ActiveWindowOrBeep]] [EvalText $theWindow]}
	addmenu {Tasks} LASTCHILD 1 "Send EOF To Task"			{\Kk}				{eoftask [ActiveWindowOrBeep]}
	addmenu {Tasks} LASTCHILD 1 "Kill Task"					{\KK}				{killtask [ActiveWindowOrBeep]}
	addmenu {Tasks} LASTCHILD 0 "space0"					{\S}				{}
	addmenu {Tasks} LASTCHILD 1 "Pipe Selections Through..."	{}				{PipeSelection [ActiveWindowOrBeep]}

addmenu {} LASTCHILD 1 "C" "" ""
	addmenu {C} LASTCHILD 1 "make"							{\Km}				{task [set theWindow [ActiveWindowOrBeep];breakundo $theWindow;set theWindow] make}
	addmenu {C} LASTCHILD 0 "space0"						{\S}				{}
	addmenu {C} LASTCHILD 1 "Get Prototypes"				{}					{GetCPrototypes [ActiveWindowOrBeep]}
	addmenu {C} LASTCHILD 1 "Locate Function..."			{}					{SelectCFunction [ActiveWindowOrBeep]}
	addmenu {C} LASTCHILD 0 "space1"						{\S}				{}
	addmenu {C} LASTCHILD 1 "Case Paste"					{}					{CasePaste [ActiveWindowOrBeep]}

# Bind keys to various useful things.
# l=caps lock, s=shift, c=control, 0-7 are additional modifiers such as command, alt, option, command, etc...
# Bind flags		 lsc01234567 (x means don't care, 0 means not pressed, 1 means pressed)

bindkey F1			{x0010000000} {setmark [ActiveWindowOrBeep] F1}
bindkey F1			{x0000000000} {gotomark [set theWindow [ActiveWindowOrBeep]] F1;homewindow $theWindow strict}
bindkey F2			{x0010000000} {setmark [ActiveWindowOrBeep] F2}
bindkey F2			{x0000000000} {gotomark [set theWindow [ActiveWindowOrBeep]] F2;homewindow $theWindow strict}
bindkey F3			{x0010000000} {setmark [ActiveWindowOrBeep] F3}
bindkey F3			{x0000000000} {gotomark [set theWindow [ActiveWindowOrBeep]] F3;homewindow $theWindow strict}
bindkey F4			{x0010000000} {setmark [ActiveWindowOrBeep] F4}
bindkey F4			{x0000000000} {gotomark [set theWindow [ActiveWindowOrBeep]] F4;homewindow $theWindow strict}

bindkey F5			{x0000000000} {WrapOnOff};
bindkey F5			{x0010000000} {WrapNewColumn};
bindkey space		{x0000000000} {WrapLine [ActiveWindowOrBeep]};		# when space is hit, see if above the given column and insert newline if needed
bindkey space		{x0001000000} {insert [ActiveWindowOrBeep] " "};

bindkey KP_Enter	{x0000000000} {execute [set theWindow [ActiveWindowOrBeep]] [EvalText $theWindow];catch {homewindow $theWindow lenient} theMessage}
bindkey Help		{x0000000000} {ManPage [textdialog "Enter man page subject:"]}

# Specify initial paths to be placed into the directory menu.

AddDirectoryMenu /
AddDirectoryMenu [glob ~]
AddDirectoryMenu [pwd]

# Source home rc file if one exists (put a .e93rch file in your home directory to add to the functionality of the standard rc file.)
if {[file exists ~/.e93rch]} \
	{
	source ~/.e93rch;
	}

# Source local rc file if one exists (put a .e93rcl file in project directories where additional project specific functions are desired.)
if {[file exists .e93rcl]} \
	{
	source .e93rcl;
	}
