'From Squeak3.7 of ''4 September 2004'' [latest update: #5989] on 5 November 2008 at 10:19:34 pm'!
Smalltalk renameClassNamed: #TextEditor as: #Editor!
!classDefinition: #Editor category: #'Collections-Text'!
Object subclass: #Editor
	instanceVariableNames: 'sensor morph selectionShowing'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Collections-Text'!

!Editor commentStamp: '<historical>' prior: 0!
New text editors.
TextEditor provides the functionality that used to be in TextMorphEditor. This class is no longer a Controller!!
SimpleEditor provides basic functionality for single line text editing. It does not handle fonts and styles, aligning and Smalltalk utilities. It handles one single line.!
]style[(75 225)cblack;,f4cblack;!

!classDefinition: #OldMinimalStringMorph category: #'Morphic-Old-Basic'!
OldMorph subclass: #OldMinimalStringMorph
	instanceVariableNames: 'font emphasis contents hasFocus'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Morphic-Old-Basic'!

!OldMinimalStringMorph commentStamp: '<historical>' prior: 0!
A greatly reduced OldStringMorph. May be of some use.!

Smalltalk renameClassNamed: #OldStringEditorMorph as: #OldOneLineEditorMorph!
!classDefinition: #OldOneLineEditorMorph category: #'Morphic-Old-Basic'!
OldMorph subclass: #OldOneLineEditorMorph
	instanceVariableNames: 'font emphasis contents hasFocus editor'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Morphic-Old-Basic'!

!OldOneLineEditorMorph commentStamp: '<historical>' prior: 0!
A plain text editor for Morphic. Handles only one line. Similar to OldStringMorphEditor. Does not handle fonts/styles, alignment, Smalltalk utilities and any other advanced stuff in OldTextMorph. Just a simple text editor.!

Smalltalk renameClassNamed: #BasicTextEditor as: #SimpleEditor!
!classDefinition: #SimpleEditor category: #'Collections-Text'!
Editor subclass: #SimpleEditor
	instanceVariableNames: 'string pointIndex markIndex beginTypeInIndex'
	classVariableNames: 'CmdActions ShiftCmdActions'
	poolDictionaries: ''
	category: 'Collections-Text'!

!SimpleEditor commentStamp: '<historical>' prior: 0!
See comment in Editor!

Smalltalk renameClassNamed: #EnhancedTextEditor as: #TextEditor!
!classDefinition: #TextEditor category: #'Collections-Text'!
Editor subclass: #TextEditor
	instanceVariableNames: 'model paragraph pointBlock markBlock beginTypeInBlock emphasisHere otherInterval lastParenLocation oldInterval'
	classVariableNames: 'ChangeText CmdActions FindText ShiftCmdActions TextEditorYellowButtonMenu UndoInterval UndoMessage UndoParagraph UndoSelection Undone'
	poolDictionaries: ''
	category: 'Collections-Text'!

!TextEditor commentStamp: '<historical>' prior: 0!
See comment in Editor!


!Editor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 13:06'!
morph
	^ morph! !

!Editor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 13:06'!
morph: aMorph
	"Install a link back to the morph being edited (esp for text links)"
	morph _ aMorph ! !

!Editor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 14:33'!
userHasEdited
	"Note that my text is free of user edits."

	morph hasUnacceptedEdits: true! !

!Editor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 23:43'!
hasSelection
	^self hasCaret not! !

!Editor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:24'!
selectionInterval
	"Answer the interval that is currently selected."

	^self startIndex to: self stopIndex - 1 ! !

!Editor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 14:02'!
unselect
	self markIndex: self pointIndex! !

!Editor methodsFor: 'current selection' stamp: 'jmv 11/4/2008 13:20'!
deselect
	"If the text selection is visible on the screen, reverse its highlight."

	selectionShowing ifTrue: [self reverseSelection]! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 23:40'!
copySelection: characterStream 
	"Copy the current text selection.  Flushes typeahead."

	sensor keyboard.		"flush character"
	self copySelection.
	^true! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 11/5/2008 10:17'!
cut: characterStream 
	"Cut out the current text selection.  Flushes typeahead."

	sensor keyboard.		"flush character"
	self cut.
	^true! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 23:16'!
noop: characterStream 
	"Unimplemented keyboard command; just ignore it."

	sensor keyboard.	  "flush character"
	^ true! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 11/5/2008 10:11'!
paste: characterStream 
	"Replace the current text selection by the text in the shared buffer.
	 Keeps typeahead."

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self paste.
	^true! !

!Editor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:42'!
initialize
	"Initialize the state of the receiver. Subclasses should include 'super 
	initialize' when redefining this message to insure proper initialization."

	sensor _ InputSensor default! !

!Editor methodsFor: 'menu messages' stamp: 'jmv 11/5/2008 10:16'!
clipboardText

	^ Clipboard clipboardText! !

!Editor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 23:42'!
clipboardTextPut: text

	^ Clipboard clipboardText: text! !

!Editor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 23:42'!
lineSelectAndEmptyCheck: returnBlock
	"If the current selection is an insertion point, expand it to be the entire current line; if after that's done the selection is still empty, then evaluate the returnBlock, which will typically consist of '[^ self]' in the caller -- check senders of this method to understand this."

	self selectLine.  "if current selection is an insertion point, then first select the entire line in which occurs before proceeding"
	self hasSelection ifFalse: [morph flash.  ^ returnBlock value]! !

!Editor methodsFor: 'menu messages' stamp: 'jmv 11/5/2008 10:12'!
paste
	"Paste the text from the shared buffer over the current selection and 
	redisplay if necessary.  Undoer & Redoer: undoAndReselect."

	self replace: self selectionInterval with: self clipboardText and:
		[self selectAt: self pointIndex]! !

!Editor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:10'!
selectAt: characterIndex 
	"Deselect, then place the caret before the character at characterIndex.
	 Be sure it is in view."

	self selectFrom: characterIndex to: characterIndex - 1! !

!Editor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:49'!
selectInterval: anInterval
	"Deselect, then select the specified characters inclusive.
	 Be sure the selection is in view."

	self selectFrom: anInterval first to: anInterval last! !

!Editor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:13'!
selectInvisiblyFrom: start to: stop
	"Select the designated characters, inclusive.  Make no visual changes."

	self markIndex: start; pointIndex: stop + 1! !

!Editor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:14'!
selectInvisiblyMark: mark point: point
	"Select the designated characters, inclusive.  Make no visual changes."

	self markIndex: mark; pointIndex: point + 1! !

!Editor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:13'!
selectMark: mark point: point
	"Deselect, then select the specified characters inclusive.
	 Be sure the selection is in view."

	(mark =  self markIndex and: [point + 1 = self pointIndex]) ifFalse:
		[self deselect.
		self selectInvisiblyMark: mark point: point]! !

!Editor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:36'!
selectWord
	"Select delimited text or word--the result of double-clicking."

	| openDelimiter closeDelimiter direction match level leftDelimiters rightDelimiters
	string here hereChar start stop |
	string _ self string.
	here _ self pointIndex.
	(here between: 2 and: string size)
		ifFalse: ["if at beginning or end, select entire string"
			^self selectFrom: 1 to: string size].
	leftDelimiters _ '([{<''"
'.
	rightDelimiters _ ')]}>''"
'.
	openDelimiter _ string at: here - 1.
	match _ leftDelimiters indexOf: openDelimiter.
	match > 0
		ifTrue: 
			["delimiter is on left -- match to the right"
			start _ here.
			direction _ 1.
			here _ here - 1.
			closeDelimiter _ rightDelimiters at: match]
		ifFalse: 
			[openDelimiter _ string at: here.
			match _ rightDelimiters indexOf: openDelimiter.
			match > 0
				ifTrue: 
					["delimiter is on right -- match to the left"
					stop _ here - 1.
					direction _ -1.
					closeDelimiter _ leftDelimiters at: match]
				ifFalse: ["no delimiters -- select a token"
					direction _ -1]].
	level _ 1.
	[level > 0 and: [direction > 0
			ifTrue: [here < string size]
			ifFalse: [here > 1]]]
		whileTrue: 
			[hereChar _ string at: (here _ here + direction).
			match = 0
				ifTrue: ["token scan goes left, then right"
					hereChar tokenish
						ifTrue: [here = 1
								ifTrue: 
									[start _ 1.
									"go right if hit string start"
									direction _ 1]]
						ifFalse: [direction < 0
								ifTrue: 
									[start _ here + 1.
									"go right if hit non-token"
									direction _ 1]
								ifFalse: [level _ 0]]]
				ifFalse: ["bracket match just counts nesting level"
					hereChar = closeDelimiter
						ifTrue: [level _ level - 1"leaving nest"]
						ifFalse: [hereChar = openDelimiter 
									ifTrue: [level _ level + 1"entering deeper nest"]]]].

	level > 0 ifTrue: ["in case ran off string end"	here _ here + direction].
	direction > 0
		ifTrue: [self selectFrom: start to: here - 1]
		ifFalse: [self selectFrom: here + 1 to: stop]! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/5/2008 10:08'!
cursorDown: characterStream 

	"Private - Move cursor from position in current line to same position in
	next line. If next line too short, put at end. If shift key down,
	select."
	self closeTypeIn: characterStream.
	self 
		moveCursor:[:position | self
				sameColumn: position
				newLine:[:line | line + 1]
				forward: true]
		forward: true
		specialBlock:[:dummy | dummy].
	^true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 23:15'!
cursorLeft: characterStream 
	"Private - Move cursor left one character if nothing selected, otherwise 
	move cursor to beginning of selection. If the shift key is down, start 
	selecting or extending current selection. Don't allow cursor past 
	beginning of text"

	self closeTypeIn: characterStream.
	self
		moveCursor:[:position | position - 1 max: 1]
		forward: false
		specialBlock:[:position | self previousWord: position].
	^ true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/5/2008 10:07'!
cursorPageDown: characterStream 

	self closeTypeIn: characterStream.
	self 
		moveCursor: [:position |
			self
				sameColumn: position
				newLine: [:lineNo | lineNo + self pageHeight]
				forward: true]
		forward: true
		specialBlock:[:dummy | dummy].
	^true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/5/2008 10:07'!
cursorPageUp: characterStream 

	self closeTypeIn: characterStream.
	self 
		moveCursor: [:position |
			self
				sameColumn: position
				newLine: [:lineNo | lineNo - self pageHeight]
				forward: false]
		forward: false
		specialBlock:[:dummy | dummy].
	^true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 23:32'!
cursorRight: characterStream 
	"Private - Move cursor right one character if nothing selected, 
	otherwise move cursor to end of selection. If the shift key is down, 
	start selecting characters or extending already selected characters. 
	Don't allow cursor past end of text"

	self closeTypeIn: characterStream.
	self
		moveCursor: [:position | position + 1]
		forward: true
		specialBlock:[:position | self nextWord: position].
	^ true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/5/2008 10:08'!
cursorUp: characterStream 

"Private - Move cursor from position in current line to same position in
prior line. If prior line too short, put at end"

	self closeTypeIn: characterStream.
	self
		moveCursor: [:position | self
				sameColumn: position
				newLine:[:line | line - 1]
				forward: false]
		forward: false
		specialBlock:[:dummy | dummy].
	^true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 23:35'!
selectWord: characterStream
	sensor keyboard.
	self closeTypeIn: characterStream.
	self selectWord.
	^ true! !

!Editor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 23:06'!
closeTypeIn: characterStream
	"Call instead of closeTypeIn when you want typeahead to be inserted before the
	 control character is executed, e.g., from Ctrl-V."

	self insertTypeAhead: characterStream.
	self closeTypeIn! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 23:27'!
backWord: characterStream 
	"If the selection is not a caret, delete it and leave it in the backspace buffer.
	 Else if there is typeahead, delete it.
	 Else, delete the word before the caret."

	| startIndex |
	sensor keyboard.
	characterStream isEmpty
		ifTrue:
			[self hasCaret
				ifTrue: "a caret, delete at least one character"
					[startIndex _ 1 max: self markIndex - 1.
					[startIndex > 1 and:
						[(self string at: startIndex - 1) tokenish]]
						whileTrue:
							[startIndex _ startIndex - 1]]
				ifFalse: "a non-caret, just delete it"
					[startIndex _ self markIndex].
			self backTo: startIndex]
		ifFalse:
			[characterStream reset].
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 23:21'!
backspace: characterStream 
	"Backspace over the last character."

	| startIndex |
	sensor leftShiftDown ifTrue: [^ self backWord: characterStream].
	characterStream isEmpty
		ifTrue:
			[startIndex _ self markIndex +
				(self hasCaret ifTrue: [0] ifFalse: [1]).
			[sensor keyboardPressed and:
			 [sensor keyboardPeek asciiValue = 8]] whileTrue: [
				"process multiple backspaces"
				sensor keyboard.
				startIndex _ 1 max: startIndex - 1.
			].
			self backTo: startIndex]
		ifFalse:
			[sensor keyboard.
			characterStream skip: -1].
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/5/2008 10:22'!
cursorTopHome: characterStream 
	"Put cursor at beginning of text -- invoked from cmd-H shortcut, useful for keyboards that have no home key."
	
	sensor keyboard.
	self selectAt: 1.
	^ true! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 15:31'!
normalCharacter: characterStream 
	"A nonspecial character is to be added to the stream of characters."

	characterStream nextPut: sensor keyboard.
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 23:48'!
selectAll

	self selectFrom: 1 to: self string size! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 23:40'!
selectAll: characterStream 
	"select everything, invoked by cmd-a.  1/17/96 sw"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self selectFrom: 1 to: self string size.
	^ true! !

!Editor methodsFor: 'private' stamp: 'jmv 11/5/2008 10:04'!
lines
	"Compute lines based on logical line breaks, not optical (which may change due to line wrapping of the editor).
	Subclasses using kinds of Paragraphs can instead use the service provided by it.
	"
	| lines string index lineIndex stringSize |
	string _ self string.
	"Empty strings have no lines at all. Think of something."
	string isEmpty ifTrue:[^{#(1 0 0)}].
	stringSize _ string size.
	lines _ OrderedCollection new: (string size // 15).
	index _ 0.
	lineIndex _ 0.
	string linesDo:[:line |
		lines addLast: (Array
			with: (index _ index + 1)
			with: (lineIndex _ lineIndex + 1)
			with: (index _ index + line size min: stringSize))].
	"Special workaround for last line empty."
	string last == Character cr
	"lines last last < stringSize" ifTrue:[lines addLast:{stringSize +1. lineIndex+1. stringSize}].
	^lines! !

!Editor methodsFor: 'private' stamp: 'jmv 11/4/2008 23:03'!
moveCursor: directionBlock forward: forward specialBlock: specialBlock
	"Private - Move cursor.
	directionBlock is a one argument Block that computes the new Position from a given one.
	specialBlock is a one argumentBlock that computes the new position from a given one under the alternate semantics.
	Note that directionBlock always is evaluated first."
	| shift indices newPosition |
	shift _ sensor leftShiftDown.
	indices _ self setIndices: shift forward: forward.
	newPosition _ directionBlock value: (indices at: #moving).
	(sensor commandKeyPressed or:[sensor controlKeyPressed])
		ifTrue: [newPosition _ specialBlock value: newPosition].
	sensor keyboard.
	shift
		ifTrue: [self selectMark: (indices at: #fixed) point: newPosition - 1]
		ifFalse: [self selectAt: newPosition]! !

!Editor methodsFor: 'private' stamp: 'jmv 11/4/2008 23:39'!
nextWord: position
	| string index |
	string _ self string.
	index _ position.
	[(index between: 1 and: string size) and: [(string at: index) isAlphaNumeric]]
		whileTrue: [index _ index + 1].
	[(index between: 1 and: string size) and: [(string at: index) isAlphaNumeric not]]
		whileTrue: [index _ index + 1].
	^ index! !

!Editor methodsFor: 'private' stamp: 'jmv 11/4/2008 23:02'!
previousWord: position
	| string index |
	string _ self string.
	index _ position.
	[(index between: 1 and: string size) and: [(string at: index) isAlphaNumeric not]]
		whileTrue: [index _ index - 1].
	[(index between: 1 and: string size) and: [(string at: index) isAlphaNumeric]]
		whileTrue: [index _ index - 1].
	^ index + 1! !

!Editor methodsFor: 'private' stamp: 'jmv 11/4/2008 13:41'!
sensor: aSensor
	"Set the receiver's sensor to aSensor."

	sensor _ aSensor! !

!Editor methodsFor: 'private' stamp: 'jmv 11/4/2008 23:15'!
setIndices: shiftPressed forward: forward
	"Little helper method that sets the moving and fixed indices according to some flags."
	| indices |
	indices _ Dictionary new.
	(shiftPressed and:[Preferences selectionsMayShrink])
		ifTrue: [
			indices at: #moving put: self pointIndex.
			indices at: #fixed put: self markIndex
		] ifFalse: [
			forward
				ifTrue:[
					indices at: #moving put: self stopIndex.
					indices at: #fixed put: self startIndex.
				] ifFalse: [
					indices at: #moving put: self startIndex.
					indices at: #fixed put: self stopIndex.
				]
		].
	^indices! !


!Editor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 11/4/2008 15:25'!
specialShiftCmdKeys

"Private - return array of key codes that represent single keys acting
as if shift-command were also being pressed"

^#(
	1	"home"
	3	"enter"
	4	"end"
	8	"backspace"
	11	"page up"
	12	"page down"
	27	"escape"
	28	"left arrow"
	29	"right arrow"
	30	"up arrow"
	31	"down arrow"
	127	"delete"
	)! !

!Editor class methodsFor: 'class initialization' stamp: 'jmv 11/5/2008 13:07'!
initialize
	"
	Editor initialize
	"
	self allSubclassesDo: [ :c | c initialize ]! !


!LabelLW methodsFor: 'drawing' stamp: 'jmv 11/4/2008 12:56'!
drawOn: aCanvas

	aCanvas drawString: contents asString at: bounds topLeft font: font color: self fontColor! !


!OldMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:49'!
initialize
	"initialize the state of the receiver"
	owner _ nil.
	submorphs _ EmptyArray.
	bounds _ self defaultBounds.
	
	color _ self defaultColor! !


!OldMinimalStringMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:56'!
defaultColor
	"answer the default color/fill style for the receiver"
	^ Color black! !

!OldMinimalStringMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:56'!
initWithContents: aString font: aFont emphasis: emphasisCode 
	self initialize.
	
	font _ aFont.
	emphasis _ emphasisCode.
	hasFocus _ false.
	self contents: aString! !

!OldMinimalStringMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:56'!
initialize
	super initialize.
	font _ nil.
	emphasis _ 0.
	hasFocus _ false! !

!OldMinimalStringMorph methodsFor: 'drawing' stamp: 'jmv 11/4/2008 12:56'!
drawOn: aCanvas

	aCanvas drawString: contents in: bounds font: self fontToUse color: color! !

!OldMinimalStringMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:56'!
contents: newContents 
	contents := 	newContents isText
		ifTrue: 
			[emphasis := newContents emphasisAt: 1.
			 newContents string]
		ifFalse: 
			[contents = newContents ifTrue: [^self].	"no substantive change"
			newContents].
	self fitContents! !

!OldMinimalStringMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:56'!
fitContents

	| newBounds boundsChanged |
	newBounds _ self measureContents.
	boundsChanged _ bounds extent ~= newBounds.
	self extent: newBounds.		"default short-circuits if bounds not changed"
	boundsChanged ifFalse: [self changed]! !

!OldMinimalStringMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:56'!
fontToUse
	| fontToUse |
	fontToUse := font isNil ifTrue: [TextStyle defaultFont] ifFalse: [font].
	(emphasis isNil or: [emphasis = 0]) 
		ifTrue: [^fontToUse]
		ifFalse: [^fontToUse emphasized: emphasis]! !

!OldMinimalStringMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:56'!
measureContents
	| f |
	f _ self fontToUse.
	^(((f widthOfString: contents) max: self minimumWidth)  @ f height).! !

!OldMinimalStringMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:56'!
minimumWidth
	"Answer the minimum width that the receiver can have.  A nonzero value here keeps the receiver from degenerating into something that cannot ever be seen or touched again!!  Obeyed by fitContents."

	^ 3! !


!OldMinimalStringMorph class methodsFor: 'instance creation' stamp: 'jmv 11/4/2008 12:56'!
contents: aString
	" 'StringMorph contents: str' is faster than 'StringMorph new contents: str' 
	(OldStringEditorMorph contents: 'Some string') openInWorld
	"
	^ self contents: aString font: nil! !

!OldMinimalStringMorph class methodsFor: 'instance creation' stamp: 'jmv 11/4/2008 12:56'!
contents: aString font: aFont
	^ self basicNew initWithContents: aString font: aFont emphasis: 0! !

!OldMinimalStringMorph class methodsFor: 'instance creation' stamp: 'jmv 11/4/2008 12:56'!
contents: aString font: aFont emphasis: emphasisCode
	^ self basicNew initWithContents: aString font: aFont emphasis: emphasisCode! !


!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/5/2008 13:17'!
baseFont

	font isNil ifTrue: [ font _ TextStyle defaultFont ].
	^font! !

!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:53'!
contents: newContents 
	contents := 	newContents isText
		ifTrue: 
			[emphasis := newContents emphasisAt: 1.
			 newContents string]
		ifFalse: 
			[contents = newContents ifTrue: [^self].	"no substantive change"
			newContents].
	self fitContents! !

!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 15:47'!
editor
	"Return my current editor, or install a new one."
	editor ifNotNil: [^ editor].
	^ self installEditorToReplace: nil! !

!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:53'!
fitContents

	| newBounds boundsChanged |
	newBounds _ self measureContents.
	boundsChanged _ bounds extent ~= newBounds.
	self extent: newBounds.		"default short-circuits if bounds not changed"
	boundsChanged ifFalse: [self changed]! !

!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/5/2008 13:18'!
fontToUse

	^ (emphasis isNil or: [emphasis = 0]) 
		ifTrue: [ self baseFont ]
		ifFalse: [ self baseFont emphasized: emphasis ]! !

!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/5/2008 13:19'!
measureContents
	| f |

	f _ self fontToUse.
	^((f widthOfString: contents) max: self minimumWidth)  @ f height.! !

!OldOneLineEditorMorph methodsFor: 'accessing' stamp: 'jmv 11/4/2008 12:55'!
minimumWidth
	"Answer the minimum width that the receiver can have.  A nonzero value here keeps the receiver from degenerating into something that cannot ever be seen or touched again!!  Obeyed by fitContents."

	^ 3! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/5/2008 12:25'!
caretWidth
	^ 2! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/5/2008 13:50'!
characterIndexAtPoint: aPoint

	| line block f |
	f _ self fontToUse.
	
	line _ OldTextLine 
		start: 1
		stop: contents size
		internalSpaces: 0
		paddingWidth: 0.
	line
		rectangle: bounds;
		lineHeight: f height baseline: f ascent.
		
	block _ (CharacterBlockScanner new text: contents asText textStyle: (TextStyle fontArray: {f}))
		characterBlockAtPoint: aPoint index: nil
		in: line.

	^ block stringIndex! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/5/2008 13:59'!
drawCaretOn: aCanvas
	|  w caretColor leftX top bottom rightX |

	top _ bounds top.
	bottom _ top + self baseFont height.
	leftX _ (self fontToUse widthOfString: contents from: 1 to: editor startIndex-1) + bounds left.
	rightX := leftX + 1.
	w := self caretWidth.
	caretColor := self insertionPointColor.
	
	1 to: w do: [:i |
		"Draw caret triangles at top and bottom"
		aCanvas fillRectangle: ((leftX - w + i - 1) @ (top + i - 1) 
				extent: ((w - i) * 2 + 3) @ 1)
			color: caretColor.
		aCanvas fillRectangle: ((leftX - w + i - 1) @ (bottom - i) 
				extent: ((w - i) * 2 + 3) @ 1)
			color: caretColor].
	aCanvas fillRectangle: (leftX @ top corner: rightX @ bottom)
		color: caretColor! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/5/2008 12:40'!
drawOn: aCanvas
	self hasSelection ifTrue: [
		self drawSelectionOn: aCanvas ].
	self hasCaret ifTrue: [
		self drawCaretOn: aCanvas].
	aCanvas drawString: contents in: bounds font: self fontToUse color: color! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/5/2008 14:00'!
drawSelectionOn: aCanvas
	| rightX leftX top bottom |

	top _ bounds top.
	bottom _ top + self baseFont height.
	leftX _ (self fontToUse widthOfString: contents from: 1 to: editor startIndex-1) + bounds left.
	rightX _ (self fontToUse widthOfString: contents from: 1 to: editor stopIndex-1) + bounds left.

	aCanvas fillRectangle: (leftX @ top corner: rightX @ bottom)
		color: self selectionColor! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/5/2008 12:40'!
insertionPointColor

	^ Display depth <= 2
		ifTrue: [Color black]
		ifFalse: [Preferences insertionPointColor]! !

!OldOneLineEditorMorph methodsFor: 'drawing' stamp: 'jmv 11/4/2008 23:29'!
selectionColor
	| c |
	Display depth = 1 ifTrue: [^ Color veryLightGray].
	Display depth = 2 ifTrue: [^ Color gray: 0.87].
	c := Preferences textHighlightColor.
	self hasFocus ifFalse: [c := c alphaMixed: 0.2 with: Color veryVeryLightGray].
	^ c! !

!OldOneLineEditorMorph methodsFor: 'editing' stamp: 'jmv 11/4/2008 15:49'!
handleInteraction: interactionBlock fromEvent: evt 
	"Perform the changes in interactionBlock, noting any change in selection
	and possibly a change in the size of the paragraph (ar 9/22/2001 - added for TextPrintIts)"

	"Also couple the editor to Morphic keyboard events"

	| oldEditor oldContents |
	self editor sensor: (OldKeyboardBuffer new startingEvent: evt).
	oldEditor := editor.
	oldContents := contents.
	interactionBlock value.
	oldContents == contents 
		ifTrue: 
			["this will not work if the paragraph changed"

			editor := oldEditor	"since it may have been changed while in block"].
	self changed! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/4/2008 15:34'!
handlesKeyboard: evt
	^true! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/4/2008 15:36'!
handlesMouseDown: evt
	^ self innerBounds containsPoint: evt cursorPoint! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/5/2008 12:18'!
hasFocus

	^ hasFocus! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/5/2008 22:11'!
keyStroke: evt
	"Handle a keystroke event."

	"CR - check for special action
	Note: Code below assumes that this was some
	input field reacting on CR. Break the keyboard
	focus so that the receiver can be safely deleted.
	jmv - Currently not implemented"
	"
	evt keyValue = 13 ifTrue: [
		action _ self crAction.
		action ifNotNil: [
			evt hand newKeyboardFocus: nil.
			^action value ] ].
	"
	self handleInteraction: [editor readKeyboard] fromEvent: evt.
	self updateFromContents.
	super keyStroke: evt  "sends to keyStroke event handler, if any"! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/5/2008 12:18'!
keyboardFocusChange: aBoolean 

	hasFocus _ aBoolean.
	aBoolean ifTrue: [
		"A hand is wanting to send us characters... Ensure editor."
		self editor ].
	self changed! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/5/2008 13:44'!
mouseDown: evt
	"Make this TextMorph be the keyboard input focus, if it isn't already,
		and repond to the text selection gesture."

	evt hand newKeyboardFocus: self.
	self handleInteraction: [
		editor mouseDown: evt index: (self characterIndexAtPoint: evt cursorPoint) ] fromEvent: evt! !

!OldOneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 11/5/2008 14:08'!
mouseMove: evt
	evt redButtonPressed ifFalse: [^ self].
	self handleInteraction: [
		editor mouseMove: evt index: (self characterIndexAtPoint: evt cursorPoint) ] fromEvent: evt! !

!OldOneLineEditorMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:50'!
defaultColor
	"answer the default color/fill style for the receiver"
	^ Color black! !

!OldOneLineEditorMorph methodsFor: 'initialization' stamp: 'jmv 11/5/2008 12:15'!
initWithContents: aString font: aFont emphasis: emphasisCode 
	self initialize.
	
	font _ aFont.
	emphasis _ emphasisCode.
	self contents: aString! !

!OldOneLineEditorMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:50'!
initialize
	super initialize.
	font _ nil.
	emphasis _ 0.
	hasFocus _ false! !

!OldOneLineEditorMorph methodsFor: 'testing' stamp: 'jmv 11/5/2008 13:13'!
hasCaret

	^self hasFocus and: [editor hasCaret]! !

!OldOneLineEditorMorph methodsFor: 'testing' stamp: 'jmv 11/5/2008 12:41'!
hasSelection

	^editor notNil and: [editor hasSelection]! !

!OldOneLineEditorMorph methodsFor: 'unaccepted edits' stamp: 'jmv 11/5/2008 22:08'!
hasUnacceptedEdits: aBoolean
	"Set the hasUnacceptedEdits flag to the given value. "
	
	self flag: #jmv.
	"Not used in this morph, as it doesn't do accept / cancel"
	self changed! !

!OldOneLineEditorMorph methodsFor: 'private' stamp: 'jmv 11/5/2008 13:00'!
installEditorToReplace: priorEditor
	"Install an editor for my contents.  This constitutes 'hasFocus'.
	If priorEditor is not nil, then initialize the new editor from its state.
	We may want to rework this so it actually uses the prior editor."

	| stateArray |
	priorEditor ifNotNil: [stateArray := priorEditor stateArray].
	editor := SimpleEditor new morph: self.
	editor changeString: contents.
	priorEditor ifNotNil: [editor stateArrayPut: stateArray].
	self changed.
	^editor! !

!OldOneLineEditorMorph methodsFor: 'private' stamp: 'jmv 11/4/2008 16:08'!
updateFromContents


	"O contents deberia ser un StringHolder????"
	contents _ editor string.
	self changed! !


!OldOneLineEditorMorph class methodsFor: 'instance creation' stamp: 'jmv 11/4/2008 12:48'!
contents: aString
	" 'StringMorph contents: str' is faster than 'StringMorph new contents: str' 
	(OldStringEditorMorph contents: 'Some string') openInWorld
	"
	^ self contents: aString font: nil! !

!OldOneLineEditorMorph class methodsFor: 'instance creation' stamp: 'jmv 11/4/2008 12:47'!
contents: aString font: aFont
	^ self basicNew initWithContents: aString font: aFont emphasis: 0! !

!OldOneLineEditorMorph class methodsFor: 'instance creation' stamp: 'jmv 11/4/2008 12:47'!
contents: aString font: aFont emphasis: emphasisCode
	^ self basicNew initWithContents: aString font: aFont emphasis: emphasisCode! !


!OldStringMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:50'!
initWithContents: aString font: aFont emphasis: emphasisCode 
	self initialize.
	
	font _ aFont.
	emphasis _ emphasisCode.
	hasFocus _ false.
	self contents: aString! !

!OldStringMorph methodsFor: 'initialization' stamp: 'jmv 11/4/2008 12:49'!
initialize
	super initialize.
	font _ nil.
	emphasis _ 0.
	hasFocus _ false! !


!OldTextMorph methodsFor: 'event handling' stamp: 'jmv 11/5/2008 12:10'!
keyboardFocusChange: aBoolean 
	"rr 3/21/2004 22:55 : removed the #ifFalse: branch, 
	which was responsible of the deselection of text when the 
	paragraph lost focus. This way selection works in a more standard 
	way, and this permits the menu keyboard control to be really effective"
	
	paragraph isNil ifFalse: [paragraph focused: aBoolean].
	aBoolean ifTrue: [
		"A hand is wanting to send us characters..."
		self hasFocus ifFalse: [self editor	"Forces install"]].
	self changed.
! !


!SimpleEditor methodsFor: 'accessing' stamp: 'jmv 11/5/2008 12:22'!
replace: oldInterval with: newString and: selectingBlock 
	"Replace the text in oldInterval with newString and execute selectingBlock to establish the new selection."

	self selectionInterval = oldInterval ifFalse: [self selectInterval: oldInterval].
	self zapSelectionWith: newString.
	selectingBlock value! !

!SimpleEditor methodsFor: 'accessing' stamp: 'jmv 11/5/2008 11:17'!
replaceSelectionWith: aString
	"Deselect, and replace the selection text by aText."

	self zapSelectionWith: aString! !

!SimpleEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 16:09'!
string
	^string! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 23:24'!
hasCaret
	^markIndex = pointIndex! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:57'!
markIndex
	^markIndex! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/5/2008 11:41'!
markIndex: anIndex
	markIndex _ anIndex min: string size + 1 max: 1! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:57'!
pointIndex
	^ pointIndex! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/5/2008 11:41'!
pointIndex: anIndex
	pointIndex _ anIndex min: string size + 1 max: 1! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/5/2008 10:24'!
selection

	^string copyFrom: self startIndex to: self stopIndex - 1 ! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:55'!
startIndex
	^ pointIndex min: markIndex! !

!SimpleEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:55'!
stopIndex
	^ pointIndex max: markIndex! !

!SimpleEditor methodsFor: 'editing keys' stamp: 'jmv 11/5/2008 10:26'!
makeCapitalized: characterStream 
	"Force the current selection to uppercase.  Triggered by Cmd-X."
	| prev |
	sensor keyboard.		"flush the triggering cmd-key character"
	prev _ $-.  "not a letter"
	self replaceSelectionWith:
			(self selection collect:
				[:c | prev _ prev isLetter ifTrue: [c asLowercase] ifFalse: [c asUppercase]]).
	^ true! !

!SimpleEditor methodsFor: 'editing keys' stamp: 'jmv 11/5/2008 10:23'!
makeLowercase: characterStream 
	"Force the current selection to lowercase.  Triggered by Cmd-X."

	sensor keyboard.		"flush the triggering cmd-key character"
	self replaceSelectionWith:  (self selection asLowercase).
	^ true! !

!SimpleEditor methodsFor: 'editing keys' stamp: 'jmv 11/5/2008 10:25'!
makeUppercase: characterStream 
	"Force the current selection to uppercase.  Triggered by Cmd-Y."

	sensor keyboard.		"flush the triggering cmd-key character"
	self replaceSelectionWith:  (self selection asUppercase).
	^ true! !

!SimpleEditor methodsFor: 'events' stamp: 'jmv 11/5/2008 13:54'!
mouseDown: evt index: index
	
	self closeTypeIn.
	evt shiftPressed
		ifFalse: [
			self pointIndex: index.
			self markIndex: index]
		ifTrue: [
			self mouseMove: evt ]! !

!SimpleEditor methodsFor: 'events' stamp: 'jmv 11/5/2008 14:10'!
mouseMove: evt index: index
	"Change the selection in response to moue-down drag"

	self pointIndex: index! !

!SimpleEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:08'!
changeString: aString
	"Install aString as the one to be edited by the receiver."

	string _ aString.
	self resetState! !

!SimpleEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:42'!
initialize
	"Initialize the state of the receiver. Subclasses should include 'super 
	initialize' when redefining this message to insure proper initialization."

	super initialize.
	self changeString: ''! !

!SimpleEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 14:12'!
resetState 
	"Establish the initial conditions for editing the paragraph: place caret 
	before first character, set the emphasis to that of the first character,
	and save the paragraph for purposes of canceling."

	pointIndex _ 1.	"Like pointBlock in EnhancedTextEditor"
	markIndex _ 1.	"Like markIndex in EnhancedTextEditor"
	beginTypeInIndex _ nil.	"Like beginTypeInBlock in EnhancedTextEditor"
	selectionShowing _ false! !

!SimpleEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:23'!
stateArray
	^ {	self selectionInterval.
		self startOfTyping}! !

!SimpleEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:23'!
stateArrayPut: stateArray
	| sel |
	sel _ stateArray at: 1.
	self selectFrom: sel first to: sel last.
	beginTypeInIndex _ stateArray at: 2! !

!SimpleEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 23:42'!
copySelection
	"Copy the current selection and store it in the paste buffer, unless a caret.  Undoer & Redoer: undoCutCopy"

	self lineSelectAndEmptyCheck: [^ self].
	self clipboardTextPut: self selection! !

!SimpleEditor methodsFor: 'menu messages' stamp: 'jmv 11/5/2008 10:21'!
cut
	"Cut out the current selection and redisplay the paragraph if necessary.  Undoer & Redoer: undoCutCopy:"

	self lineSelectAndEmptyCheck: [^ self].
	self replaceSelectionWith: ''. 
	self clipboardTextPut: self selection! !

!SimpleEditor methodsFor: 'mvc compatibility' stamp: 'jmv 11/4/2008 14:26'!
zapSelectionWith: aString

	| start stop |
	self deselect.
	start _ self startIndex.
	stop _ self stopIndex.
	(start = stop and: [aString size = 0]) ifFalse: [
		string _ string copyReplaceFrom: start to: stop-1 with: aString.
		self markIndex: start; pointIndex: start + aString size].

	self userHasEdited  " -- note text now dirty"! !

!SimpleEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:12'!
selectFrom: start to: stop
	"Select the specified characters inclusive."
	self selectInvisiblyFrom: start to: stop.
	self closeTypeIn! !

!SimpleEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 23:49'!
selectLine
	"Make the receiver's selection, if it currently consists of an insertion point only, encompass the current line."
	self hasSelection ifTrue:[^self].
	self selectAll! !

!SimpleEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 23:19'!
cursorEnd: characterStream 

	"Private - Move cursor end of current line."
	self closeTypeIn: characterStream.
	self
		moveCursor:
			[:position | 
				string
					indexOf: Character cr
					startingAt: position
					ifAbsent:[string size + 1]]
		forward: true
		specialBlock:[:dummy | string size + 1].
	^true! !

!SimpleEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 23:18'!
cursorHome: characterStream 

	"Private - Move cursor from position in current line to beginning of
	current line. If control key is pressed put cursor at beginning of text"

	self
		moveCursor: [ :position |
				(string
					lastIndexOf: Character cr
					startingAt: position - 1
					ifAbsent:[0]) + 1]
		forward: false
		specialBlock: [:dummy | 1].
	^true! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 23:30'!
backTo: startIndex
	"During typing, backspace to startIndex.  Deleted characters fall into three
	 clusters, from left to right in the text: (1) preexisting characters that were
	 backed over; (2) newly typed characters that were backed over (excluding
	 typeahead, which never even appears); (3) preexisting characters that
	 were highlighted before typing began.  If typing has not yet been opened,
	 open it and watch for the first and third cluster.  If typing has been opened,
	 watch for the first and second cluster.  
	 Delete all the clusters.  The code is shorter than the comment."

	| saveLimit |
	saveLimit _ beginTypeInIndex
		ifNil: [self openTypeIn. self stopIndex]
		ifNotNil: [self startOfTyping].
	self markIndex: startIndex.
	startIndex < saveLimit ifTrue: [
		beginTypeInIndex _ self startIndex.].
	self zapSelectionWith: ''.
	self unselect! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 23:08'!
closeTypeIn
	"See comment in openTypeIn.  It is important to call closeTypeIn before executing
	 any non-typing key, making a new selection, etc.  It is called automatically for
	 menu commands.
	 Typing commands can call 'closeTypeIn: aCharacterStream' instead of this to
	 save typeahead."

	beginTypeInIndex _ nil! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/5/2008 22:19'!
dispatchOnCharacter: char with: typeAheadStream
	"Carry out the action associated with this character, if any.
	Type-ahead is passed so some routines can flush or use it."

	| honorCommandKeys |
	"We don't support multiple lines. Therefore, we don't process cr as a #normalCharacter:"
	((char == Character cr) "and: [morph acceptOnCR]")
		ifTrue:
			[sensor keyboard.  "Gobble cr -- probably unnecessary."
			self closeTypeIn.
			^ true].

	char asciiValue = 13 ifTrue: [
		^ sensor controlKeyPressed
			ifTrue: [self normalCharacter: typeAheadStream]
			ifFalse: [self crWithIndent: typeAheadStream]].

	((honorCommandKeys _ Preferences cmdKeysInText) and: [char = Character enter])
		ifTrue: [^ self dispatchOnEnterWith: typeAheadStream].

	"Special keys overwrite crtl+key combinations - at least on Windows. To resolve this
	conflict, assume that keys other than cursor keys aren't used together with Crtl." 
	((self class specialShiftCmdKeys includes: char asciiValue) and: [char asciiValue < 27])
		ifTrue: [^ sensor controlKeyPressed
			ifTrue: [self perform: (ShiftCmdActions at: char asciiValue + 1) with: typeAheadStream]
			ifFalse: [self perform: (CmdActions at: char asciiValue + 1) with: typeAheadStream]].

	"backspace, and escape keys (ascii 8 and 27) are command keys"
	((honorCommandKeys and: [sensor commandKeyPressed]) or: [self class specialShiftCmdKeys includes: char asciiValue]) ifTrue:
		[^ sensor leftShiftDown
			ifTrue:
				[self perform: (ShiftCmdActions at: char asciiValue + 1) with: typeAheadStream]
			ifFalse:
				[self perform: (CmdActions at: char asciiValue + 1) with: typeAheadStream]].

	"the control key can be used to invoke shift-cmd shortcuts"
	(honorCommandKeys and: [sensor controlKeyPressed])
		ifTrue:
			[^ self perform: (ShiftCmdActions at: char asciiValue + 1) with: typeAheadStream].

	^ self perform: #normalCharacter: with: typeAheadStream! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 14:04'!
doneTyping
	beginTypeInIndex _ nil! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 23:09'!
insertTypeAhead: typeAhead
	typeAhead position = 0 ifFalse:
		[self zapSelectionWith: typeAhead contents.
		typeAhead reset.
		self unselect]! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 14:06'!
openTypeIn
	"Set up beginTypeInIndex to keep track of the leftmost backspace."

	beginTypeInIndex isNil
		ifTrue: [ beginTypeInIndex _ self startIndex ]! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 14:35'!
readKeyboard
	"Key struck on the keyboard. Find out which one and, if special, carry 
	out the associated special action. Otherwise, add the character to the 
	stream of characters.  Undoer & Redoer: see closeTypeIn."

	| typeAhead char |
	typeAhead _ WriteStream on: (String new: 128).
	[sensor keyboardPressed] whileTrue: [
		self deselect.
		[sensor keyboardPressed] whileTrue: 
			[char _ sensor keyboardPeek.
			(self dispatchOnCharacter: char with: typeAhead) ifTrue: [
				self doneTyping.
				^self].
			self openTypeIn].
		self zapSelectionWith: typeAhead contents.
		typeAhead reset.
		self unselect]! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 13:28'!
startOfTyping
	^ beginTypeInIndex! !

!SimpleEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 23:39'!
forwardDelete: characterStream
	"Delete forward over the next character.
	  Make Undo work on the whole type-in, not just the one char.
	wod 11/3/1998: If there was a selection use #zapSelectionWith: rather than #backspace: which was 'one off' in deleting the selection. Handling of things like undo or typeIn area were not fully considered."
	| startIndex stopIndex |
	startIndex _ self markIndex.
	startIndex > string size ifTrue:
		[sensor keyboard.
		^ false].
	self hasSelection ifTrue:
		["there was a selection"
		sensor keyboard.
		self zapSelectionWith: ''.
		^ false].
	"Null selection - do the delete forward"
	beginTypeInIndex isNil	"no previous typing.  openTypeIn"
		ifTrue: [self openTypeIn].
	stopIndex := startIndex.
	(sensor keyboard asciiValue = 127 and: [sensor leftShiftDown])
		ifTrue: [stopIndex := (self nextWord: stopIndex) - 1].
	self selectFrom: startIndex to: stopIndex.
	self replaceSelectionWith: ''.
	self selectFrom: startIndex to: startIndex-1.
	^false! !


!SimpleEditor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 11/5/2008 13:07'!
initializeCmdKeyShortcuts
	"Initialize the (unshifted) command-key (or alt-key) shortcut table."

	"NOTE: if you don't know what your keyboard generates, use Sensor kbdTest"

	"SimpleTextEditor initialize"

	| cmdMap cmds |
	cmdMap := Array new: 256 withAll: #noop:.	"use temp in case of a crash"
	cmdMap at: 1 + 1 put: #cursorHome:.	"home key"
	cmdMap at: 4 + 1 put: #cursorEnd:.	"end key"
	cmdMap at: 8 + 1 put: #backspace:.	"ctrl-H or delete key"
	cmdMap at: 28 + 1 put: #cursorLeft:.	"left arrow key"
	cmdMap at: 29 + 1 put: #cursorRight:.	"right arrow key"
	cmdMap at: 32 + 1 put: #selectWord:.	"space bar key"
	cmdMap at: 127 + 1 put: #forwardDelete:.	"del key"
	
	cmds := #($a #selectAll: $c #copySelection: $v #paste: $w #backWord: $x #cut: ).
	1 to: cmds size
		by: 2
		do: [:i | cmdMap at: (cmds at: i) asciiValue + 1 put: (cmds at: i + 1)].
	CmdActions := cmdMap! !

!SimpleEditor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 11/5/2008 13:05'!
initializeShiftCmdKeyShortcuts 
	"Initialize the shift-command-key (or control-key) shortcut table."
	"NOTE: if you don't know what your keyboard generates, use Sensor kbdTest"
	"wod 11/3/1998: Fix setting of cmdMap for shifted keys to actually use the 
	capitalized versions of the letters.
	TPR 2/18/99: add the plain ascii values back in for those VMs that don't return the shifted values."

	| cmdMap cmds |

	"shift-command and control shortcuts"
	cmdMap _ Array new: 256 withAll: #noop:.  "use temp in case of a crash"
	cmdMap at: ( 1 + 1) put: #cursorHome:.			"home key"
	cmdMap at: ( 4 + 1) put: #cursorEnd:.			"end key"
	cmdMap at: ( 8 + 1) put: #forwardDelete:.		"ctrl-H or delete key"
	cmdMap at: (28 + 1) put: #cursorLeft:.			"left arrow key"
	cmdMap at: (29 + 1) put: #cursorRight:.			"right arrow key"
	cmdMap at: (32 + 1) put: #selectWord:.			"space bar key"
	cmdMap at: (127 + 1) put: #forwardDelete:.		"del key"

	cmds _ #(
		$x	makeLowercase:
		$y	makeUppercase:
		$z	makeCapitalized:
	).
	1 to: cmds size by: 2 do: [ :i |
		cmdMap at: ((cmds at: i) asciiValue + 1) put: (cmds at: i + 1).		"plain keys"
		cmdMap at: ((cmds at: i) asciiValue - 32 + 1) put: (cmds at: i + 1).		"shifted keys"
		cmdMap at: ((cmds at: i) asciiValue - 96 + 1) put: (cmds at: i + 1).		"ctrl keys"
	].
	ShiftCmdActions _ cmdMap! !

!SimpleEditor class methodsFor: 'class initialization' stamp: 'jmv 11/5/2008 13:07'!
initialize 
	"Initialize the keyboard shortcut maps and the shared buffers
	for copying text across views and managing again and undo." 
 
	"Simp;eEditor initialize"

	self initializeCmdKeyShortcuts.
	self initializeShiftCmdKeyShortcuts! !


!TextEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 11:51'!
replace: xoldInterval with: newText and: selectingBlock 
	"Replace the text in oldInterval with newText and execute selectingBlock to establish the new selection.  Create an undoAndReselect:redoAndReselect: undoer to allow perfect undoing."

	| undoInterval |
	undoInterval _ self selectionInterval.
	undoInterval = xoldInterval ifFalse: [self selectInterval: xoldInterval].
	UndoSelection _ self selection.
	self zapSelectionWith: newText.
	selectingBlock value.
	otherInterval _ self selectionInterval.
	self undoer: #undoAndReselect:redoAndReselect: with: undoInterval with: otherInterval! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 11:51'!
replaceSelectionWith: aText
	"Remember the selection text in UndoSelection.
	 Deselect, and replace the selection text by aText.
	 Remember the resulting selectionInterval in UndoInterval and PriorInterval.
	 Set up undo to use UndoReplace."

	beginTypeInBlock ~~ nil ifTrue: [^self zapSelectionWith: aText]. "called from old code"
	UndoSelection _ self selection.
	self zapSelectionWith: aText.
	self undoer: #undoReplace! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 11:51'!
setSearch: aString
	"Set the FindText and ChangeText to seek aString; except if already seeking aString, leave ChangeText alone so again will repeat last replacement."

	FindText string = aString
		ifFalse: [FindText _ ChangeText _ aString asText]! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 23:01'!
string

	^self text string! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 11:51'!
text
	"Answer the text of the paragraph being edited."

	^paragraph text! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 11/4/2008 11:51'!
transformFrom: owner
	^morph transformFrom: owner! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
hasCaret
	^self markBlock = self pointBlock! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:51'!
markBlock
	^ markBlock! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:51'!
markBlock: aCharacterBlock
	markBlock _ aCharacterBlock! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
markIndex
	^ self markBlock stringIndex! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:59'!
markIndex: anIndex
	self markBlock: (paragraph characterBlockForIndex: anIndex)
! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:50'!
pointBlock
	^ pointBlock! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 13:50'!
pointBlock: aCharacterBlock
	pointBlock _ aCharacterBlock.
! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
pointIndex
	^ self pointBlock stringIndex! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 14:00'!
pointIndex: anIndex
	self pointBlock: (paragraph characterBlockForIndex: anIndex)
! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
selection
	"Answer the text in the paragraph that is currently selected."

	^paragraph text copyFrom: self startIndex to: self stopIndex - 1 ! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
selectionAsStream
	"Answer a ReadStream on the text in the paragraph that is currently 
	selected."

	^ReadStream
		on: (paragraph string copyFrom: self startIndex to: self stopIndex - 1)! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
startBlock
	^ self pointBlock min: self markBlock! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
startIndex
	^ self startBlock stringIndex! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
stopBlock
	^ self pointBlock max: self markBlock! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
stopIndex
	^ self stopBlock stringIndex! !

!TextEditor methodsFor: 'accessing-selection' stamp: 'jmv 11/4/2008 11:51'!
unselect
	self markBlock: self pointBlock copy! !

!TextEditor methodsFor: 'as yet unclassified' stamp: 'jmv 11/4/2008 11:51'!
doesNotUnderstand: aMessage
	Transcript cr; show: self printString, ' >> ', aMessage printString! !

!TextEditor methodsFor: 'as yet unclassified' stamp: 'jmv 11/4/2008 11:51'!
totalTextHeight

	^paragraph lines last bottom! !

!TextEditor methodsFor: 'as yet unclassified' stamp: 'jmv 11/4/2008 11:51'!
visibleHeight

	^morph owner bounds height! !

!TextEditor methodsFor: 'attributes' stamp: 'jmv 11/4/2008 11:51'!
changeEmphasisOrAlignment
	| aList reply  code align menuList startIndex |
	self flag: #arNote. "Move this up once we get rid of MVC"
	startIndex _ self startIndex.
	aList _ #(normal bold italic narrow underlined struckOut leftFlush centered rightFlush justified).	
	align _ paragraph text alignmentAt: startIndex 
		ifAbsent:[paragraph textStyle alignment].
	code _ paragraph text emphasisAt: startIndex.
	menuList _ WriteStream on: Array new.
	menuList nextPut: (code isZero ifTrue:['<on>'] ifFalse:['<off>']), 'normal' translated.
	menuList nextPutAll: (#(bold italic underlined struckOut) collect:[:emph|
		(code anyMask: (TextEmphasis perform: emph) emphasisCode)
			ifTrue:['<on>', emph asString translated]
			ifFalse:['<off>',emph asString translated]]).
	((paragraph text attributesAt: startIndex forStyle: paragraph textStyle)
		anySatisfy:[:attr| attr isKern and:[attr kern < 0]]) 
			ifTrue:[menuList nextPut:'<on>', 'narrow' translated]
			ifFalse:[menuList nextPut:'<off>', 'narrow' translated].
	menuList nextPutAll: (#(leftFlush centered rightFlush justified) collectWithIndex:[:type :i|
		align = (i-1)
			ifTrue:['<on>',type asString translated]
			ifFalse:['<off>',type asString translated]]).
	aList _ #(normal bold italic underlined struckOut narrow leftFlush centered rightFlush justified).
	reply _ (SelectionMenu labelList: menuList contents lines: #(1 6) selections: aList) startUpWithoutKeyboard.
	reply notNil ifTrue:
		[(#(leftFlush centered rightFlush justified) includes: reply)
			ifTrue:
				[self setAlignment: reply.
				paragraph composeAll.
				self recomputeInterval]
			ifFalse:
				[self setEmphasis: reply.
				paragraph composeAll.
				self recomputeSelection.
				self mvcRedisplay]].
	^ true! !

!TextEditor methodsFor: 'attributes' stamp: 'jmv 11/4/2008 11:51'!
changeStyle
	"Let user change styles for the current text pane."
	| aList reply style theStyle menuList |
	self flag: #arNote. "Move this up once we get rid of MVC"
	self flag: #jmv. "what???"
	aList _ StrikeFont actualFamilyNames.
	theStyle _ paragraph textStyle.
	menuList _ aList collect:[:styleName|
		"Hack!! use defaultFont for comparison - we have no name that we could use for compare and the style changes with alignment so they're no longer equal."
		(TextConstants at: styleName) defaultFont == theStyle defaultFont
			ifTrue:['<on>', styleName]
			ifFalse:['<off>',styleName]].
	theStyle = TextStyle default
		ifTrue:[menuList addFirst: '<on>DefaultTextStyle']
		ifFalse:[menuList addFirst: '<off>DefaultTextStyle'].
	aList addFirst: 'DefaultTextStyle'.
	reply _ (SelectionMenu labelList: menuList lines: #(1) selections: aList) startUp.
	reply ifNotNil:
		[(style _ TextStyle named: reply) ifNil: [Beeper beep. ^ true].
		paragraph textStyle: style copy.
		paragraph composeAll.
		self recomputeSelection].
	^ true! !

!TextEditor methodsFor: 'attributes' stamp: 'jmv 11/4/2008 11:51'!
changeTextFont
	"Present a menu of available fonts, and if one is chosen, apply it to the current selection."
	| curFont newFont attr startIndex |
	startIndex _ self startIndex.
	curFont _ (paragraph text fontAt: startIndex withStyle: paragraph textStyle).
	newFont _ StrikeFont fromUser: curFont.
	newFont ifNil:[^self].
	attr _ TextFontReference toFont: newFont.
	paragraph text addAttribute: attr from: startIndex to: (self stopIndex-1 min: paragraph text size).
	paragraph composeAll.
	self recomputeInterval.
	morph changed.! !

!TextEditor methodsFor: 'attributes' stamp: 'jmv 11/4/2008 11:51'!
offerFontMenu
	"Present a menu of available fonts, and if one is chosen, apply it to the current selection.  
	Use only names of Fonts of this paragraph  "
	| aList reply curFont menuList |
true ifTrue:[^self changeTextFont].
	self flag: #arNote. "Move this up once we get rid of MVC"
	self flag: #jmv. "I DON'T understand what Andreas says!!"
	curFont _ (paragraph text fontAt: self startIndex withStyle: paragraph textStyle) fontNameWithPointSize.
	aList _ paragraph textStyle fontNamesWithPointSizes.
	menuList _ aList collect:[:fntName|
		fntName = curFont ifTrue:['<on>',fntName] ifFalse:['<off>',fntName]].
	reply _ (SelectionMenu labelList: menuList selections: aList) startUp.
	reply ~~ nil ifTrue:
		[self replaceSelectionWith:
			(Text string: self selection asString 
				attribute: (TextFontChange fontNumber: (aList indexOf: reply)))] ! !

!TextEditor methodsFor: 'binding' stamp: 'jmv 11/4/2008 11:51'!
bindingOf: aString
	^model bindingOf: aString! !

!TextEditor methodsFor: 'controlling' stamp: 'jmv 11/4/2008 11:51'!
controlInitialize
	"No-op for MVC ParagraphEditor compatibility"! !

!TextEditor methodsFor: 'controlling' stamp: 'jmv 11/4/2008 11:51'!
controlTerminate
	"No-op for MVC ParagraphEditor compatibility"! !

!TextEditor methodsFor: 'current selection' stamp: 'jmv 11/4/2008 14:28'!
recomputeInterval
	"The same characters are selected but their coordinates may have changed."

	self markIndex: self markIndex; pointIndex: self pointIndex! !

!TextEditor methodsFor: 'current selection' stamp: 'jmv 11/4/2008 11:51'!
reverseSelection
	"Reverse the valence of the current selection highlighting."
	selectionShowing _ selectionShowing not.
	paragraph reverseFrom: self pointBlock to: self markBlock! !

!TextEditor methodsFor: 'current selection' stamp: 'jmv 11/4/2008 11:51'!
selectAndScroll
	"Ignore scroll requests."! !

!TextEditor methodsFor: 'displaying' stamp: 'jmv 11/4/2008 11:51'!
flash
	^ morph flash! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
compileSelectionFor: anObject in: evalContext

	| methodNode method |
	methodNode _ [Compiler new
		compileNoPattern: self selectionAsStream
		in: anObject class
		context: evalContext
		notifying: self
		ifFail: [^nil]]
			on: OutOfScopeNotification
			do: [:ex | ex resume: true].
	method _ methodNode generate: #(0 0 0 0).
	^method copyWithTempNames: methodNode tempNames! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
debug: aCompiledMethod receiver: anObject in: evalContext

	| selector guineaPig debugger context |
	selector _ evalContext isNil ifTrue: [#DoIt] ifFalse: [#DoItIn:].
	anObject class addSelectorSilently: selector withMethod: aCompiledMethod.
	guineaPig _ evalContext isNil
		ifTrue: [[anObject DoIt] newProcess]
		ifFalse: [[anObject DoItIn: evalContext] newProcess].
	context _ guineaPig suspendedContext.
	debugger _ Debugger new
		process: guineaPig
		controller: nil
		context: context.
	debugger openFullNoSuspendLabel: 'Debug it'.
	[debugger interruptedContext method == aCompiledMethod]
		whileFalse: [debugger send].
	anObject class basicRemoveSelector: selector! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
debugIt

	| method receiver context |
	(model respondsTo: #doItReceiver) 
		ifTrue: 
			[FakeClassPool adopt: model selectedClass.
			receiver _ model doItReceiver.
			context _ model doItContext]
		ifFalse:
			[receiver _ context _ nil].
	self lineSelectAndEmptyCheck: [^self].
	method _ self compileSelectionFor: receiver in: context.
	method notNil ifTrue:
		[self debug: method receiver: receiver in: context].
	FakeClassPool adopt: nil! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
doIt
	"Set the context to include pool vars of the model.  Then evaluate."
	^ self evaluateSelection! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
evaluateSelection
	"Treat the current selection as an expression; evaluate it and return the result"
	| result rcvr ctxt |
	self lineSelectAndEmptyCheck: [^ ''].

	(model respondsTo: #doItReceiver) 
		ifTrue: [FakeClassPool adopt: model selectedClass.  "Include model pool vars if any"
				rcvr _ model doItReceiver.
				ctxt _ model doItContext]
		ifFalse: [rcvr _ ctxt _ nil].
	result _ [
		rcvr class evaluatorClass new 
			evaluate: self selectionAsStream
			in: ctxt
			to: rcvr
			notifying: self
			ifFail: [FakeClassPool adopt: nil. ^ #failedDoit]
			logged: true.
	] 
		on: OutOfScopeNotification 
		do: [ :ex | ex resume: true].
	FakeClassPool adopt: nil.
	^ result! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
exploreIt
	| result |
	result _ self evaluateSelection.
	((result isKindOf: FakeClassPool) or: [result == #failedDoit])
			ifTrue: [morph flash]
			ifFalse: [result explore]! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
inspectIt
	"1/13/96 sw: minor fixup"
	| result |
	result _ self evaluateSelection.
	((result isKindOf: FakeClassPool) or: [result == #failedDoit])
			ifTrue: [morph flash]
			ifFalse: [result inspect]! !

!TextEditor methodsFor: 'do-its' stamp: 'jmv 11/4/2008 11:51'!
printIt
	"Treat the current text selection as an expression; evaluate it. Insert the 
	description of the result of evaluation after the selection and then make 
	this description the new text selection."
	| result |
	result _ self evaluateSelection.
	((result isKindOf: FakeClassPool) or: [result == #failedDoit])
			ifTrue: [morph flash]
			ifFalse: [self afterSelectionInsertAndSelect: result printString]! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
align: characterStream 
	"Triggered by Cmd-u;  cycle through alignment alternatives.  8/11/96 sw"

	sensor keyboard.		"flush character"
	self align.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
browseIt: characterStream 
	"Triggered by Cmd-B; browse the thing represented by the current selection, if plausible.  1/18/96 sw"

	sensor keyboard.		"flush character"
	self browseIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
browseItHere: characterStream 
	"Triggered by Cmd-shift-B; browse the thing represented by the current selection, if plausible, in the receiver's own window.  3/1/96 sw"

	sensor keyboard.		"flush character"
	self browseItHere.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/5/2008 10:40'!
cancel: characterStream 
	"Cancel unsubmitted changes.  Flushes typeahead.  1/12/96 sw"

	sensor keyboard.
	self cancel.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
changeEmphasis: characterStream 
	"Change the emphasis of the current selection or prepare to accept characters with the change in emphasis. Emphasis change amounts to a font change.  Keeps typeahead."

	"control 0..9 -> 0..9"

	| keyCode attribute oldAttributes index thisSel colors extras |
	keyCode := ('0123456789-=' indexOf: sensor keyboard ifAbsent: [1]) - 1.
	oldAttributes := paragraph text attributesAt: self pointIndex
				forStyle: paragraph textStyle.
	thisSel := self selection.

	"Decipher keyCodes for Command 0-9..."
	(keyCode between: 1 and: 5) 
		ifTrue: [attribute := TextFontChange fontNumber: keyCode].
	keyCode = 6 
		ifTrue: 
			[colors := #(#black #magenta #red #yellow #green #blue #cyan #white).
			extras := (self morph isKindOf: OldTextMorphForEditView)
							ifFalse: 
								["not a system window"

								#()]
							ifTrue: 
								[#('Link to comment of class' 'Link to definition of class' 'Link to hierarchy of class' 'Link to method')].
			index := (PopUpMenu 
						labelArray: colors , #('choose color...' 'Do it' 'Print it') , extras 
								, #('be a web URL link' 'Edit hidden info' 'Copy hidden info')
						lines: (Array with: colors size + 1)) startUp.
			index = 0 ifTrue: [^true].
			index <= colors size 
				ifTrue: [attribute := TextColor color: (Color perform: (colors at: index))]
				ifFalse: 
					[index := index - colors size - 1.	"Re-number!!!!!!"
					index = 0 ifTrue: [attribute := self chooseColor].
					index = 1 
						ifTrue: 
							[attribute := TextDoIt new.
							thisSel := attribute analyze: self selection asString].
					index = 2 
						ifTrue: 
							[attribute := TextPrintIt new.
							thisSel := attribute analyze: self selection asString].
					extras size = 0 & (index > 2) ifTrue: [index := index + 5].	"skip those"
					index = 3 
						ifTrue: 
							[attribute := TextLink new.
							thisSel := attribute analyze: self selection asString with: 'Comment'].
					index = 4 
						ifTrue: 
							[attribute := TextLink new.
							thisSel := attribute analyze: self selection asString with: 'Definition'].
					index = 5 
						ifTrue: 
							[attribute := TextLink new.
							thisSel := attribute analyze: self selection asString with: 'Hierarchy'].
					index = 6 
						ifTrue: 
							[attribute := TextLink new.
							thisSel := attribute analyze: self selection asString].
					index = 7 
						ifTrue: 
							[attribute := TextURL new.
							thisSel := attribute analyze: self selection asString].
					index = 8 
						ifTrue: 
							["Edit hidden info"

							thisSel := self hiddenInfo.	"includes selection"
							attribute := TextEmphasis normal].
					index = 9 
						ifTrue: 
							["Copy hidden info"

							self copyHiddenInfo.
							^true].	"no other action"
					thisSel ifNil: [^true]	"Could not figure out what to link to"]].
	(keyCode between: 7 and: 11) 
		ifTrue: 
			[sensor leftShiftDown 
				ifTrue: 
					[keyCode = 10 ifTrue: [attribute := TextKern kern: -1].
					keyCode = 11 ifTrue: [attribute := TextKern kern: 1]]
				ifFalse: 
					[attribute := TextEmphasis 
								perform: (#(#bold #italic #narrow #underlined #struckOut) at: keyCode - 6).
					oldAttributes 
						do: [:att | (att dominates: attribute) ifTrue: [attribute turnOff]]]].
	keyCode = 0 ifTrue: [attribute := TextEmphasis normal].
	beginTypeInBlock ~~ nil 
		ifTrue: 
			["only change emphasisHere while typing"

			self insertTypeAhead: characterStream.
			emphasisHere := Text addAttribute: attribute toArray: oldAttributes.
			^true].
	self replaceSelectionWith: (thisSel asText addAttribute: attribute).
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
changeLfToCr: characterStream 
	"Replace all LFs by CRs.
	Triggered by Cmd-U -- useful when getting code from FTP sites"
	| cr lf |
	sensor keyboard.		"flush the triggering cmd-key character"
	cr _ Character cr.  lf _ Character linefeed.
	self replaceSelectionWith: (Text fromString:
			(self selection string collect: [:c | c = lf ifTrue: [cr] ifFalse: [c]])).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
chooseColor
	"Make a new Text Color Attribute, let the user pick a color, and return the attribute"

	| attribute |
	(OldColorPickerMorph new)
		choseModalityFromPreference;
		sourceHand: morph activeHand;
		target: (attribute := TextColor color: Color black);
		selector: #color:;
		originalColor: Color black;
		putUpFor: morph near: morph fullBoundsInWorld.	"default"
	^attribute! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
compareToClipboard: characterStream 
	"Compare the receiver to the text on the clipboard.  Flushes typeahead.  5/1/96 sw"

	sensor keyboard.	
	self compareToClipboard.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
copyHiddenInfo
	"In TextLinks, TextDoits, TextColor, and TextURLs, there is hidden
info.  Copy that to the clipboard.  You can paste it and see what it is.
Usually enclosed in <>."

	^ self clipboardTextPut: self hiddenInfo asText! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
doIt: characterStream 
	"Called when user hits cmd-d.  Select the current line, if relevant, then evaluate and execute.  2/1/96 sw.
	2/29/96 sw: don't call selectLine; it's done by doIt now"

	sensor keyboard.	
	self doIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
duplicate: characterStream
	"Paste the current selection over the prior selection, if it is non-overlapping and
	 legal.  Flushes typeahead.  Undoer & Redoer: undoAndReselect."

	sensor keyboard.
	self closeTypeIn.
	(self hasSelection and: [self isDisjointFrom: otherInterval])
		ifTrue: "Something to duplicate"
			[self replace: otherInterval with: self selection and:
				[self selectAt: self pointIndex]]
		ifFalse:
			[morph flash].
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
enclose: characterStream
	"Insert or remove bracket characters around the current selection.
	 Flushes typeahead."

	| char left right startIndex stopIndex oldSelection which text |
	char _ sensor keyboard.
	self closeTypeIn.
	startIndex _ self startIndex.
	stopIndex _ self stopIndex.
	oldSelection _ self selection.
	which _ '([<{"''' indexOf: char ifAbsent: [ ^true ].
	left _ '([<{"''' at: which.
	right _ ')]>}"''' at: which.
	text _ paragraph text.
	((startIndex > 1 and: [stopIndex <= text size])
		and:
		[(text at: startIndex-1) = left and: [(text at: stopIndex) = right]])
		ifTrue:
			["already enclosed; strip off brackets"
			self selectFrom: startIndex-1 to: stopIndex.
			self replaceSelectionWith: oldSelection]
		ifFalse:
			["not enclosed; enclose by matching brackets"
			self replaceSelectionWith:
				(Text string: (String with: left), oldSelection string ,(String with: right)
					emphasis: emphasisHere).
			self selectFrom: startIndex+1 to: stopIndex].
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
exchange: characterStream
	"Exchange the current and prior selections.  Keeps typeahead."

	sensor keyboard.	 "Flush character"
	self closeTypeIn: characterStream.
	self exchange.
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
exploreIt: characterStream 
	"Explore the selection -- invoked via cmd-shift-I.  If there is no current selection, use the current line."

	sensor keyboard.		"flush character"
	self exploreIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
fileItIn: characterStream 
	"File in the selection; invoked via a keyboard shortcut, -- for now, cmd-shift-G."

	sensor keyboard.		"flush character"
	self fileItIn.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
hiddenInfo
	"In TextLinks, TextDoits, TextColor, and TextURLs, there is hidden info.  Return the entire string that was used by Cmd-6 to create this text attribute.  Usually enclosed in < >."

	| attrList |
	attrList _ paragraph text attributesAt: (self pointIndex +
self markIndex)//2 forStyle: paragraph textStyle.
	attrList do: [:attr |
		(attr isKindOf: TextAction) ifTrue:
			[^ self selection asString, '<', attr info, '>']].
	"If none of the above"
	attrList do: [:attr |
		attr class == TextColor ifTrue:
			[^ self selection asString, '<', attr color printString, '>']].
	^ self selection asString, '[No hidden info]'! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
implementorsOfIt: characterStream 
	"Triggered by Cmd-m; browse implementors of the selector represented by the current selection, if plausible. 2/1/96 sw"

	sensor keyboard.		"flush character"
	self implementorsOfIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
inOutdent: characterStream delta: delta
	"Add/remove a tab at the front of every line occupied by the selection. Flushes typeahead.  Derived from work by Larry Tesler back in December 1985.  Now triggered by Cmd-L and Cmd-R.  2/29/96 sw"

	| cr realStart realStop lines startLine stopLine start stop adjustStart indentation size numLines inStream newString outStream |
	sensor keyboard.  "Flush typeahead"
	cr _ Character cr.

	"Operate on entire lines, but remember the real selection for re-highlighting later"
	realStart _ self startIndex.
	realStop _ self stopIndex - 1.

	"Special case a caret on a line of its own, including weird case at end of paragraph"
	(realStart > realStop and:
				[realStart < 2 or: [(paragraph string at: realStart - 1) == cr]])
		ifTrue:
			[delta < 0
				ifTrue:
					[morph flash]
				ifFalse:
					[self replaceSelectionWith: Character tab asSymbol asText.
					self selectAt: realStart + 1].
			^true].

	lines _ paragraph lines.
	startLine _ paragraph lineIndexOfCharacterIndex: realStart.
	stopLine _ paragraph lineIndexOfCharacterIndex: (realStart max: realStop).
	start _ (lines at: startLine) first.
	stop _ (lines at: stopLine) last.
	
	"Pin the start of highlighting unless the selection starts a line"
	adjustStart _ realStart > start.

	"Find the indentation of the least-indented non-blank line; never outdent more"
	indentation _ (startLine to: stopLine) inject: 1000 into:
		[:m :l |
		m _ m min: (paragraph indentationOfLineIndex: l ifBlank: [:tabs | 1000])].			

	size _  stop + 1 - start.
	numLines _ stopLine + 1 - startLine.
	inStream _ ReadStream on: paragraph string from: start to: stop.

	newString _ String new: size + ((numLines * delta) max: 0).
	outStream _ ReadWriteStream on: newString.

	"This subroutine does the actual work"
	self indent: delta fromStream: inStream toStream: outStream.

	"Adjust the range that will be highlighted later"
	adjustStart ifTrue: [realStart _ (realStart + delta) max: start].
	realStop _ realStop + outStream position - size.

	"Prepare for another iteration"
	indentation _ indentation + delta.
	size _ outStream position.
	inStream _ outStream setFrom: 1 to: size.

	outStream == nil
		ifTrue: 	"tried to outdent but some line(s) were already left flush"
			[morph flash]
		ifFalse:
			[self selectInvisiblyFrom: start to: stop.
			size = newString size ifFalse: [newString _ outStream contents].
			self replaceSelectionWith: newString asText].
	self selectFrom: realStart to: realStop. 	"highlight only the original range"
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
indent: characterStream
	"Add a tab at the front of every line occupied by the selection. Flushes typeahead.  Invoked from keyboard via cmd-shift-R.  2/29/96 sw"

	^ self inOutdent: characterStream delta: 1! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
inspectIt: characterStream 
	"Inspect the selection -- invoked via cmd-i.  If there is no current selection, use the current line.  1/17/96 sw
	 2/29/96 sw: don't call selectLine; it's done by inspectIt now"

	sensor keyboard.		"flush character"
	self inspectIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
makeCapitalized: characterStream 
	"Force the current selection to uppercase.  Triggered by Cmd-X."
	| prev |
	sensor keyboard.		"flush the triggering cmd-key character"
	prev _ $-.  "not a letter"
	self replaceSelectionWith: (Text fromString:
			(self selection string collect:
				[:c | prev _ prev isLetter ifTrue: [c asLowercase] ifFalse: [c asUppercase]])).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
makeLowercase: characterStream 
	"Force the current selection to lowercase.  Triggered by Cmd-X."

	sensor keyboard.		"flush the triggering cmd-key character"
	self replaceSelectionWith: (Text fromString: (self selection string asLowercase)).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
makeUppercase: characterStream 
	"Force the current selection to uppercase.  Triggered by Cmd-Y."

	sensor keyboard.		"flush the triggering cmd-key character"
	self replaceSelectionWith: (Text fromString: (self selection string asUppercase)).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
methodNamesContainingIt: characterStream 
	"Browse methods whose selectors containing the selection in their names"

	sensor keyboard.		"flush character"
	self methodNamesContainingIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
methodStringsContainingIt: characterStream 
	"Invoked from cmd-E -- open a browser on all methods holding string constants containing it.  Flushes typeahead. "

	sensor keyboard.	
	self methodStringsContainingit.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
offerFontMenu: characterStream 
	"The user typed the command key that requests a font change; Offer the font menu.  5/27/96 sw
	 Keeps typeahead.  (?? should flush?)"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self offerFontMenu.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
outdent: characterStream
	"Remove a tab from the front of every line occupied by the selection. Flushes typeahead.  Invoked from keyboard via cmd-shift-L.  2/29/96 sw"

	^ self inOutdent: characterStream delta: -1! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
pasteInitials: characterStream 
	"Replace the current text selection by an authorship name/date stamp; invoked by cmd-shift-v, easy way to put an authorship stamp in the comments of an editor.
	 Keeps typeahead."

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self replace: self selectionInterval with: (Text fromString: Utilities changeStamp) and: [self selectAt: self stopIndex].
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
printIt: characterStream 
	"Print the results of evaluting the selection -- invoked via cmd-p.  If there is no current selection, use the current line.  1/17/96 sw
	 2/29/96 sw: don't call selectLine now, since it's called by doIt"

	sensor keyboard.		"flush character"
	self printIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
referencesToIt: characterStream 
	"Triggered by Cmd-N; browse references to the current selection"

	sensor keyboard.		"flush character"
	self referencesToIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
save: characterStream
	"Submit the current text.  Equivalent to 'accept' 1/18/96 sw
	 Keeps typeahead."

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self accept.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
sendersOfIt: characterStream 
	"Triggered by Cmd-n; browse implementors of the selector represented by the current selection, if plausible. 2/1/96 sw"

	sensor keyboard.		"flush character"
	self sendersOfIt.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
shiftEnclose: characterStream
	"Insert or remove bracket characters around the current selection.
	 Flushes typeahead."

	| char left right startIndex stopIndex oldSelection which text |
	char _ sensor keyboard.
	char = $9 ifTrue: [ char _ $( ].
	char = $, ifTrue: [ char _ $< ].
	char = $[ ifTrue: [ char _ ${ ].
	char = $' ifTrue: [ char _ $" ].
	char asciiValue = 27 ifTrue: [ char _ ${ ].	"ctrl-["

	self closeTypeIn.
	startIndex _ self startIndex.
	stopIndex _ self stopIndex.
	oldSelection _ self selection.
	which _ '([<{"''' indexOf: char ifAbsent: [1].
	left _ '([<{"''' at: which.
	right _ ')]>}"''' at: which.
	text _ paragraph text.
	((startIndex > 1 and: [stopIndex <= text size])
		and:
		[(text at: startIndex-1) = left and: [(text at: stopIndex) = right]])
		ifTrue:
			["already enclosed; strip off brackets"
			self selectFrom: startIndex-1 to: stopIndex.
			self replaceSelectionWith: oldSelection]
		ifFalse:
			["not enclosed; enclose by matching brackets"
			self replaceSelectionWith:
				(Text string: (String with: left), oldSelection string ,(String with: right)
					emphasis: emphasisHere).
			self selectFrom: startIndex+1 to: stopIndex].
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
spawnIt: characterStream
	"Triggered by Cmd-o; spawn a new code window, if it makes sense."

	sensor keyboard.
	self spawn.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
swapChars: characterStream 
	"Triggered byCmd-Y;.  Swap two characters, either those straddling the insertion point, or the two that comprise the selection.  Suggested by Ted Kaehler.  "

	| currentSelection aString chars |
	sensor keyboard.		"flush the triggering cmd-key character"
	(chars _ self selection) size == 0
		ifTrue:
			[currentSelection _ self pointIndex.
			self selectMark: currentSelection - 1 point: currentSelection]
		ifFalse:
			[chars size == 2
				ifFalse:
					[morph flash. ^ true]
				ifTrue:
					[currentSelection _ self pointIndex - 1]].
	aString _ self selection string.
	self replaceSelectionWith: (Text string: aString reversed emphasis: emphasisHere).
	self selectAt: currentSelection + 1.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 11/4/2008 11:51'!
undo: characterStream 
	"Undo the last edit.  Keeps typeahead, so undo twice is a full redo."

	sensor keyboard. 	"flush character"
	self closeTypeIn: characterStream.
	self undo.
	^true! !

!TextEditor methodsFor: 'events' stamp: 'jmv 11/5/2008 14:15'!
mouseDown: evt 
	"An attempt to break up the old processRedButton code into threee phases"
	| clickPoint b |

	oldInterval _ self selectionInterval.
	clickPoint _ evt cursorPoint.
	(paragraph clickAt: clickPoint for: model controller: self) ifTrue: [
		b _ paragraph characterBlockAtPoint: clickPoint.
		self markBlock: b.
		self pointBlock: b.
		evt hand releaseKeyboardFocus: self.
		^ self].
	evt shiftPressed
		ifFalse:
			[self closeTypeIn.
			b _ paragraph characterBlockAtPoint: clickPoint.
			self markBlock: b.
			self pointBlock: b.]
		ifTrue:
			[self closeTypeIn.
			self mouseMove: evt].
	self storeSelectionInParagraph! !

!TextEditor methodsFor: 'events' stamp: 'jmv 11/5/2008 14:15'!
mouseMove: evt 
	"Change the selection in response to moue-down drag"

	self pointBlock: (paragraph characterBlockAtPoint: (evt cursorPoint)).
	self storeSelectionInParagraph! !

!TextEditor methodsFor: 'events' stamp: 'jmv 11/4/2008 11:51'!
mouseUp: evt
	"An attempt to break up the old processRedButton code into threee phases"
	oldInterval ifNil: [^ self].  "Patched during clickAt: repair"
	(self hasCaret 
		and: [oldInterval = self selectionInterval])
		ifTrue: [self selectWord].
	self setEmphasisHere.
	(self isDisjointFrom: oldInterval) ifTrue:
		[otherInterval _ oldInterval].
	self storeSelectionInParagraph! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainAnySel: symbol 
	"Is this any message selector?"

	| list reply |
	list _ Smalltalk allClassesImplementing: symbol.
	list size = 0 ifTrue: [^nil].
	list size < 12
		ifTrue: [reply _ ' is a message selector which is defined in these classes ' , list printString]
		ifFalse: [reply _ ' is a message selector which is defined in many classes'].
	^'"' , symbol , reply , '."' , '\' withCRs, 'Smalltalk browseAllImplementorsOf: #' , symbol! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainChar: string
	"Does string start with a special character?"

	| char |
	char _ string at: 1.
	char = $. ifTrue: [^'"Period marks the end of a Smalltalk statement.  A period in the middle of a number means a decimal point.  (The number is an instance of class Float)."'].
	char = $' ifTrue: [^'"The characters between two single quotes are made into an instance of class String"'].
	char = $" ifTrue: [^'"Double quotes enclose a comment.  Smalltalk ignores everything between double quotes."'].
	char = $# ifTrue: [^'"The characters following a hash mark are made into an instance of class Symbol.  If parenthesis follow a hash mark, an instance of class Array is made.  It contains literal constants."'].
	(char = $( or: [char = $)]) ifTrue: [^'"Expressions enclosed in parenthesis are evaluated first"'].
	(char = $[ or: [char = $]]) ifTrue: [^'"The code inside square brackets is an unevaluated block of code.  It becomes an instance of BlockContext and is usually passed as an argument."'].
	(char = ${ or: [char = $}]) ifTrue: [^ '"A sequence of expressions separated by periods, when enclosed in curly braces, are evaluated to yield the elements of a new Array"'].
	(char = $< or: [char = $>]) ifTrue: [^'"<primitive: xx> means that this method is usually preformed directly by the virtual machine.  If this method is primitive, its Smalltalk code is executed only when the primitive fails."'].
	char = $^ ifTrue: [^'"Uparrow means return from this method.  The value returned is the expression following the ^"'].
	char = $| ifTrue: [^'"Vertical bars enclose the names of the temporary variables used in this method.  In a block, the vertical bar separates the argument names from the rest of the code."'].
	char = $_ ifTrue: [^'"Left arrow means assignment.  The value of the expression after the left arrow is stored into the variable before it."'].
	char = $; ifTrue: [^'"Semicolon means cascading.  The message after the semicolon is sent to the same object which received the message before the semicolon."'].
	char = $: ifTrue: [^'"A colon at the end of a keyword means that an argument is expected to follow.  Methods which take more than one argument have selectors with more than one keyword.  (One keyword, ending with a colon, appears before each argument).', '\\' withCRs, 'A colon before a variable name just inside a block means that the block takes an agrument.  (When the block is evaluated, the argument will be assigned to the variable whose name appears after the colon)."'].
	char = $$ ifTrue: [^'"The single character following a dollar sign is made into an instance of class Character"'].
	char = $- ifTrue: [^'"A minus sign in front of a number means a negative number."'].
	char = $e ifTrue: [^'"An e in the middle of a number means that the exponent follows."'].
	char = $r ifTrue: [^'"An r in the middle of a bunch of digits is an instance of Integer expressed in a certain radix.  The digits before the r denote the base and the digits after it express a number in that base."'].
	char = Character space ifTrue: [^'"the space Character"'].
	char = Character tab ifTrue: [^'"the tab Character"'].
	char = Character cr ifTrue: [^'"the carriage return Character"'].
	^nil! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainClass: symbol 
	"Is symbol a class variable or a pool variable?"
	| class reply classes |
	(model respondsTo: #selectedClassOrMetaClass)
		ifFalse: [^ nil].
	(class _ model selectedClassOrMetaClass) ifNil: [^ nil].
	"no class is selected"
	(class isKindOf: Metaclass)
		ifTrue: [class _ class soleInstance].
	classes _ (Array with: class)
				, class allSuperclasses.
	"class variables"
	reply _ classes detect: [:each | (each classVarNames detect: [:name | symbol = name]
					ifNone: [])
					~~ nil]
				ifNone: [].
	reply == nil ifFalse: [^ '"is a class variable, defined in class ' , reply printString , '"\' withCRs , 'SystemNavigation new browseAllCallsOn: (' , reply printString , ' classPool associationAt: #' , symbol , ').'].
	"pool variables"
	classes do: [:each | (each sharedPools
			detect: [:pool | (pool includesKey: symbol)
					and: 
						[reply _ pool.
						true]]
			ifNone: [])
			~~ nil].
	reply
		ifNil: [(Undeclared includesKey: symbol)
				ifTrue: [^ '"is an undeclared variable.' , '"\' withCRs , 'SystemNavigation new browseAllCallsOn: (Undeclared associationAt: #' , symbol , ').']]
		ifNotNil: 
			[classes _ WriteStream on: Array new.
			Smalltalk
				allBehaviorsDo: [:each | (each sharedPools
						detect: 
							[:pool | 
							pool == reply]
						ifNone: [])
						~~ nil ifTrue: [classes nextPut: each]].
			"Perhaps not print whole list of classes if too long. (unlikely)"
			^ '"is a pool variable from the pool ' , (Smalltalk keyAtIdentityValue: reply) asString , ', which is used by the following classes ' , classes contents printString , '"\' withCRs , 'SystemNavigation new browseAllCallsOn: (' , (Smalltalk keyAtIdentityValue: reply) asString , ' bindingOf: #' , symbol , ').'].
	^ nil! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainCtxt: symbol 
	"Is symbol a context variable?"

	| reply classes text cls |
	symbol = #nil ifTrue: [reply _ '"is a constant.  It is the only instance of class UndefinedObject.  nil is the initial value of all variables."'].
	symbol = #true ifTrue: [reply _ '"is a constant.  It is the only instance of class True and is the receiver of many control messages."'].
	symbol = #false ifTrue: [reply _ '"is a constant.  It is the only instance of class False and is the receiver of many control messages."'].
	symbol = #thisContext ifTrue: [reply _ '"is a context variable.  Its value is always the MethodContext which is executing this method."'].
	(model respondsTo: #selectedClassOrMetaClass) ifTrue: [
		cls _ model selectedClassOrMetaClass].
	cls ifNil: [^ reply].	  "no class known"
	symbol = #self ifTrue: 
			[classes _ cls withAllSubclasses.
			classes size > 12
				ifTrue: [text _ cls printString , ' or a subclass']
				ifFalse: 
					[classes _ classes printString.
					text _ 'one of these classes' , (classes copyFrom: 4 to: classes size)].
			reply _ '"is the receiver of this message; an instance of ' , text , '"'].
	symbol = #super ifTrue: [reply _ '"is just like self.  Messages to super are looked up in the superclass (' , cls superclass printString , ')"'].
	^reply! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainGlobal: symbol 
	"Is symbol a global variable?"
	| reply classes |
	reply _ Smalltalk at: symbol ifAbsent: [^nil].
	(reply class == Dictionary or:[reply isKindOf: SharedPool class])
		ifTrue: 
			[classes _ Set new.
			Smalltalk allBehaviorsDo: [:each | (each sharedPools detect: [:pool | pool == reply]
					ifNone: [])
					~~ nil ifTrue: [classes add: each]].
			classes _ classes printString.
			^'"is a global variable.  It is a pool which is used by the following classes ' , (classes allButFirst: 5) , '"'].
	(reply isKindOf: Behavior)
		ifTrue: [^'"is a global variable.  ' , symbol , ' is a class in category ', reply category,
			'."', '\' withCRs, 'Browser newOnClass: ' , symbol , '.'].
	symbol == #Smalltalk ifTrue: [^'"is a global.  Smalltalk is the only instance of SystemDictionary and holds all global variables."'].
	^'"is a global variable.  ' , symbol , ' is ' , reply printString , '"'! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainInst: string 
	"Is string an instance variable of this class?"
	| classes cls |

	(model respondsTo: #selectedClassOrMetaClass) ifTrue: [
		cls _ model selectedClassOrMetaClass].
	cls ifNil: [^ nil].	  "no class known"
	classes _ (Array with: cls)
				, cls allSuperclasses.
	classes _ classes detect: [:each | (each instVarNames
			detect: [:name | name = string] ifNone: [])
			~~ nil] ifNone: [^nil].
	classes _ classes printString.
	^ '"is an instance variable of the receiver; defined in class ' , classes , 
		'"\' withCRs , classes , ' systemNavigation browseAllAccessesTo: ''' , string , ''' from: ', classes, '.'! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainMySel: symbol 
	"Is symbol the selector of this method?  Is it sent by this method?  If 
	not, then expalin will call (explainPartSel:) to see if it is a fragment of a 
	selector sent here.  If not, explain will call (explainAnySel:) to catch any 
	selector. "

	| lits classes msg |
	(model respondsTo: #selectedMessageName) ifFalse: [^ nil].
	(msg _ model selectedMessageName) ifNil: [^nil].	"not in a message"
	classes _ Smalltalk allClassesImplementing: symbol.
	classes size > 12
		ifTrue: [classes _ 'many classes']
		ifFalse: [classes _ 'these classes ' , classes printString].
	msg = symbol
		ifTrue: [^ '"' , symbol , ' is the selector of this very method!!  It is defined in ',
			classes , '.  To see the other definitions, go to the message list pane, get the menu from the top of the scroll bar, and select ''implementors of...''."']
		ifFalse: 
			[lits _ (model selectedClassOrMetaClass compiledMethodAt:
				msg) messages.
			(lits detect: [:each | each == symbol]
				ifNone: [])
				== nil ifTrue: [^nil].
			^ '"' , symbol , ' is a message selector which is defined in ', classes , '.  To see the definitions, go to the message list pane, get the menu from the top of the scroll bar, and select ''implementors of...''."'].! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainNumber: string 
	"Is string a Number?"

	| strm c |
	(c _ string at: 1) isDigit ifFalse: [(c = $- and: [string size > 1 and: [(string at: 2) isDigit]])
			ifFalse: [^nil]].
	strm _ ReadStream on: string.
	c _ Number readFrom: strm.
	strm atEnd ifFalse: [^nil].
	c printString = string
		ifTrue: [^'"' , string , ' is a ' , c class name , '"']
		ifFalse: [^'"' , string , ' (= ' , c printString , ') is a ' , c class name , '"']! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainPartSel: string 
	"Is this a fragment of a multiple-argument selector sent in this method?"
	| lits whole reply classes s msg |

	(model respondsTo: #selectedMessageName) ifFalse: [^ nil].
	(msg _ model selectedMessageName) ifNil: [^ nil].  "not in a message"
	string last == $: ifFalse: [^ nil].
	"Name of this method"
	lits _ Array with: msg.
	(whole _ lits detect: [:each | (each keywords detect: [:frag | frag = string]
					ifNone: []) ~~ nil]
				ifNone: []) ~~ nil
		ifTrue: [reply _ ', which is the selector of this very method!!'.
			s _ '.  To see the other definitions, go to the message list pane, get the menu from the top of the scroll bar, and select ''implementors of...''."']
		ifFalse: 
			["Selectors called from this method"
			lits _ (model selectedClassOrMetaClass compiledMethodAt:
				msg) messages.
			(whole _ lits detect: [:each | (each keywords detect: [:frag | frag = string]
							ifNone: []) ~~ nil]
						ifNone: []) ~~ nil
				ifFalse: [string = 'primitive:'
					ifTrue: [^self explainChar: '<']
					ifFalse: [^nil]].
			reply _ '.'.
			s _ '.  To see the definitions, go to the message list pane, get the menu from the top of the scroll bar, and select ''implementors of...''."'].
	classes _ Smalltalk allClassesImplementing: whole.
	classes size > 12
		ifTrue: [classes _ 'many classes']
		ifFalse: [classes _ 'these classes ' , classes printString].
	^ '"' , string , ' is one part of the message selector ' , whole, reply , '  It is defined in ' , classes , s! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainScan: string 
	"Remove beginning and trailing space, tab, cr.
	 1/15/96 sw: copied intact from BrowserCodeController"

	| c beg end |
	beg _ 1.
	end _ string size.
	
	[beg = end ifTrue: [^string copyFrom: 1 to: 1].
	"if all blank, tell about the first"
	c _ string at: beg.
	c = Character space or: [c = Character tab or: [c = Character cr]]]
		whileTrue: [beg _ beg + 1].
	
	[c _ string at: end.
	c = Character space or: [c = Character tab or: [c = Character cr]]]
		whileTrue: [end _ end - 1].
	^string copyFrom: beg to: end	"Return purely visible characters"! !

!TextEditor methodsFor: 'explain' stamp: 'jmv 11/4/2008 11:51'!
explainTemp: string 
	"Is string the name of a temporary variable (or block argument variable)?"

	| selectedClass tempNames i reply methodNode method msg |
	(model respondsTo: #selectedMessageName) ifFalse: [^ nil].
	(msg _ model selectedMessageName) ifNil: [^nil].	"not in a message"
	selectedClass _ model selectedClassOrMetaClass.
	tempNames _ selectedClass parserClass new 
			parseArgsAndTemps: model selectedMessage notifying: nil.
	method _ selectedClass compiledMethodAt: msg.
	(i _ tempNames findFirst: [:each | each = string]) = 0 ifTrue: [
		(method numTemps > tempNames size)
			ifTrue: 
				["It must be an undeclared block argument temporary"
				methodNode _ selectedClass compilerClass new
							parse: model selectedMessage
							in: selectedClass
							notifying: nil.
				tempNames _ methodNode tempNames]
			ifFalse: [^nil]].
	(i _ tempNames findFirst: [:each | each = string]) > 0 ifTrue: [i > method numArgs
			ifTrue: [reply _ '"is a temporary variable in this method"']
			ifFalse: [reply _ '"is an argument to this method"']].
	^reply! !

!TextEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 11:51'!
changeParagraph: aParagraph 
	"Install aParagraph as the one to be edited by the receiver."

	UndoParagraph == paragraph ifTrue: [UndoParagraph _ nil].
	paragraph _ aParagraph.
	self resetState! !

!TextEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:42'!
initialize
	"Initialize the state of the receiver. Subclasses should include 'super 
	initialize' when redefining this message to insure proper initialization."

	super initialize.
	self changeParagraph: '' asParagraph! !

!TextEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 13:51'!
resetState 
	"Establish the initial conditions for editing the paragraph: place caret 
	before first character, set the emphasis to that of the first character,
	and save the paragraph for purposes of canceling."

	markBlock _ paragraph defaultCharacterBlock.
	self pointBlock: markBlock copy.
	beginTypeInBlock _ nil.
	UndoInterval _ otherInterval _ 1 to: 0.
	self setEmphasisHere.
	selectionShowing _ false! !

!TextEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 11:51'!
stateArray
	^ {ChangeText.
		FindText.
		UndoInterval.
		UndoMessage.
		UndoParagraph.
		UndoSelection.
		Undone.
		self selectionInterval.
		self startOfTyping.
		emphasisHere}! !

!TextEditor methodsFor: 'initialize-release' stamp: 'jmv 11/4/2008 11:51'!
stateArrayPut: stateArray
	| sel |
	ChangeText _ stateArray at: 1.
	FindText _ stateArray at: 2.
	UndoInterval _ stateArray at: 3.
	UndoMessage _ stateArray at: 4.
	UndoParagraph _ stateArray at: 5.
	UndoSelection _ stateArray at: 6.
	Undone _ stateArray at: 7.
	sel _ stateArray at: 8.
	self selectFrom: sel first to: sel last.
	beginTypeInBlock _ stateArray at: 9.
	emphasisHere _ stateArray at: 10! !

!TextEditor methodsFor: 'menu commands' stamp: 'jmv 11/4/2008 11:51'!
offerMenuFromEsc: characterStream 
	"The escape key was hit while the receiver has the keyboard focus; take action"

	^ ActiveEvent shiftPressed 
		ifTrue:
			[self escapeToDesktop: characterStream]
		ifFalse:
			[self raiseContextMenu: characterStream]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
accept
	"Save the current text of the text being edited as the current acceptable version for purposes of canceling.  Allow my morph to take appropriate action"
	morph acceptContents! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
again
	"Text substitution. If the left shift key is down, the substitution is made 
	throughout the entire Paragraph. Otherwise, only the next possible 
	substitution is made.
	Undoer & Redoer: #undoAgain:andReselect:typedKey:."

	"If last command was also 'again', use same keys as before"
	self againOrSame: (UndoMessage sends: #undoAgain:andReselect:typedKey:)! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
align
	"Align text according to the next greater alignment value,
	cycling among leftFlush, rightFlush, center, and justified."
	self changeAlignment.
	self recomputeInterval! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
browseChangeSetsWithSelector
	"Determine which, if any, change sets have at least one change for the selected selector, independent of class"

	| aSelector |
	self lineSelectAndEmptyCheck: [^ self].
	(aSelector _ self selectedSelector) == nil ifTrue: [^ morph flash].
	ChangeSorter browseChangeSetsWithSelector: aSelector! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
browseClassFromIt
	"Launch a hierarchy browser for the class indicated by the current selection.  If multiple classes matching the selection exist, let the user choose among them."

	| aClass |
	self lineSelectAndEmptyCheck: [^ self].

	aClass _ Utilities classFromPattern: (self selection string copyWithout: Character cr) withCaption: 'choose a class to browse...'.
	aClass ifNil: [^ morph flash].

	Utilities spawnHierarchyForClass: aClass selector: nil! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
browseIt
	"Launch a browser for the current selection, if appropriate"

	| aSymbol anEntry brow |

	Preferences alternativeBrowseIt ifTrue: [^ self browseClassFromIt].

	self lineSelectAndEmptyCheck: [^ self].
	(aSymbol _ self selectedSymbol) isNil ifTrue: [^ morph flash].

	aSymbol first isUppercase
		ifTrue:
			[anEntry _ (Smalltalk
				at: aSymbol
				ifAbsent:
					[ Smalltalk browseAllImplementorsOf: aSymbol.
					^ nil]).
			anEntry isNil ifTrue: [^ morph flash].
			(anEntry isKindOf: Class)
				ifFalse:	[anEntry _ anEntry class].
			brow _ Preferences browseToolClass new.
			brow setClass: anEntry selector: nil.
			brow class
				openBrowserView: (brow openEditString: nil)
				label: 'System Browser']
		ifFalse:
			[Smalltalk browseAllImplementorsOf: aSymbol]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
browseItHere
	"Retarget the receiver's window to look at the selected class, if appropriate.  3/1/96 sw"
	| aSymbol foundClass b |
	(((b _ model) isKindOf: Browser) and: [b couldBrowseAnyClass])
		ifFalse: [^ morph flash].
	model okToChange ifFalse: [^ morph flash].
	self selectionInterval isEmpty ifTrue: [self selectWord].
	(aSymbol _ self selectedSymbol) isNil ifTrue: [^ morph flash].

	foundClass _ (Smalltalk at: aSymbol ifAbsent: [nil]).
		foundClass isNil ifTrue: [^ morph flash].
		(foundClass isKindOf: Class)
			ifTrue:
				[model systemCategoryListIndex: 
					(model systemCategoryList indexOf: foundClass category).
	model classListIndex: (model classList indexOf: foundClass name)]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
cancel
	"Cancel the changes made so far to this text"
	morph cancelEdits! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
changeAlignment
	| aList reply  |
	aList _ #(leftFlush centered justified rightFlush).
	reply _ (SelectionMenu labelList: aList selections: aList) startUp.
	reply ifNil:[^self].
	self setAlignment: reply.
	paragraph composeAll.
	self recomputeSelection.
	^ true! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
chooseAlignment
	self changeAlignment! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
classCommentsContainingIt
	"Open a browser class comments which contain the current selection somewhere in them."

	self lineSelectAndEmptyCheck: [^ self].
	Smalltalk browseClassCommentsWithString: self selection string! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
classNamesContainingIt
	"Open a browser on classes whose names contain the selected string"

	self lineSelectAndEmptyCheck: [^self].
	Smalltalk
		browseClassesWithNamesContaining: self selection string
		caseSensitive: Sensor leftShiftDown! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
compareToClipboard
	"Check to see if whether the receiver's text is the same as the text currently on the clipboard, and inform the user."
	| s1 s2 |
	s1 _ self clipboardText string.
	s2 _ paragraph text string.
	s1 = s2 ifTrue: [^ self inform: 'Exact match'].

	(StringHolder new textContents:
		(TextDiffBuilder buildDisplayPatchFrom: s1 to: s2))
		openLabel: 'Comparison to Clipboard Text'! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
copySelection
	"Copy the current selection and store it in the paste buffer, unless a caret.  Undoer & Redoer: undoCutCopy"

	self lineSelectAndEmptyCheck: [^ self].

	"Simulate 'substitute: self selection' without locking the controller"
	UndoSelection _ self selection.
	self undoer: #undoCutCopy: with: self clipboardText.
	UndoInterval _ self selectionInterval.
	self clipboardTextPut: UndoSelection! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
cut
	"Cut out the current selection and redisplay the paragraph if necessary.  Undoer & Redoer: undoCutCopy:"

	self lineSelectAndEmptyCheck: [^ self].

	self replaceSelectionWith: self nullText. 
	self undoer: #undoCutCopy: with: self clipboardText.
	self clipboardTextPut: UndoSelection! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
exchange
	"See comment in exchangeWith:"

	self exchangeWith: otherInterval! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
explain
	"Try to shed some light on what kind of entity the current selection is. 
	The selection must be a single token or construct. Insert the answer after 
	the selection. Send private messages whose names begin with 'explain' 
	that return a string if they recognize the selection, else nil."

	| string tiVars cgVars selectors delimitors numbers sorry reply symbol |
Cursor execute showWhile: 
			[sorry _ '"Sorry, I can''t explain that.  Please select a single
token, construct, or special character.'.
			sorry _ sorry , (morph canDiscardEdits
							ifFalse: ['  Also, please cancel or accept."']
							ifTrue: ['"']).
			(string _ self selection asString) isEmpty
				ifTrue: [reply _ '']
				ifFalse: [string _ self explainScan: string.
					"Remove space, tab, cr"
					"Temps and Instance vars need only test strings that are all letters"
					(string detect: [:char | (char isLetter or: [char isDigit]) not]
						ifNone: []) ifNil: 
							[tiVars _ self explainTemp: string.
							tiVars == nil ifTrue: [tiVars _ self explainInst: string]].
					(tiVars == nil and: [model respondsTo: #explainSpecial:])
						ifTrue: [tiVars _ model explainSpecial: string].
					tiVars == nil
						ifTrue: [tiVars _ '']
						ifFalse: [tiVars _ tiVars , '\' withCRs].
					"Context, Class, Pool, and Global vars, and Selectors need 
					only test symbols"
					(Symbol hasInterned: string ifTrue: [:s | symbol _ s])
						ifTrue: [cgVars _ self explainCtxt: symbol.
							cgVars == nil
								ifTrue: [cgVars _ self explainClass: symbol.
									cgVars == nil ifTrue: [cgVars _ self explainGlobal: symbol]].
							"See if it is a Selector (sent here or not)"
							selectors _ self explainMySel: symbol.
							selectors == nil
								ifTrue: 
									[selectors _ self explainPartSel: string.
									selectors == nil ifTrue: [
										selectors _ self explainAnySel: symbol]]]
						ifFalse: [selectors _ self explainPartSel: string].
					cgVars == nil
						ifTrue: [cgVars _ '']
						ifFalse: [cgVars _ cgVars , '\' withCRs].
					selectors == nil
						ifTrue: [selectors _ '']
						ifFalse: [selectors _ selectors , '\' withCRs].
					string size = 1
						ifTrue: ["single special characters"
							delimitors _ self explainChar: string]
						ifFalse: ["matched delimitors"
							delimitors _ self explainDelimitor: string].
					numbers _ self explainNumber: string.
					numbers == nil ifTrue: [numbers _ ''].
					delimitors == nil ifTrue: [delimitors _ ''].
					reply _ tiVars , cgVars , selectors , delimitors , numbers].
			reply size = 0 ifTrue: [reply _ sorry].
			self afterSelectionInsertAndSelect: reply]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
fileItIn
	"Make a Stream on the text selection and fileIn it.
	 1/24/96 sw: moved here from FileController; this function can be useful from any text window that shows stuff in chunk format"

	| selection |
	selection _ self selection.
	(ReadWriteStream on: selection string from: 1 to: selection size) fileIn
! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
find
	"Prompt the user for a string to search for, and search the receiver from the current selection onward for it.  1/26/96 sw"

	| reply |
	reply _ FillInTheBlank request: 'Find what? ' initialAnswer: ''.
	reply size == 0 ifTrue: [
		^ self].
	self setSearch: reply.
	ChangeText _ FindText.  "Implies no replacement to againOnce: method"
	self againOrSame: true.

	morph installEditorToReplace: self! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
findAgain
	"Find the text-to-find again.  1/24/96 sw"

	self againOrSame: true! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
implementorsOfIt
	"Open an implementors browser on the selected selector"

	| aSelector |
	self lineSelectAndEmptyCheck: [^ self].
	(aSelector _ self selectedSelector) == nil ifTrue: [^ morph flash].
	Smalltalk browseAllImplementorsOf: aSelector! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
methodNamesContainingIt
	"Open a browser on methods names containing the selected string"

	self lineSelectAndEmptyCheck: [^ self].
	Cursor wait showWhile:
		[Smalltalk browseMethodsWhoseNamesContain: self selection string withBlanksTrimmed].
	Cursor normal show! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
methodSourceContainingIt
	"Open a browser on methods which contain the current selection in their source (case-sensitive full-text search of source).   EXTREMELY slow!!"

	self lineSelectAndEmptyCheck: [^ self].
	(self confirm: 'This will take a few minutes.
Shall I proceed?') ifFalse: [^ self].
	Smalltalk browseMethodsWithSourceString: self selection string! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
methodStringsContainingit
	"Open a browser on methods which contain the current selection as part of a string constant."

	self lineSelectAndEmptyCheck: [^ self].
	Smalltalk browseMethodsWithString: self selection string! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
mvcRedisplay
	"Ignore mvcRedisplay requests."! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
pasteRecent
	"Paste an item chose from RecentClippings."

	| clipping |
	(clipping _ Clipboard chooseRecentClipping) ifNil: [^ self].
	Clipboard clipboardText: clipping.
	^ self paste! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
presentSpecialMenu
	"Present a list of expressions, and if the user chooses one, evaluate it in the context of the receiver, a ParagraphEditor.  Primarily for debugging, this provides a convenient way to talk to the various views, controllers, and models associated with any text pane"

	| reply items |
	reply _ (PopUpMenu labelArray: (items _ self specialMenuItems) lines: #()) startUp.
	reply = 0 ifTrue: [^ self].
	Compiler new evaluate: (items at: reply) in: [] to: self! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
prettyPrint
	self prettyPrint: false! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
prettyPrint: decorated
	"Reformat the contents of the receiver's view (a Browser)."

	| selectedClass newText |
	model selectedMessageCategoryName ifNil: [^ morph flash].
	selectedClass _ model selectedClassOrMetaClass.
	newText _ selectedClass compilerClass new
		format: self text
		in: selectedClass
		notifying: self
		decorated: decorated.
	newText ifNotNil:
		[self deselect; selectInvisiblyFrom: 1 to: paragraph text size.
		self replaceSelectionWith: (newText asText makeSelectorBoldIn: selectedClass).
		self selectAt: 1]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
prettyPrintWithColor
	self prettyPrint: true! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
printerSetup
	
	TextPrinter defaultTextPrinter inspect! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
referencesToIt
	"Open a references browser on the selected symbol"

	| aSymbol |
	self selectLine.
	((aSymbol _ self selectedSymbol) == nil or:
		[(Smalltalk includesKey: aSymbol) not])
			ifTrue: [^ morph flash].

	Smalltalk browseAllCallsOn: (Smalltalk associationAt: self selectedSymbol)! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
saveContentsInFile
	"Save the receiver's contents string to a file, prompting the user for a file-name.  Suggest a reasonable file-name."

	| fileName stringToSave parentWindow labelToUse suggestedName lastIndex |
	stringToSave := paragraph text string.
	stringToSave size == 0 ifTrue: [^self inform: 'nothing to save.'].
	parentWindow := self model dependents 
				detect: [:dep | dep isKindOf: OldSystemWindow]
				ifNone: [nil].
	labelToUse := parentWindow ifNil: ['Untitled']
				ifNotNil: [parentWindow label].
	suggestedName := nil.
	#(#('Decompressed contents of: ' '.gz')) do: 
			[:leaderTrailer | 
			"can add more here..."

			(labelToUse beginsWith: leaderTrailer first) 
				ifTrue: 
					[suggestedName := labelToUse copyFrom: leaderTrailer first size + 1
								to: labelToUse size.
					(labelToUse endsWith: leaderTrailer last) 
						ifTrue: 
							[suggestedName := suggestedName copyFrom: 1
										to: suggestedName size - leaderTrailer last size]
						ifFalse: 
							[lastIndex := suggestedName lastIndexOf: $. ifAbsent: [0].
							(lastIndex = 0 or: [lastIndex = 1]) 
								ifFalse: [suggestedName := suggestedName copyFrom: 1 to: lastIndex - 1]]]].
	suggestedName ifNil: [suggestedName := labelToUse , '.text'].
	fileName := FillInTheBlank request: 'File name?'
				initialAnswer: suggestedName.
	fileName isEmptyOrNil 
		ifFalse: 
			[(FileStream newFileNamed: fileName)
				nextPutAll: stringToSave;
				close]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
selectedSelector
	"Try to make a selector out of the current text selection"
	^self selection string findSelector! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
selectedSymbol
	"Return the currently selected symbol, or nil if none.  Spaces, tabs and returns are ignored"

	| aString |
	self hasCaret ifTrue: [^ nil].
	aString _ self selection string copyWithoutAll:
		{Character space.  Character cr.  Character tab}.
	aString size == 0 ifTrue: [^ nil].
	Symbol hasInterned: aString  ifTrue: [:sym | ^ sym].

	^ nil! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
sendContentsToPrinter
	| textToPrint printer parentWindow |
	textToPrint := paragraph text.
	textToPrint size == 0 ifTrue: [^self inform: 'nothing to print.'].
	printer := TextPrinter defaultTextPrinter.
	parentWindow := self model dependents 
				detect: [:dep | dep isSystemWindow]
				ifNone: [nil].
	parentWindow isNil 
		ifTrue: [printer documentTitle: 'Untitled']
		ifFalse: [printer documentTitle: parentWindow label].
	printer printText: textToPrint! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
sendersOfIt
	"Open a senders browser on the selected selector"

	| aSelector |
	self lineSelectAndEmptyCheck: [^ self].
	(aSelector _ self selectedSelector) == nil ifTrue: [^ morph flash].
	Smalltalk browseAllCallsOn: aSelector! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
setAlignment: aSymbol
	| attr interval |
	attr _ TextAlignment perform: aSymbol.
	interval _ self encompassLine: self selectionInterval.
	paragraph replaceFrom: interval first to: interval last with:
		((paragraph text copyFrom: interval first to: interval last) addAttribute: attr) displaying: true.
! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
setSearchString
	"Make the current selection, if any, be the current search string."
	self hasCaret ifTrue: [morph flash. ^ self].
	self setSearch:  self selection string! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
spawn
	"Create and schedule a message browser for the code of the model's 
	selected message. Retain any edits that have not yet been accepted."
	| code |
	code _ paragraph text string.
	self cancel.
	model spawn: code.! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
undo
	"Reset the state of the paragraph prior to the previous edit.
	 If another ParagraphEditor instance did that edit, UndoInterval is invalid;
	 just recover the contents of the undo-buffer at the start of the paragraph."

	sensor flushKeyboard. "a way to flush stuck keys"
	self closeTypeIn.

	UndoParagraph == paragraph ifFalse: "Can't undo another paragraph's edit"
		[UndoMessage _ Message selector: #undoReplace.
		UndoInterval _ 1 to: 0.
		Undone _ true].
	UndoInterval ~= self selectionInterval ifTrue: "blink the actual target"
		[self selectInterval: UndoInterval; deselect].

	"Leave a signal of which phase is in progress"
	UndoParagraph _ Undone ifTrue: [#redoing] ifFalse: [#undoing].
	UndoMessage sentTo: self.
	UndoParagraph _ paragraph! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 11/4/2008 11:51'!
yellowButtonActivity
	"This normally opens a popup menu. Determine the selected
	item and, if one is selected, then send the corresponding message
	to either the model or the receiver."

	^ self pluggableYellowButtonActivity: sensor leftShiftDown! !

!TextEditor methodsFor: 'model access' stamp: 'jmv 11/4/2008 11:51'!
model: aModel 
	"Controller|model: and Controller|view: are sent by View|controller: in 
	order to coordinate the links between the model, view, and controller. In 
	ordinary usage, the receiver is created and passed as the parameter to 
	View|controller: so that the receiver's model and view links can be set 
	up by the view."

	model _ aModel! !

!TextEditor methodsFor: 'mvc compatibility' stamp: 'jmv 11/4/2008 11:51'!
storeSelectionInParagraph
	paragraph selectionStart: self startBlock selectionStop: self stopBlock! !

!TextEditor methodsFor: 'mvc compatibility' stamp: 'jmv 11/4/2008 14:30'!
zapSelectionWith: aText
	"**overridden to inhibit old-style display
	jmv- when reimplementing, take a look (and understand) the old implementation at ParagraphEditor"

	| start stop |
	self deselect.
	start _ self startIndex.
	stop _ self stopIndex.
	(aText isEmpty and: [stop > start]) ifTrue:
		["If deleting, then set emphasisHere from 1st character of the deletion"
		emphasisHere _ (paragraph text attributesAt: start forStyle: paragraph textStyle)
					select: [:att | att mayBeExtended]].
	(start = stop and: [aText size = 0]) ifFalse:
		[paragraph replaceFrom: start to: stop - 1
			with: aText displaying: false.  "** was true in super"
		self markIndex: start; pointIndex: start + aText size.
		UndoInterval _ otherInterval _ self selectionInterval].

	self userHasEdited  " -- note text now dirty"! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
afterSelectionInsertAndSelect: aString

	self insertAndSelect: aString at: self stopIndex ! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
correctFrom: start to: stop with: aString
	"Make a correction in the model that the user has authorised from somewhere else in the system (such as from the compilier).  The user's selection is not changed, only corrected."
	| wasShowing userSelection delta loc |
	aString = '#insert period' ifTrue:
		[loc _ start.
		[(loc _ loc-1)>0 and: [(paragraph text string at: loc) isSeparator]]
			whileTrue: [loc _ loc-1].
		^ self correctFrom: loc+1 to: loc with: '.'].
	(wasShowing _ selectionShowing) ifTrue: [ self reverseSelection ].
	userSelection _ self selectionInterval.

	self selectInvisiblyFrom: start to: stop.
	self replaceSelectionWith: aString asText.

	delta _ aString size - (stop - start + 1).
	self selectInvisiblyFrom:
		userSelection first + (userSelection first > start ifFalse: [ 0 ] ifTrue: [ delta ])
		to: userSelection last + (userSelection last > start ifFalse: [ 0 ] ifTrue: [ delta ]).
	wasShowing ifTrue: [ self reverseSelection ].
! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
encompassLine: anInterval
	"Return an interval that encompasses the entire line"
	| string left right |
	string _ paragraph text string.
	left _ (string lastIndexOf: Character cr startingAt: anInterval first - 1 ifAbsent:[0]) + 1.
	right _ (string indexOf: Character cr startingAt: anInterval last + 1 ifAbsent: [string size + 1]) - 1.
	^left to: right! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
insertAndSelect: aString at: anInteger

	self replace: (anInteger to: anInteger - 1)
		with: (Text string: (' ' , aString)
					attributes: emphasisHere)
		and: [self ]! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
selectFrom: start to: stop
	"Select the specified characters inclusive."
	self selectInvisiblyFrom: start to: stop.
	self closeTypeIn.
	self storeSelectionInParagraph.
	self setEmphasisHere.
! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
selectLine
	"Make the receiver's selection, if it currently consists of an insertion point only, encompass the current line."
	self hasSelection ifTrue:[^self].
	self selectInterval: (self encompassLine: self selectionInterval)! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 11/4/2008 11:51'!
selectPrecedingIdentifier
	"Invisibly select the identifier that ends at the end of the selection, if any."

	| string sep stop tok |
	tok _ false.
	string _ paragraph text string.
	stop _ self stopIndex - 1.
	[stop > 0 and: [(string at: stop) isSeparator]] whileTrue: [stop _ stop - 1].
	sep _ stop.
	[sep > 0 and: [(string at: sep) tokenish]] whileTrue: [tok _ true. sep _ sep - 1].
	tok ifTrue: [self selectInvisiblyFrom: sep + 1 to: stop]! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
cursorDown: characterStream 

	"Private - Move cursor from position in current line to same position in
	next line. If next line too short, put at end. If shift key down,
	select."
	self closeTypeIn: characterStream.
	self 
		moveCursor:[:position | self
				sameColumn: position
				newLine:[:line | line + 1]
				forward: true]
		forward: true
		specialBlock:[:dummy | dummy].
	^true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
cursorEnd: characterStream 

	"Private - Move cursor end of current line."
	| string |
	self closeTypeIn: characterStream.
	string _ paragraph text string.
	self
		moveCursor:
			[:position | Preferences wordStyleCursorMovement
				ifTrue:[| targetLine |
					targetLine _ paragraph lines at:(paragraph lineIndexOfCharacterIndex: position).
					targetLine = paragraph lastLine
						ifTrue:[targetLine last + 1]
						ifFalse:[targetLine last]]
				ifFalse:[
					string
						indexOf: Character cr
						startingAt: position
						ifAbsent:[string size + 1]]]
		forward: true
		specialBlock:[:dummy | string size + 1].
	^true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
cursorHome: characterStream 

	"Private - Move cursor from position in current line to beginning of
	current line. If control key is pressed put cursor at beginning of text"

	| string |

	string _ paragraph text string.
	self
		moveCursor: [ :position | Preferences wordStyleCursorMovement
				ifTrue:[
					(paragraph lines at:(paragraph lineIndexOfCharacterIndex: position)) first]
				ifFalse:[
					(string
						lastIndexOf: Character cr
						startingAt: position - 1
						ifAbsent:[0]) + 1]]
		forward: false
		specialBlock: [:dummy | 1].
	^true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
escapeToDesktop: characterStream 
	"Pop up a morph to field keyboard input in the context of the desktop"

	ActiveWorld putUpWorldMenuFromEscapeKey.
	^ true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
raiseContextMenu: characterStream 
	(morph respondsTo: #editView)
		ifTrue: [morph editView yellowButtonActivity: ActiveEvent shiftPressed].
	^ true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
selectCurrentTypeIn: characterStream 
	"Select what would be replaced by an undo (e.g., the last typeIn)."

	| prior |

	self closeTypeIn: characterStream.
	prior _ otherInterval.
	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self selectInterval: UndoInterval.
	otherInterval _ prior.
	^ true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 11/4/2008 11:51'!
setSearchString: characterStream
	"Establish the current selection as the current search string."

	| aString |
	self closeTypeIn: characterStream.
	sensor keyboard.
	self lineSelectAndEmptyCheck: [^ true].
	aString _  self selection string.
	aString size == 0
		ifTrue:
			[self flash]
		ifFalse:
			[self setSearch: aString].
	^ true! !

!TextEditor methodsFor: 'parenblinking' stamp: 'jmv 11/4/2008 11:51'!
blinkParenAt: parenLocation 
	self text
		addAttribute: TextEmphasis bold
		from: parenLocation
		to: parenLocation.
	lastParenLocation _ parenLocation.! !

!TextEditor methodsFor: 'parenblinking' stamp: 'jmv 11/4/2008 13:50'!
blinkPrevParen
	| openDelimiter closeDelimiter level string here hereChar |
	string _ paragraph text string.
	here _ pointBlock stringIndex.
	openDelimiter _ sensor keyboardPeek.
	closeDelimiter _ '([{' at: (')]}' indexOf: openDelimiter).
	level _ 1.
	[level > 0 and: [here > 2]]
		whileTrue:
			[hereChar _ string at: (here _ here - 1).
			hereChar = closeDelimiter
				ifTrue:
					[level _ level - 1.
					level = 0
						ifTrue: [^ self blinkParenAt: here]]
				ifFalse:
					[hereChar = openDelimiter
						ifTrue: [level _ level + 1]]]! !

!TextEditor methodsFor: 'parenblinking' stamp: 'jmv 11/4/2008 11:51'!
clearParens
	lastParenLocation ifNotNil:
		[self text string size >= lastParenLocation ifTrue: [
			self text
				removeAttribute: TextEmphasis bold
				from: lastParenLocation
				to: lastParenLocation]]! !

!TextEditor methodsFor: 'pluggable menus' stamp: 'jmv 11/4/2008 11:51'!
pluggableYellowButtonActivity: shiftKeyState
	"Invoke the model's popup menu."

	| menu |
	(menu _ self getPluggableYellowButtonMenu: shiftKeyState)
		ifNil:
			[sensor waitNoButton]
		ifNotNil:
			[menu invokeOn: model orSendTo: self]! !

!TextEditor methodsFor: 'pluggable menus' stamp: 'jmv 11/4/2008 11:51'!
shiftedTextPaneMenuRequest
	"The user chose the more... branch from the text-pane menu."

	^ self pluggableYellowButtonActivity: true! !

!TextEditor methodsFor: 'scrolling' stamp: 'jmv 11/4/2008 11:51'!
scrollBy: ignore 
	"Ignore scroll requests."! !

!TextEditor methodsFor: 'scrolling' stamp: 'jmv 11/4/2008 11:51'!
updateMarker
	"Ignore scrollbar redraw requests."
! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 13:59'!
backTo: startIndex
	"During typing, backspace to startIndex.  Deleted characters fall into three
	 clusters, from left to right in the text: (1) preexisting characters that were
	 backed over; (2) newly typed characters that were backed over (excluding
	 typeahead, which never even appears); (3) preexisting characters that
	 were highlighted before typing began.  If typing has not yet been opened,
	 open it and watch for the first and third cluster.  If typing has been opened,
	 watch for the first and second cluster.  Save characters from the first and third
	 cluster in UndoSelection.  Tally characters from the first cluster in UndoMessage's parameter.
	 Delete all the clusters.  Do not alter Undoer or UndoInterval (except via
	 openTypeIn).  The code is shorter than the comment."

	| saveLimit newBackovers |
	saveLimit _ beginTypeInBlock
		ifNil: [self openTypeIn. UndoSelection _ self nullText. self stopIndex]
		ifNotNil: [self startOfTyping].
	self markIndex: startIndex.
	startIndex < saveLimit ifTrue:
		[newBackovers _ self startOfTyping - startIndex.
		beginTypeInBlock _ self startIndex.
		UndoSelection replaceFrom: 1 to: 0 with:
			(paragraph text copyFrom: startIndex to: saveLimit - 1).
		UndoMessage argument: (UndoMessage argument ifNil: [1]) + newBackovers].
	self zapSelectionWith: self nullText.
	self unselect! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
closeTypeIn
	"See comment in openTypeIn.  It is important to call closeTypeIn before executing
	 any non-typing key, making a new selection, etc.  It is called automatically for
	 menu commands.
	 Typing commands can call 'closeTypeIn: aCharacterStream' instead of this to
	 save typeahead.  Undoer & Redoer: undoAndReselect:redoAndReselect:."

	| begin stop |
	beginTypeInBlock == nil ifFalse:
		[(UndoMessage sends: #noUndoer) ifTrue: "should always be true, but just in case..."
			[begin _ self startOfTyping.
			stop _ self stopIndex.
			self undoer: #undoAndReselect:redoAndReselect:
				with: (begin + UndoMessage argument to: begin + UndoSelection size - 1)
				with: (stop to: stop - 1).
			UndoInterval _ begin to: stop - 1].
		beginTypeInBlock _ nil]! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
dispatchOnCharacter: char with: typeAheadStream
	"Carry out the action associated with this character, if any.
	Type-ahead is passed so some routines can flush or use it."

	| honorCommandKeys |
	((char == Character cr) and: [morph acceptOnCR])
		ifTrue:
			[sensor keyboard.  "Gobble cr -- probably unnecessary."
			self closeTypeIn.
			^ true].

	self clearParens.
  
	char asciiValue = 13 ifTrue: [
		^ sensor controlKeyPressed
			ifTrue: [self normalCharacter: typeAheadStream]
			ifFalse: [self crWithIndent: typeAheadStream]].

	((honorCommandKeys _ Preferences cmdKeysInText) and: [char = Character enter])
		ifTrue: [^ self dispatchOnEnterWith: typeAheadStream].

	"Special keys overwrite crtl+key combinations - at least on Windows. To resolve this
	conflict, assume that keys other than cursor keys aren't used together with Crtl." 
	((self class specialShiftCmdKeys includes: char asciiValue) and: [char asciiValue < 27])
		ifTrue: [^ sensor controlKeyPressed
			ifTrue: [self perform: (ShiftCmdActions at: char asciiValue + 1) with: typeAheadStream]
			ifFalse: [self perform: (CmdActions at: char asciiValue + 1) with: typeAheadStream]].

	"backspace, and escape keys (ascii 8 and 27) are command keys"
	((honorCommandKeys and: [sensor commandKeyPressed]) or: [self class specialShiftCmdKeys includes: char asciiValue]) ifTrue:
		[^ sensor leftShiftDown
			ifTrue:
				[self perform: (ShiftCmdActions at: char asciiValue + 1) with: typeAheadStream]
			ifFalse:
				[self perform: (CmdActions at: char asciiValue + 1) with: typeAheadStream]].

	"the control key can be used to invoke shift-cmd shortcuts"
	(honorCommandKeys and: [sensor controlKeyPressed])
		ifTrue:
			[^ self perform: (ShiftCmdActions at: char asciiValue + 1) with: typeAheadStream].

	(')]}' includes: char)
		ifTrue: [self blinkPrevParen].

	^ self perform: #normalCharacter: with: typeAheadStream! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
dispatchOnEnterWith: typeAheadStream
	"Enter key hit.  Treat is as an 'accept', viz a synonym for cmd-s.  If cmd key is down, treat is as a synonym for print-it. "

	sensor keyboard.  "consume enter key"
	sensor commandKeyPressed
		ifTrue:
			[self printIt.]
		ifFalse: 
			[self closeTypeIn: typeAheadStream.
			self accept].
	^ true! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
doneTyping
	beginTypeInBlock _ nil! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
insertTypeAhead: typeAhead
	typeAhead position = 0 ifFalse:
		[self zapSelectionWith: (Text string: typeAhead contents emphasis: emphasisHere).
		typeAhead reset.
		self unselect]! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
openTypeIn
	"Set up UndoSelection to null text (to be added to by readKeyboard and backTo:),
	 beginTypeInBlock to keep track of the leftmost backspace, and UndoParameter to tally
	 how many deleted characters were backspaced over rather than 'cut'.
	 You can't undo typing until after closeTypeIn."

	beginTypeInBlock == nil ifTrue:
		[UndoSelection _ self nullText.
		self undoer: #noUndoer with: 0.
		beginTypeInBlock _ self startIndex]! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
readKeyboard
	"Key struck on the keyboard. Find out which one and, if special, carry 
	out the associated special action. Otherwise, add the character to the 
	stream of characters.  Undoer & Redoer: see closeTypeIn."

	| typeAhead char |
	typeAhead _ WriteStream on: (String new: 128).
	[sensor keyboardPressed] whileTrue: [
		self deselect.
		[sensor keyboardPressed] whileTrue: 
			[char _ sensor keyboardPeek.
			(self dispatchOnCharacter: char with: typeAhead) ifTrue: [
				self doneTyping.
				self setEmphasisHere.
				self storeSelectionInParagraph.
				^self].
			self openTypeIn].
		self hasSelection ifTrue: "save highlighted characters"
			[UndoSelection _ self selection]. 
		self zapSelectionWith: 
			(Text string: typeAhead contents emphasis: emphasisHere).
		typeAhead reset.
		self unselect].
	self storeSelectionInParagraph! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
setEmphasisHere

	emphasisHere _ (paragraph text attributesAt: (self pointIndex - 1 max: 1) forStyle: paragraph textStyle)
					select: [:att | att mayBeExtended]! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 11/4/2008 11:51'!
startOfTyping
	"Compatibility during change from characterBlock to integer"
	beginTypeInBlock == nil ifTrue: [^ nil].
	beginTypeInBlock isNumber ifTrue: [^ beginTypeInBlock].
	"Last line for compatibility during change from CharacterBlock to Integer."
	^ beginTypeInBlock stringIndex! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
argAdvance: characterStream
	"Invoked by Ctrl-a.  Useful after Ctrl-q.
	 Search forward from the end of the selection for a colon followed by
		a space.  Place the caret after the space.  If none are found, place the
		caret at the end of the text.  Does not affect the undoability of the 
	 	previous command."

	| start |
	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	start _ paragraph text findString: ': ' startingAt: self stopIndex.
	start = 0 ifTrue: [start _ paragraph text size + 1].
	self selectAt: start + 2.
	^true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
changeStyle: characterStream 
	"Put up the style-change menu"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self changeStyle.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
crWithIndent: characterStream 
	"Replace the current text selection with CR followed by as many tabs
	as on the current line (+/- bracket count) -- initiated by Shift-Return."
	| char s i tabCount |
	sensor keyboard.		"flush character"
	s _ paragraph string.
	i _ self stopIndex.
	tabCount _ 0.
	[(i _ i-1) > 0 and: [(char _ s at: i) ~= Character cr]]
		whileTrue:  "Count tabs and brackets (but not a leading bracket)"
		[(char = Character tab and: [i < s size and: [(s at: i+1) ~= $[ ]]) ifTrue: [tabCount _ tabCount + 1].
		char = $[ ifTrue: [tabCount _ tabCount + 1].
		char = $] ifTrue: [tabCount _ tabCount - 1]].
	characterStream crtab: tabCount.  "Now inject CR with tabCount tabs"
	^ false! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
displayIfFalse: characterStream 
	"Replace the current text selection with the text 'ifFalse:'--initiated by 
	ctrl-f."

	sensor keyboard.		"flush character"
	characterStream nextPutAll: 'ifFalse:'.
	^false! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
displayIfTrue: characterStream 
	"Replace the current text selection with the text 'ifTrue:'--initiated by 
	ctrl-t."

	sensor keyboard.		"flush character"
	characterStream nextPutAll: 'ifTrue:'.
	^false! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
doAgainMany: characterStream 
	"Do the previous thing again repeatedly. 1/26/96 sw"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self againOrSame: (UndoMessage sends: #undoAgain:andReselect:typedKey:) many: true.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
doAgainOnce: characterStream 
	"Do the previous thing again once. 1/26/96 sw"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self again.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
find: characterStream
	"Prompt the user for what to find, then find it, searching from the current selection onward.  1/24/96 sw"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self find.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
findAgain: characterStream 
	"Find the desired text again.  1/24/96 sw"

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self findAgain.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 13:56'!
forwardDelete: characterStream
	"Delete forward over the next character.
	  Make Undo work on the whole type-in, not just the one char.
	wod 11/3/1998: If there was a selection use #zapSelectionWith: rather than #backspace: which was 'one off' in deleting the selection. Handling of things like undo or typeIn area were not fully considered."
	| startIndex usel upara uinterval ind stopIndex |
	startIndex _ self markIndex.
	startIndex > paragraph text size ifTrue:
		[sensor keyboard.
		^ false].
	self hasSelection ifTrue:
		["there was a selection"
		sensor keyboard.
		self zapSelectionWith: self nullText.
		^ false].
	"Null selection - do the delete forward"
	beginTypeInBlock == nil	"no previous typing.  openTypeIn"
		ifTrue: [self openTypeIn. UndoSelection _ self nullText].
	uinterval _ UndoInterval deepCopy.
	upara _ UndoParagraph deepCopy.
	stopIndex := startIndex.
	(sensor keyboard asciiValue = 127 and: [sensor leftShiftDown])
		ifTrue: [stopIndex := (self nextWord: stopIndex) - 1].
	self selectFrom: startIndex to: stopIndex.
	self replaceSelectionWith: self nullText.
	self selectFrom: startIndex to: startIndex-1.
	UndoParagraph _ upara.  UndoInterval _ uinterval.
	UndoMessage selector == #noUndoer ifTrue: [
		(UndoSelection isText) ifTrue: [
			usel _ UndoSelection.
			ind _ startIndex. "UndoInterval startIndex"
			usel replaceFrom: usel size + 1 to: usel size with:
				(UndoParagraph text copyFrom: ind to: ind).
			UndoParagraph text replaceFrom: ind to: ind with:
self nullText]].
	^false! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
querySymbol: characterStream
	"Invoked by Ctrl-q to query the Symbol table and display alternate symbols.
	 See comment in completeSymbol:lastOffering: for details."

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.	"keep typeahead"
	self hasCaret
		ifTrue: "Ctrl-q typed when a caret"
			[self perform: #completeSymbol:lastOffering: withArguments:
				((UndoParagraph == paragraph and: [UndoMessage sends: #undoQuery:lastOffering:])
					ifTrue: [UndoMessage arguments] "repeated Ctrl-q"
					ifFalse: [Array with: nil with: nil])] "initial Ctrl-q"
		ifFalse: "Ctrl-q typed when statements were highlighted"
			[morph flash].
	^true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 11/4/2008 11:51'!
search: characterStream
	"Invoked by Ctrl-S.  Same as 'again', but always uses the existing FindText
	 and ChangeText regardless of the last edit."

	sensor keyboard.		"flush character"
	self closeTypeIn: characterStream.
	self againOrSame: true. "true means use same keys"
	^true! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
isDoing
	"Call from a doer/undoer/redoer any time to see which it is."

	^(self isUndoing | self isRedoing) not! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
isRedoing
	"Call from a doer/undoer/redoer any time to see which it is."

	^UndoParagraph == #redoing! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
isUndoing
	"Call from a doer/undoer/redoer any time to see which it is."

	^UndoParagraph == #undoing! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
noUndoer
	"The Undoer to use when the command can not be undone.  Checked for
	 specially by readKeyboard."

	UndoMessage _ Message selector: #noUndoer! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
undoMessage: aMessage forRedo: aBoolean
	"Call this from an undoer/redoer to set up UndoMessage as the
	 corresponding redoer/undoer.  Also set up UndoParagraph, as well
	 as the state variable Undone.  It is assumed that UndoInterval has been
	 established (generally by zapSelectionWith:) and that UndoSelection has been
	 saved (generally by replaceSelectionWith: or replace:With:and:)."

	self isDoing ifTrue: [UndoParagraph _ paragraph].
	UndoMessage _ aMessage.
	Undone _ aBoolean! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
undoer: aSelector
	"See comment in undoMessage:.  Use this version when aSelector has no arguments, and you are doing or redoing and want to prepare for undoing."

	self undoMessage: (Message selector: aSelector) forRedo: false! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
undoer: aSelector with: arg1
	"See comment in undoMessage:.  Use this version when aSelector has one argument, and you are doing or redoing and want to prepare for undoing."

	self undoMessage: (Message selector: aSelector argument: arg1) forRedo: false! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
undoer: aSelector with: arg1 with: arg2
	"See comment in undoMessage:.  Use this version when aSelector has two arguments, and you are doing or redoing and want to prepare for undoing."

	self undoMessage: (Message selector: aSelector arguments: (Array with: arg1 with: arg2)) forRedo: false! !

!TextEditor methodsFor: 'undo support' stamp: 'jmv 11/4/2008 11:51'!
undoer: aSelector with: arg1 with: arg2 with: arg3
	"See comment in undoMessage:.  Use this version when aSelector has three arguments, and you are doing or redoing and want to prepare for undoing."

	self undoMessage: (Message selector: aSelector arguments: (Array with: arg1 with: arg2 with: arg3)) forRedo: false! !

!TextEditor methodsFor: 'undoers' stamp: 'jmv 11/4/2008 11:51'!
undoAgain: indices andReselect: home typedKey: wasTypedKey
	"The last command was again.  Undo it. Redoer: itself."

	| findSize substText index subject |
	(self isRedoing & wasTypedKey) ifTrue: "redelete search key"
		[self selectInterval: home.
		self zapSelectionWith: self nullText].

	findSize _ (self isRedoing ifTrue: [FindText] ifFalse: [ChangeText]) size.
	substText _ self isUndoing ifTrue: [FindText] ifFalse: [ChangeText].
	(self isUndoing ifTrue: [indices size to: 1 by: -1] ifFalse: [1 to: indices size]) do:
		[:i |
		index _ indices at: i.
		(subject _ index to: index + findSize - 1) = self selectionInterval ifFalse:
			[self selectInterval: subject].
		FindText == ChangeText ifFalse: [self zapSelectionWith: substText]].

	self isUndoing
		ifTrue:  "restore selection to where it was when 'again' was invoked"
			[wasTypedKey
				ifTrue: "search started by typing key at a caret; restore it"
					[self selectAt: home first.
					self zapSelectionWith: FindText.
					self selectAt: home last + 1]
				ifFalse: [self selectInterval: home]].

	self undoMessage: UndoMessage forRedo: self isUndoing! !

!TextEditor methodsFor: 'undoers' stamp: 'jmv 11/4/2008 11:51'!
undoAndReselect: undoHighlight redoAndReselect: redoHighlight
	"Undo typing, cancel, paste, and other operations that are like replaces
	 but the selection is not the whole restored text after undo, redo, or both.
	 undoHighlight is selected after this phase and redoHighlight after the next phase.
	Redoer: itself."

	self replace: self selectionInterval with: UndoSelection and:
		[self selectInterval: undoHighlight].
	self undoMessage: (UndoMessage argument: redoHighlight) forRedo: self isUndoing
! !

!TextEditor methodsFor: 'undoers' stamp: 'jmv 11/4/2008 11:51'!
undoCutCopy: oldPasteBuffer
	"Undo of a cut, copy, or any edit that changed CurrentSelection.  Be sure
	 undo-copy does not lock the model.  Redoer: itself, so never isRedoing."

	| recentCut |
	recentCut _ self clipboardText.	
	UndoSelection size = UndoInterval size
		ifFalse: [self replaceSelectionWith: UndoSelection].
	self clipboardTextPut: oldPasteBuffer.
	self undoer: #undoCutCopy: with: recentCut! !

!TextEditor methodsFor: 'undoers' stamp: 'jmv 11/4/2008 11:51'!
undoQuery: hintText lastOffering: selectorOrNil
	"Undo ctrl-q.  selectorOrNil (if not nil) is the previously offered selector.
	 hintText is the original hint.  Redoer: completeSymbol."

	self zapSelectionWith: UndoSelection.
	self undoMessage: (Message selector: #completeSymbol:lastOffering: arguments: UndoMessage arguments) forRedo: true.
	self selectAt: self stopIndex! !

!TextEditor methodsFor: 'undoers' stamp: 'jmv 11/4/2008 11:51'!
undoReplace
	"Undo of any command that replaced a selection by other text that it left
	 highlighted, and that is undone and redone by simple reversal of the
	 operation.  This is the most common Undoer; call replaceSelectionWith:
	 to get this setup.  Redoer: itself, so never isRedoing."

	self replaceSelectionWith: UndoSelection! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
againOnce: indices
	"Find the next occurrence of FindText.  If none, answer false.
	Append the start index of the occurrence to the stream indices, and, if
	ChangeText is not the same object as FindText, replace the occurrence by it.
	Note that the search is case-sensitive for replacements, otherwise not."

	| where |
	where _ paragraph text findString: FindText startingAt: self stopIndex
				caseSensitive: ((ChangeText ~~ FindText) or: [Preferences caseSensitiveFinds]).
	where = 0 ifTrue: [^ false].
	self deselect; selectInvisiblyFrom: where to: where + FindText size - 1.
	ChangeText ~~ FindText ifTrue: [self zapSelectionWith: ChangeText].
	indices nextPut: where.
	^ true! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
againOrSame: useOldKeys
	"Subroutine of search: and again.  If useOldKeys, use same FindText and ChangeText as before.
	 1/26/96 sw: real worked moved to againOrSame:many:"

	self againOrSame: useOldKeys many: sensor leftShiftDown.

	(morph respondsTo: #editView) 
		ifTrue: [morph editView selectionInterval: self selectionInterval]! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
againOrSame: useOldKeys many: many
	"Subroutine of search: and again.  If useOldKeys, use same FindText and ChangeText as before.  If many is true, do it repeatedly.  Created 1/26/96 sw by adding the many argument to #againOrSame."

	|  home indices wasTypedKey |

	home _ self selectionInterval.  "what was selected when 'again' was invoked"

	"If new keys are to be picked..."
	useOldKeys ifFalse: "Choose as FindText..."
		[FindText _ UndoSelection.  "... the last thing replaced."
		"If the last command was in another paragraph, ChangeText is set..."
		paragraph == UndoParagraph ifTrue: "... else set it now as follows."
			[UndoInterval ~= home ifTrue: [self selectInterval: UndoInterval]. "blink"
			ChangeText _ ((UndoMessage sends: #undoCutCopy:) and: [self hasSelection])
				ifTrue: [FindText] "== objects signal no model-locking by 'undo copy'"
				ifFalse: [self selection]]]. "otherwise, change text is last-replaced text"

	(wasTypedKey _ FindText size = 0)
		ifTrue: "just inserted at a caret"
			[home _ self selectionInterval.
			self replaceSelectionWith: self nullText.  "delete search key..."
			FindText _ ChangeText] "... and search for it, without replacing"
		ifFalse: "Show where the search will start"
			[home last = self selectionInterval last ifFalse:
				[self selectInterval: home]].

	"Find and Change, recording start indices in the array"
	indices _ WriteStream on: (Array new: 20). "an array to store change locs"
	[(self againOnce: indices) & many] whileTrue. "<-- this does the work"
	indices isEmpty ifTrue:  "none found"
		[self flash.
		wasTypedKey ifFalse: [^self]].

	(many | wasTypedKey) ifFalse: "after undo, select this replacement"
		[home _ self startIndex to:
			self startIndex + UndoSelection size - 1].

	self undoer: #undoAgain:andReselect:typedKey: with: indices contents with: home with: wasTypedKey! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
completeSymbol: hintText lastOffering: selectorOrNil
	"Invoked by Ctrl-q when there is only a caret.
		Do selector-completion, i.e., try to replace the preceding identifier by a
		selector that begins with those characters & has as many keywords as possible.
	 	Leave two spaces after each colon (only one after the last) as space for
		arguments.  Put the caret after the space after the first keyword.  If the
		user types Ctrl-q again immediately, choose a different selector.
	 Undoer: #undoQuery:lastOffering:; Redoer: itself.
	If redoing, just redisplay the last offering, selector[OrNil]."

	| firstTime input prior caret newStart sym kwds outStream |
	firstTime _ self isRedoing
		ifTrue: [prior _ sym _ selectorOrNil. true]
		ifFalse: [hintText isNil].
	firstTime
		ifTrue: "Initial Ctrl-q (or redo)"					
			[caret _ self startIndex.
			self selectPrecedingIdentifier.
			input _ self selection]
		ifFalse: "Repeated Ctrl-q"
			[caret _ UndoInterval first + hintText size.
			self selectInvisiblyFrom: UndoInterval first to: UndoInterval last.
			input _ hintText.
			prior _ selectorOrNil].
	(input size ~= 0 and: [sym ~~ nil or:
			[(sym _ Symbol thatStarts: input string skipping: prior) ~~ nil]])
		ifTrue: "found something to offer"
			[newStart _ self startIndex.
			outStream _ WriteStream on: (String new: 2 * sym size).
			1 to: (kwds _ sym keywords) size do:
				[:i |
				outStream nextPutAll: (kwds at: i).
				i = 1 ifTrue: [caret _ newStart + outStream contents size + 1].
				outStream nextPutAll:
					(i < kwds size ifTrue: ['  '] ifFalse: [' '])].
			UndoSelection _ input.
			self deselect; zapSelectionWith: outStream contents asText.
			self undoer: #undoQuery:lastOffering: with: input with: sym]
		ifFalse: "no more matches"
			[firstTime ifFalse: "restore original text & set up for a redo"
				[UndoSelection _ self selection.
				self deselect; zapSelectionWith: input.
				self undoer: #completeSymbol:lastOffering: with: input with: prior.
				Undone _ true].
			morph flash].
	self selectAt: caret! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
exchangeWith: prior
	"If the prior selection is non-overlapping and legal, exchange the text of
	 it with the current selection and leave the currently selected text selected
	 in the location of the prior selection (or leave a caret after a non-caret if it was
	 exchanged with a caret).  If both selections are carets, flash & do nothing.
	 Don't affect the paste buffer.  Undoer: itself; Redoer: Undoer."

	| start stop before selection priorSelection delta altInterval |
	start _ self startIndex.
	stop _ self stopIndex - 1.
	((prior first <= prior last) | (start <= stop) "Something to exchange" and:
			[self isDisjointFrom: prior])
		ifTrue:
			[before _ prior last < start.
			selection _ self selection.
			priorSelection _ paragraph text copyFrom: prior first to: prior last.

			delta _ before ifTrue: [0] ifFalse: [priorSelection size - selection size].
			self zapSelectionWith: priorSelection.
			self selectFrom: prior first + delta to: prior last + delta.

			delta _ before ifTrue: [stop - prior last] ifFalse: [start - prior first].
			self zapSelectionWith: selection.
			altInterval _ prior first + delta to: prior last + delta.
			self undoer: #exchangeWith: with: altInterval.
			"If one was a caret, make it otherInterval & leave the caret after the other"
			prior first > prior last ifTrue: [self selectAt: UndoInterval last + 1].
			otherInterval _ start > stop
				ifTrue: [self selectAt: altInterval last + 1. UndoInterval]
				ifFalse: [altInterval]]
		ifFalse:
			[morph flash]! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
getPluggableYellowButtonMenu: shiftKeyState
	| customMenu |
	^ (morph notNil and: [(customMenu _ morph getMenu: shiftKeyState) notNil])
		ifTrue: [customMenu]
		ifFalse:
			[shiftKeyState
				ifTrue: [self class shiftedYellowButtonMenu]
				ifFalse: [self class yellowButtonMenu]]! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
indent: delta fromStream: inStream toStream: outStream
	"Append the contents of inStream to outStream, adding or deleting delta or -delta
	 tabs at the beginning, and after every CR except a final CR.  Do not add tabs
	 to totally empty lines, and be sure nothing but tabs are removed from lines."

	| ch skip cr tab prev atEnd |
	cr _ Character cr.
	tab _ Character tab.
	delta > 0
		ifTrue: "shift right"
			[prev _ cr.
			 [ch _ (atEnd _ inStream atEnd) ifTrue: [cr] ifFalse: [inStream next].
			  (prev == cr and: [ch ~~ cr]) ifTrue:
				[delta timesRepeat: [outStream nextPut: tab]].
			  atEnd]
				whileFalse:
					[outStream nextPut: ch.
					prev _ ch]]
		ifFalse: "shift left"
			[skip _ delta. "a negative number"
			 [inStream atEnd] whileFalse:
				[((ch _ inStream next) == tab and: [skip < 0]) ifFalse:
					[outStream nextPut: ch].
				skip _ ch == cr ifTrue: [delta] ifFalse: [skip + 1]]]! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
isDisjointFrom: anInterval
	"Answer true if anInterval is a caret not touching or within the current
	 interval, or if anInterval is a non-caret that does not overlap the current
	 selection."

	| fudge |
	fudge _ anInterval size = 0 ifTrue: [1] ifFalse: [0].
	^(anInterval last + fudge < self startIndex or:
			[anInterval first - fudge >= self stopIndex])
! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
nullText

	^Text string: '' emphasis: emphasisHere! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
pageHeight
	| howManyLines visibleHeight totalHeight ratio |
	howManyLines _ paragraph numberOfLines.
	visibleHeight _ self visibleHeight.
	totalHeight _ self totalTextHeight.
	ratio _ visibleHeight / totalHeight.
	^(ratio * howManyLines) rounded - 2! !

!TextEditor methodsFor: 'private' stamp: 'jmv 11/4/2008 11:51'!
sameColumn: start newLine: lineBlock forward: isForward
	"Private - Compute the index in my text
	with the line number derived from lineBlock,"
	" a one argument block accepting the old line number.
	The position inside the line will be preserved as good as possible"
	"The boolean isForward is used in the border case to determine if
	we should move to the beginning or the end of the line."
	| wordStyle column currentLine offsetAtTargetLine targetEOL lines numberOfLines currentLineNumber targetLineNumber |
	wordStyle _ Preferences wordStyleCursorMovement.
	wordStyle
		ifTrue: [
			lines _ paragraph lines.
			numberOfLines := paragraph numberOfLines.
			currentLineNumber  _ paragraph lineIndexOfCharacterIndex: start.
			currentLine _ lines at: currentLineNumber]
		ifFalse: [
			lines _ self lines.
			numberOfLines := lines size.
			currentLine _ lines
				detect:[:lineInterval | lineInterval last >= start]
				ifNone:[lines last].
			currentLineNumber _ currentLine second].
	column _ start - currentLine first.
	targetLineNumber _ ((lineBlock value: currentLineNumber) max: 1) min: numberOfLines.
	offsetAtTargetLine _ (lines at: targetLineNumber) first.
	targetEOL _ (lines at: targetLineNumber) last + (targetLineNumber == numberOfLines ifTrue:[1]ifFalse:[0]).
	targetLineNumber == currentLineNumber
	"No movement or movement failed. Move to beginning or end of line."
		ifTrue:[^isForward
			ifTrue:[targetEOL]
			ifFalse:[offsetAtTargetLine]].
	^offsetAtTargetLine + column min: targetEOL.! !


!TextEditor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 11/4/2008 15:18'!
initializeCmdKeyShortcuts
	"Initialize the (unshifted) command-key (or alt-key) shortcut table."

	"NOTE: if you don't know what your keyboard generates, use Sensor kbdTest"

	"EnhancedTextEditor initialize"

	| cmdMap cmds |
	cmdMap := Array new: 256 withAll: #noop:.	"use temp in case of a crash"
	cmdMap at: 1 + 1 put: #cursorHome:.	"home key"
	cmdMap at: 4 + 1 put: #cursorEnd:.	"end key"
	cmdMap at: 8 + 1 put: #backspace:.	"ctrl-H or delete key"
	cmdMap at: 11 + 1 put: #cursorPageUp:.	"page up key"
	cmdMap at: 12 + 1 put: #cursorPageDown:.	"page down key"
	cmdMap at: 13 + 1 put: #crWithIndent:.	"cmd-Return"
	cmdMap at: 27 + 1 put: #offerMenuFromEsc:.	"escape key"
	cmdMap at: 28 + 1 put: #cursorLeft:.	"left arrow key"
	cmdMap at: 29 + 1 put: #cursorRight:.	"right arrow key"
	cmdMap at: 30 + 1 put: #cursorUp:.	"up arrow key"
	cmdMap at: 31 + 1 put: #cursorDown:.	"down arrow key"
	cmdMap at: 32 + 1 put: #selectWord:.	"space bar key"
	cmdMap at: 127 + 1 put: #forwardDelete:.	"del key"
	'0123456789-=' 
		do: [:char | cmdMap at: char asciiValue + 1 put: #changeEmphasis:].
	'([{''"<' do: [:char | cmdMap at: char asciiValue + 1 put: #enclose:].
	cmdMap at: $, asciiValue + 1 put: #shiftEnclose:.
	cmds := #($a #selectAll: $b #browseIt: $c #copySelection: $d #doIt: $e #exchange: $f #find: $g #findAgain: $h #setSearchString: $i #inspectIt: $j #doAgainOnce: $k #offerFontMenu: $l #cancel: $m #implementorsOfIt: $n #sendersOfIt: $o #spawnIt: $p #printIt: $q #querySymbol: $r #recognizer: $s #save: $u #align: $v #paste: $w #backWord: $x #cut: $y #swapChars: $z #undo:).
	1 to: cmds size
		by: 2
		do: [:i | cmdMap at: (cmds at: i) asciiValue + 1 put: (cmds at: i + 1)].
	CmdActions := cmdMap! !

!TextEditor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 11/4/2008 11:51'!
initializeShiftCmdKeyShortcuts 
	"Initialize the shift-command-key (or control-key) shortcut table."
	"NOTE: if you don't know what your keyboard generates, use Sensor kbdTest"
	"wod 11/3/1998: Fix setting of cmdMap for shifted keys to actually use the 
	capitalized versions of the letters.
	TPR 2/18/99: add the plain ascii values back in for those VMs that don't return the shifted values."

	| cmdMap cmds |

	"shift-command and control shortcuts"
	cmdMap _ Array new: 256 withAll: #noop:.  "use temp in case of a crash"
	cmdMap at: ( 1 + 1) put: #cursorHome:.			"home key"
	cmdMap at: ( 4 + 1) put: #cursorEnd:.			"end key"
	cmdMap at: ( 8 + 1) put: #forwardDelete:.		"ctrl-H or delete key"
	cmdMap at: (11 + 1) put: #cursorPageUp:.		"page up key"
	cmdMap at: (12 + 1) put: #cursorPageDown:.		"page down key"
	cmdMap at: (13 + 1) put: #crWithIndent:.			"ctrl-Return"
	cmdMap at: (27 + 1) put: #offerMenuFromEsc:.	"escape key"
	cmdMap at: (28 + 1) put: #cursorLeft:.			"left arrow key"
	cmdMap at: (29 + 1) put: #cursorRight:.			"right arrow key"
	cmdMap at: (30 + 1) put: #cursorUp:.			"up arrow key"
	cmdMap at: (31 + 1) put: #cursorDown:.			"down arrow key"
	cmdMap at: (32 + 1) put: #selectWord:.			"space bar key"
	cmdMap at: (45 + 1) put: #changeEmphasis:.		"cmd-sh-minus"
	cmdMap at: (61 + 1) put: #changeEmphasis:.		"cmd-sh-plus"
	cmdMap at: (127 + 1) put: #forwardDelete:.		"del key"

	"Note: Command key overrides shift key, so, for example, cmd-shift-9 produces $9 not $("
	'9[,''' do: [ :char | cmdMap at: (char asciiValue + 1) put: #shiftEnclose: ].	"({< and double-quote"
	"Note: Must use cmd-9 or ctrl-9 to get '()' since cmd-shift-9 is a Mac FKey command."

	"NB: sw 12/9/2001 commented out the idiosyncratic line just below, which was grabbing shift-esc in the text editor and hence which argued with the wish to have shift-esc be a universal gesture for escaping the local context and calling up the desktop menu."  
	"cmdMap at: (27 + 1) put: #shiftEnclose:." 	"ctrl-["

	"'""''(' do: [ :char | cmdMap at: (char asciiValue + 1) put: #enclose:]."

	cmds _ #(
		$a	argAdvance:
		$b	browseItHere:
		$c	compareToClipboard:
		$d	duplicate:
		$e	methodStringsContainingIt:
		$f	displayIfFalse:
		$g	fileItIn:
		$h	cursorTopHome:
		$i	exploreIt:
		$j	doAgainMany:
		$k	changeStyle:
		$l	outdent:
		$m	selectCurrentTypeIn:
		$n	referencesToIt:
		$r	indent:
		$s	search:
		$t	displayIfTrue:
		$u	changeLfToCr:
		$v	pasteInitials:
		$w	methodNamesContainingIt:
		$x	makeLowercase:
		$y	makeUppercase:
		$z	makeCapitalized:
	).
	1 to: cmds size by: 2 do: [ :i |
		cmdMap at: ((cmds at: i) asciiValue + 1) put: (cmds at: i + 1).		"plain keys"
		cmdMap at: ((cmds at: i) asciiValue - 32 + 1) put: (cmds at: i + 1).		"shifted keys"
		cmdMap at: ((cmds at: i) asciiValue - 96 + 1) put: (cmds at: i + 1).		"ctrl keys"
	].
	ShiftCmdActions _ cmdMap! !

!TextEditor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 11/4/2008 11:51'!
initializeTextEditorMenus
	"Initialize the yellow button pop-up menu and corresponding messages."
	"OldTextMorpgEditor2 initializeTextEditorMenus"

	TextEditorYellowButtonMenu _ SelectionMenu
		fromArray: {
		{'find...(f)' translated.		#find}.			     
		{'find again (g)' translated.		#findAgain}.		     
		{'set search string (h)' translated.	#setSearchString}.
		#-.	     
		{'do again (j)' translated.		#again}.		     
		{'undo (z)' translated.			#undo}.			     
		#-.	     
		{'copy (c)' translated.			#copySelection}.	     
		{'cut (x)' translated.			#cut}.			     
		{'paste (v)' translated.		#paste}.		     
		{'paste...' translated.			#pasteRecent}.		     
		#-.	     
		{'do it (d)' translated.		#doIt}.			     
		{'print it (p)' translated.		#printIt}.		     
		{'inspect it (i)' translated.		#inspectIt}.		     
		{'explore it (I)' translated.		#exploreIt}.		     
		{'debug it' translated.			#debugIt}.		     
		#-.	     
		{'accept (s)' translated.		#accept}.		     
		{'cancel (l)' translated.		#cancel}.		     
		#-.	     
		{'show bytecodes' translated.		#showBytecodes}.	     
		#-.	     
		{'more...' translated.			#shiftedTextPaneMenuRequest}.
	}! !

!TextEditor class methodsFor: 'class initialization' stamp: 'jmv 11/4/2008 11:51'!
abandonChangeText
	"Call this to get out of the maddening situation in which the system keeps aggressively trying to do a replacement that you no longer wish to make, every time you make choose a new method in a list."
	ChangeText _ FindText

	"ParagraphEditor abandonChangeText"! !

!TextEditor class methodsFor: 'class initialization' stamp: 'jmv 11/5/2008 13:08'!
initialize 
	"Initialize the keyboard shortcut maps and the shared buffers
	for copying text across views and managing again and undo." 
 
	"TextEditor initialize"

	UndoSelection _ FindText _ ChangeText _ Text new.
	UndoMessage _ Message selector: #halt.

	self initializeCmdKeyShortcuts.
	self initializeShiftCmdKeyShortcuts.
	self initializeTextEditorMenus! !

!TextEditor class methodsFor: 'class initialization' stamp: 'jmv 11/4/2008 11:51'!
shiftedYellowButtonMenu
	"Answer the menu to be presented when the yellow button is pressed while the shift key is down"

	^ SelectionMenu fromArray: {
		{'set font... (k)' translated.					#offerFontMenu}.
		{'set style... (K)' translated.					#changeStyle}.
		{'set alignment...' translated.				#chooseAlignment}.
		#-.
		{'explain' translated.						#explain}.
		{'pretty print' translated.					#prettyPrint}.
		{'pretty print with color' translated.			#prettyPrintWithColor}.
		{'file it in (G)' translated.					#fileItIn}.
		{'spawn (o)' translated.						#spawn}.
		#-.
		{'browse it (b)' translated.					#browseIt}.
		{'senders of it (n)' translated.				#sendersOfIt}.
		{'implementors of it (m)' translated.		#implementorsOfIt}.
		{'references to it (N)' translated.			#referencesToIt}.
		#-.
		{'selectors containing it (W)' translated.	#methodNamesContainingIt}.
		{'method strings with it (E)' translated.	#methodStringsContainingit}.
		{'method source with it' translated.		#methodSourceContainingIt}.
		{'class names containing it' translated.	#classNamesContainingIt}.
		{'class comments with it' translated.		#classCommentsContainingIt}.
		{'change sets with it' translated.			#browseChangeSetsWithSelector}.
		#-.
		{'save contents to file...' translated.		#saveContentsInFile}.
		{'send contents to printer' translated.		#sendContentsToPrinter}.
		{'printer setup' translated.					#printerSetup}.
		#-.
		{'special menu...' translated.				#presentSpecialMenu}.
		{'more...' translated.							#yellowButtonActivity}.
	}! !

!TextEditor class methodsFor: 'class initialization' stamp: 'jmv 11/4/2008 11:51'!
yellowButtonMenu

	^ TextEditorYellowButtonMenu! !

TextEditor initialize!
SimpleEditor initialize!

!SimpleEditor class reorganize!
('keyboard shortcut tables' initializeCmdKeyShortcuts initializeShiftCmdKeyShortcuts)
('class initialization' initialize)
!

SimpleEditor removeSelector: #cancel:!
SimpleEditor removeSelector: #hasSelection!
SimpleEditor removeSelector: #mouseDown:!
SimpleEditor removeSelector: #pageHeight!
SimpleEditor removeSelector: #sameColumn:newLine:forward:!
SimpleEditor removeSelector: #save:!

!OldTextMorph reorganize!
('accessing' asText autoFit: backgroundColor backgroundColor: borderWidth: contents contents: contents:wrappedTo: contentsAsIs: contentsWrapped: crAction crAction: cursor editor isAutoFit isWrapped margins margins: newContents: text textColor textColor: textStyle userString wrapFlag:)
('alignment' centered justified leftFlush rightFlush)
('anchors' adjustTextAnchor: anchorMorph:at:type:)
('caching' releaseCachedState)
('change reporting' ownerChanged)
('classification' isTextMorph)
('containment' fillingOnOff occlusionsOnOff setContainer:)
('copying' copy veryDeepFixupWith: veryDeepInner:)
('drawing' areasRemainingToFill: debugDrawLineRectsOn: drawNullTextOn: drawOn:)
('editing' acceptContents acceptOnCR cancelEdits chooseAlignment chooseEmphasis chooseEmphasisOrAlignment chooseFont chooseStyle enterClickableRegion: handleEdit: handleInteraction:fromEvent: hasUnacceptedEdits: passKeyboardFocusTo: xeqLinkText:withParameter:)
('event handling' handlesKeyboard: handlesMouseDown: hasFocus keyStroke: keyboardFocusChange: mouseDown: mouseMove: mouseUp: wouldAcceptKeyboardFocusUponTab)
('events-processing' handleKeystroke: handleMouseMove:)
('geometry' bounds container defaultLineHeight extent: minimumExtent privateMoveBy: textBounds)
('geometry testing' containsPoint:)
('initialization' beAllFont: defaultColor initialize setTextStyle: string:fontName:size: string:fontName:size:wrap:)
('layout' acceptDroppingMorph:event:)
('linked frames' addPredecessor: addSuccessor: firstCharacterIndex firstInChain isLinkedTo: lastCharacterIndex predecessor recomposeChain startingIndex successor withSuccessorsDo:)
('menu' addCustomMenuItems:hand: autoFitOnOff autoFitString changeMargins: reverseCurveDirection setCurveBaseline: shiftedYellowButtonActivity wrapOnOff wrapString yellowButtonActivity)
('printing' fullPrintOn:)
('scripting access' insertCharacters: insertContentsOf:)
('submorphs-add/remove' addMorphFront:fromWorldPosition: delete goBehind)
('testing' basicType)
('visual properties' fillStyle fillStyle:)
('private' adjustLineIndicesBy: clippingRectangle composeToBounds compositionRectangle fit installEditorToReplace: paragraph paragraphClass predecessor:successor: predecessorChanged privateOwner: releaseEditor releaseParagraph releaseParagraphReally removedMorph: selectionChanged setDefaultContentsIfNil setPredecessor: setSuccessor: text:textStyle: text:textStyle:wrap:color:predecessor:successor: updateFromParagraph)
('*connectors-accessing' fontName:size:)
!

OldStringMorph removeSelector: #lookTranslucent!
OldOneLineEditorMorph removeSelector: #acceptOnCR!

!OldOneLineEditorMorph reorganize!
('accessing' baseFont contents: editor fitContents fontToUse measureContents minimumWidth)
('drawing' caretWidth characterIndexAtPoint: drawCaretOn: drawOn: drawSelectionOn: insertionPointColor selectionColor)
('editing' handleInteraction:fromEvent:)
('event handling' handlesKeyboard: handlesMouseDown: hasFocus keyStroke: keyboardFocusChange: mouseDown: mouseMove:)
('initialization' defaultColor initWithContents:font:emphasis: initialize)
('testing' hasCaret hasSelection)
('unaccepted edits' hasUnacceptedEdits:)
('private' installEditorToReplace: updateFromContents)
!

Editor initialize!
"Postscript:
Leave the line above, and replace the rest of this comment by a useful one.
Executable statements should follow this comment, and should
be separated by periods, with no exclamation points (!!).
Be sure to put any further comments in double-quotes, like this one."
Editor initialize!

