'From Cuis 1.0 of 27 August 2009 [latest update: #282] on 2 September 2009 at 9:48:14 pm'!
!classDefinition: #Editor category: #'System-Text'!
Object subclass: #Editor
	instanceVariableNames: 'sensor morph selectionShowing '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'System-Text'!

!Editor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:37'!
copySelection: aKeyboardEvent
	"Copy the current text selection."

	self copySelection.
	^true! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:38'!
cut: aKeyboardEvent 
	"Cut out the current text selection."

	self cut.
	^true! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:38'!
noop: aKeyboardEvent 
	"Unimplemented keyboard command; just ignore it."

	^ true! !

!Editor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:38'!
paste: aKeyboardEvent 
	"Replace the current text selection by the text in the shared buffer."

	self closeTypeIn.
	self paste.
	^true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:17'!
cursorDown: aKeyboardEvent 

	"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.
	self 
		moveCursor:[:position | self
				sameColumn: position
				newLine:[:line | line + 1]
				forward: true]
		forward: true
		specialBlock:[:dummy | dummy]
		event: aKeyboardEvent.
	^true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:17'!
cursorLeft: aKeyboardEvent 
	"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.
	self
		moveCursor:[:position | position - 1 max: 1]
		forward: false
		specialBlock:[:position | self previousWord: position]
		event: aKeyboardEvent.
	^ true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:17'!
cursorPageDown: aKeyboardEvent 

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

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:17'!
cursorPageUp: aKeyboardEvent 

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

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:17'!
cursorRight: aKeyboardEvent 
	"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.
	self
		moveCursor: [:position | position + 1]
		forward: true
		specialBlock:[:position | self nextWord: position]
		event: aKeyboardEvent.
	^ true! !

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:17'!
cursorUp: aKeyboardEvent 
"Private - Move cursor from position in current line to same position in
prior line. If prior line too short, put at end"

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

!Editor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 12:38'!
selectWord: aKeyboardEvent

	self closeTypeIn.
	self selectWord.
	^ true! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:37'!
backWord: aKeyboardEvent
	"If the selection is not a caret, delete it and leave it in the backspace buffer.
	 Else, delete the word before the caret."

	| startIndex |
	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.
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 13:01'!
backspace: aKeyboardEvent
	"Backspace over the last character."

	| startIndex |
	aKeyboardEvent shiftPressed ifTrue: [^ self backWord: aKeyboardEvent keyCharacter].
	startIndex _ self markIndex + (self hasCaret ifTrue: [0] ifFalse: [1]).
	startIndex _ 1 max: startIndex - 1.
	self backTo: startIndex.
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:37'!
crlf: aKeyboardEvent
	"Append a line feed character to the stream of characters."

	self addString: String crlf.
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:37'!
cursorTopHome: aKeyboardEvent
	"Put cursor at beginning of text -- invoked from cmd-H shortcut, useful for keyboards that have no home key."

	self selectAt: 1.
	^ true! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:38'!
lf: aKeyboardEvent 
	"Append a line feed character to the stream of characters."

	self addString: Character lf asString.
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:38'!
normalCharacter: aKeyboardEvent 
	"A nonspecial character is to be added to the stream of characters."

	self addString: aKeyboardEvent keyCharacter asString.
	^false! !

!Editor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:38'!
selectAll: aKeyboardEvent 
	"select everything, invoked by cmd-a.  1/17/96 sw"

	self closeTypeIn.
	self selectFrom: 1 to: self string size.
	^ true! !

!Editor methodsFor: 'private' stamp: 'jmv 9/2/2009 13:18'!
moveCursor: directionBlock forward: forward specialBlock: specialBlock event: aKeyboardEvent
	"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 _ aKeyboardEvent shiftPressed.
	indices _ self setIndices: shift forward: forward.
	newPosition _ directionBlock value: (indices at: #moving).
	(aKeyboardEvent commandKeyPressed or: [ aKeyboardEvent controlKeyPressed ])
		ifTrue: [newPosition _ specialBlock value: newPosition].
	shift
		ifTrue: [self selectMark: (indices at: #fixed) point: newPosition - 1]
		ifFalse: [self selectAt: newPosition]! !


!EntryField2LW methodsFor: 'editing' stamp: 'jmv 9/2/2009 13:51'!
handleInteraction: interactionBlock
	"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 |
	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! !

!EntryField2LW methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:52'!
keyStroke: aKeyboardEvent
	"Handle a keystroke event."

	| k |
	aKeyboardEvent commandKeyPressed
		ifTrue: [ ^super keyStroke: aKeyboardEvent ].
	
	k _ aKeyboardEvent keyValue.
	k = 13 ifTrue: [
		crAction notNil ifTrue: [	
			self performActionWith: contents.
			self updateView.
			^target perform: crAction ].
		^ aKeyboardEvent hand keyboardFocusNext ].
	(k = 30 or: [ k = 31 ])
		ifTrue: [ ^super keyStroke: aKeyboardEvent ].
	(k = 28 and: [ editor isAtStart ])
		ifTrue: [ ^super keyStroke: aKeyboardEvent ].
	(k = 29 and: [ editor isAtEnd ])
		ifTrue: [ ^super keyStroke: aKeyboardEvent ].

	self handleInteraction: [ editor processKeyStroke: aKeyboardEvent ].
	self updateFromContents! !

!EntryField2LW methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:52'!
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) ]! !

!EntryField2LW methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
mouseMove: evt
	evt redButtonPressed ifFalse: [^ self].
	self handleInteraction: [
		editor mouseMove: evt index: (self characterIndexAtPoint: evt cursorPoint) ]! !


!CellStyleEntryFieldLW methodsFor: 'editing' stamp: 'jmv 9/2/2009 13:52'!
handleInteraction: interactionBlock

	super handleInteraction: interactionBlock.
	editor isInCellShiftMode ifTrue: [
		self startStepping ]! !


!OneLineEditorMorph methodsFor: 'editing' stamp: 'jmv 9/2/2009 13:51'!
handleInteraction: interactionBlock
	"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 |
	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! !

!OneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
keyStroke: evt
	"Handle a keystroke event."

	(evt commandKeyPressed and: [ self focusKeyboardFor: evt ])
		ifTrue: [ ^ self ].

	"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 processKeyStroke: evt ].
	self updateFromContents.
	super keyStroke: evt  "sends to keyStroke event handler, if any"! !

!OneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
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) ]! !

!OneLineEditorMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
mouseMove: evt
	evt redButtonPressed ifFalse: [^ self].
	self handleInteraction: [
		editor mouseMove: evt index: (self characterIndexAtPoint: evt cursorPoint) ]! !


!SharedPool class methodsFor: 'name lookup' stamp: 'jmv 9/2/2009 12:50'!
keysDo: aBlock
	^self classPool keysDo: aBlock! !


!SimpleEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:40'!
makeCapitalized: aKeyboardEvent
	"Force the current selection to uppercase.  Triggered by Cmd-X."
	| prev |
	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 9/2/2009 12:40'!
makeLowercase: aKeyboardEvent
	"Force the current selection to lowercase.  Triggered by Cmd-X."

	self replaceSelectionWith: (self selection asLowercase).
	^ true! !

!SimpleEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:40'!
makeUppercase: aKeyboardEvent
	"Force the current selection to uppercase.  Triggered by Cmd-Y."

	self replaceSelectionWith: (self selection asUppercase).
	^ true! !

!SimpleEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:18'!
cursorEnd: aKeyboardEvent

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

!SimpleEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:18'!
cursorHome: aKeyboardEvent 

	"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]
		event: aKeyboardEvent.
	^true! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 07:23'!
addString: aString
	"Think of a better name"

	self zapSelectionWith: aString! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 11:47'!
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; 
	(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 9/2/2009 11:47'!
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."

	beginTypeInIndex _ nil! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 13:09'!
dispatchOn: aKeyboardEvent
	"Carry out the action associated with this character, if any.
	Type-ahead is passed so some routines can flush or use it."

	| asciiValue honorCommandKeys |
	"We don't support multiple lines. Therefore, we don't process cr as a #normalCharacter:"
	((aKeyboardEvent keyCharacter == Character cr) "and: [morph acceptOnCR]")
		ifTrue: [
			self closeTypeIn.
			^ true].

	asciiValue _ aKeyboardEvent keyValue.
	asciiValue = 13 ifTrue: [
		aKeyboardEvent controlKeyPressed ifTrue: [
			^ self normalCharacter: aKeyboardEvent ].
		aKeyboardEvent shiftPressed ifTrue: [
			^ self lf: aKeyboardEvent ].
		aKeyboardEvent commandKeyPressed ifTrue: [
			^ self crlf: aKeyboardEvent ].
		^ self crWithIndent: aKeyboardEvent ].

	honorCommandKeys _ Preferences cmdKeysInText.

	"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: asciiValue) and: [ asciiValue < 27])
		ifTrue: [^ aKeyboardEvent controlKeyPressed
			ifTrue: [self perform: (ShiftCmdActions at: asciiValue + 1) with: aKeyboardEvent]
			ifFalse: [self perform: (CmdActions at: asciiValue + 1) with: aKeyboardEvent]].

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

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

	^ self normalCharacter: aKeyboardEvent! !

!SimpleEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 12:58'!
processKeyStroke: aKeyboardEvent

	"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."
	
		self deselect.
			
			(self dispatchOn: aKeyboardEvent) ifTrue: [
				self doneTyping.
				^self].
			self openTypeIn.

		self unselect! !

!SimpleEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 13:02'!
forwardDelete: aKeyboardEvent
	"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: [
		^ false].
	self hasSelection ifTrue: [
		"there was a selection"
		self zapSelectionWith: ''.
		^ false].
	"Null selection - do the delete forward"
	beginTypeInIndex isNil	"no previous typing.  openTypeIn"
		ifTrue: [self openTypeIn].
	stopIndex := startIndex.
	(aKeyboardEvent keyValue = 127 and: [ aKeyboardEvent shiftPressed ])
		ifTrue: [ stopIndex := (self nextWord: stopIndex) - 1 ].
	self selectFrom: startIndex to: stopIndex.
	self replaceSelectionWith: ''.
	self selectFrom: startIndex to: startIndex-1.
	^false! !


!CellStyleEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:36'!
normalCharacter: aKeyboardEvent
	"A nonspecial character is to be added to the stream of characters."
	
	| keystroke char |
	keystroke _ aKeyboardEvent keyCharacter.
	keystroke isDigit
		ifTrue: [
			(keystroke = lastKeystroke and: [ self isInCellShiftMode ])
				ifTrue: [
					char _ self nextCellCharFor: keystroke after: (string at: markIndex - 1).
					self backTo: markIndex - 1]
				ifFalse: [ 
					char _ self nextCellCharFor: keystroke after: nil ].
			storeLastTime _ true ]
		ifFalse: [
			char _ keystroke ].
	self addString: char.
	lastKeystroke _ keystroke.
	^false! !

!CellStyleEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 12:55'!
dispatchOn: aKeyboardEvent
	| answer |
	storeLastTime _ false.
	answer _ super dispatchOn: aKeyboardEvent.
	lastTime _ storeLastTime
		ifTrue: [Time millisecondClockValue]
		ifFalse: [0].
	^ answer! !


!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:42'!
align: aKeyboardEvent
	"Triggered by Cmd-u;  cycle through alignment alternatives.  8/11/96 sw"

	self align.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:42'!
cancel: aKeyboardEvent
	"Cancel unsubmitted changes."

	self cancel.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 13:03'!
changeEmphasis: aKeyboardEvent
	"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 |
	keyCode := ('0123456789-=' indexOf: aKeyboardEvent keyCharacter ifAbsent: [1]) - 1.
	oldAttributes := paragraph text attributesAt: self pointIndex.
	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).
			index := (PopUpMenu 
						labelArray: colors , #('choose color...' )
						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].
					thisSel ifNil: [^true]	"Could not figure out what to link to"]].
	(keyCode between: 7 and: 11) 
		ifTrue: [
			aKeyboardEvent shiftPressed 
				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 ].
	attribute ifNotNil: [
		self applyAttribute: attribute].
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:42'!
changeLfToCr: aKeyboardEvent
	"Replace all LFs by CRs.
	Triggered by Cmd-U -- useful when getting code from FTP sites
	jmv- Modified to als change crlf by cr"
	
	| fixed |
	fixed _ self selection string.
	fixed _ fixed copyReplaceAll: String crlf with: String cr.
	fixed _ fixed copyReplaceAll: String lf with: String cr. 
	self replaceSelectionWith: (Text fromString: fixed).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:42'!
compareToClipboard: aKeyboardEvent
	"Compare the receiver to the text on the clipboard."

	self compareToClipboard.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:43'!
duplicate: aKeyboardEvent
	"Paste the current selection over the prior selection, if it is non-overlapping and
	 legal.  Undoer & Redoer: undoAndReselect."

	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 9/2/2009 12:43'!
enclose: aKeyboardEvent
	"Insert or remove bracket characters around the current selection."

	| left right startIndex stopIndex oldSelection which text |
	self closeTypeIn.
	startIndex _ self startIndex.
	stopIndex _ self stopIndex.
	oldSelection _ self selection.
	which _ '([<{"''' indexOf: aKeyboardEvent keyCharacter 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) attributes: emphasisHere).
			self selectFrom: startIndex+1 to: stopIndex].
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:43'!
exchange: aKeyboardEvent
	"Exchange the current and prior selections."

	self closeTypeIn.
	self exchange.
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:44'!
inOutdent: aKeyboardEvent delta: delta
	"Add/remove a tab at the front of every line occupied by the selection. 
	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 |
	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 9/2/2009 12:44'!
indent: aKeyboardEvent
	"Add a tab at the front of every line occupied by the selection. Invoked from keyboard via cmd-shift-R.  2/29/96 sw"

	^ self inOutdent: aKeyboardEvent delta: 1! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:44'!
makeCapitalized: aKeyboardEvent
	"Force the current selection to uppercase.  Triggered by Cmd-X."

	| prev |
	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 9/2/2009 12:44'!
makeLowercase: aKeyboardEvent
	"Force the current selection to lowercase.  Triggered by Cmd-X."

	self replaceSelectionWith: (Text fromString: (self selection string asLowercase)).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:44'!
makeUppercase: aKeyboardEvent
	"Force the current selection to uppercase.  Triggered by Cmd-Y."

	self replaceSelectionWith: (Text fromString: (self selection string asUppercase)).
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:44'!
offerFontMenu: aKeyboardEvent 
	"The user typed the command key that requests a font change; Offer the font menu.  5/27/96 sw"

	self closeTypeIn.
	self offerFontMenu.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:45'!
outdent: aKeyboardEvent
	"Remove a tab from the front of every line occupied by the selection.
	Invoked from keyboard via cmd-shift-L.  2/29/96 sw"

	^ self inOutdent: aKeyboardEvent delta: -1! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:45'!
shiftEnclose: aKeyboardEvent
	"Insert or remove bracket characters around the current selection."

	| char left right startIndex stopIndex oldSelection which text |
	char _ aKeyboardEvent keyCharacter.
	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) attributes: emphasisHere).
			self selectFrom: startIndex+1 to: stopIndex].
	^true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:45'!
swapChars: aKeyboardEvent
	"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 |
	(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 attributes: emphasisHere).
	self selectAt: currentSelection + 1.
	^ true! !

!TextEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:45'!
undo: aKeyboardEvent 
	"Undo the last edit."

	self closeTypeIn.
	self undo.
	^true! !

!TextEditor methodsFor: 'menu commands' stamp: 'jmv 9/2/2009 12:44'!
offerMenuFromEsc: aKeyboardEvent
	"The escape key was hit while the receiver has the keyboard focus; take action"

	^ ActiveEvent shiftPressed
		ifFalse: [
			self raiseContextMenu: aKeyboardEvent]! !

!TextEditor methodsFor: 'menu messages' stamp: 'jmv 9/2/2009 13:31'!
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."

	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 9/2/2009 13:44'!
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: false! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:18'!
cursorEnd: aKeyboardEvent 

	"Private - Move cursor end of current line."
	| string |
	self closeTypeIn.
	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]
		event: aKeyboardEvent.
	^true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 13:18'!
cursorHome: aKeyboardEvent

	"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]
		event: aKeyboardEvent.
	^true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 12:44'!
raiseContextMenu: aKeyboardEvent

	(morph respondsTo: #editView)
		ifTrue: [morph editView yellowButtonActivity: ActiveEvent shiftPressed].
	^ true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 12:45'!
selectCurrentTypeIn: aKeyboardEvent 
	"Select what would be replaced by an undo (e.g., the last typeIn)."

	| prior |

	self closeTypeIn.
	prior _ otherInterval.
	self closeTypeIn.
	self selectInterval: UndoInterval.
	otherInterval _ prior.
	^ true! !

!TextEditor methodsFor: 'nonediting/nontyping keys' stamp: 'jmv 9/2/2009 12:45'!
setSearchString: aKeyboardEvent
	"Establish the current selection as the current search string."

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

!TextEditor methodsFor: 'parenblinking' stamp: 'jmv 9/2/2009 13:20'!
blinkPrevParen: aKeyboardEvent
	| openDelimiter closeDelimiter level string here hereChar |
	string _ paragraph text string.
	here _ pointBlock stringIndex.
	openDelimiter _ aKeyboardEvent keyValue.
	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: 'pluggable menus' stamp: 'jmv 9/2/2009 13:43'!
pluggableYellowButtonActivity: shiftKeyState
	"Invoke the model's popup menu."

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

!TextEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 07:23'!
addString: aString
	"Think of a better name"

	self zapSelectionWith: 
		(Text string: aString attributes: emphasisHere)! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 11:47'!
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;
	(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 hasArguments ifTrue: [ 
			UndoMessage argument: (UndoMessage argument ifNil: [1]) + newBackovers]].
	self zapSelectionWith: self nullText.
	self unselect! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 11:48'!
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.
	 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 9/2/2009 13:19'!
dispatchOn: aKeyboardEvent
	"Carry out the action associated with this character, if any.
	Type-ahead is passed so some routines can flush or use it."

	| asciiValue honorCommandKeys |
	((aKeyboardEvent keyCharacter == Character cr) and: [morph acceptOnCR])
		ifTrue: [
			self closeTypeIn.
			^ true].

	self clearParens.
	
  	asciiValue _ aKeyboardEvent keyValue.
	asciiValue = 13 ifTrue: [
		aKeyboardEvent controlKeyPressed ifTrue: [
			^ self normalCharacter: aKeyboardEvent ].
		aKeyboardEvent shiftPressed ifTrue: [
			^ self lf: aKeyboardEvent ].
		aKeyboardEvent commandKeyPressed ifTrue: [
			^ self crlf: aKeyboardEvent ].
		^ self crWithIndent: aKeyboardEvent ].

	honorCommandKeys _ Preferences cmdKeysInText.

	"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: asciiValue) and: [ asciiValue < 27])
		ifTrue: [^ aKeyboardEvent controlKeyPressed
			ifTrue: [self perform: (self class shiftCmdActions at: asciiValue + 1) with: aKeyboardEvent]
			ifFalse: [self perform: (self class cmdActions at: asciiValue + 1) with: aKeyboardEvent]].

	"backspace, and escape keys (ascii 8 and 27) are command keys"
	((honorCommandKeys and: [aKeyboardEvent commandKeyPressed]) or: [self class specialShiftCmdKeys includes: asciiValue]) ifTrue: [
		^  aKeyboardEvent shiftPressed
			ifTrue: [
				self perform: (self class shiftCmdActions at: asciiValue + 1) with: aKeyboardEvent]
			ifFalse: [
				self perform: (self class cmdActions at: asciiValue + 1) with: aKeyboardEvent]].

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

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

	^ self normalCharacter: aKeyboardEvent! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 9/2/2009 12:59'!
processKeyStroke: aKeyboardEvent
	"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."
	
	
		self deselect.
		
		
			(self dispatchOn: aKeyboardEvent) ifTrue: [
				self doneTyping.
				self storeSelectionInParagraph.
				^self].
			self openTypeIn.
			
			
		self hasSelection ifTrue: [ "save highlighted characters"
			UndoSelection _ self selection].
		self unselect.
		
		
	self storeSelectionInParagraph! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:42'!
changeStyle: aKeyboardEvent 
	"Put up the style-change menu"

	self closeTypeIn.
	self changeStyle.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:43'!
crWithIndent: aKeyboardEvent
	"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 |
	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]].
	 "Now inject CR with tabCount tabs"
	self addString: (String streamContents: [ :strm | strm crtab: tabCount ]).
	^ false! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:43'!
doAgainMany: aKeyboardEvent 
	"Do the previous thing again repeatedly. 1/26/96 sw"

	self closeTypeIn.
	self againOrSame: (UndoMessage sends: #undoAgain:andReselect:typedKey:) many: true.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:43'!
find: aKeyboardEvent
	"Prompt the user for what to find, then find it, searching from the current selection onward.  1/24/96 sw"

	self closeTypeIn.
	self find.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 13:30'!
findAgain: aKeyboardEvent 
	"Find the desired text again.  1/24/96 sw"

	self closeTypeIn.
	self againOrSame: true many: aKeyboardEvent shiftPressed.
	^ true! !

!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 13:03'!
forwardDelete: aKeyboardEvent
	"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: [
		^ false].
	self hasSelection ifTrue: [
		"there was a selection"
		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.
	(aKeyboardEvent keyValue = 127 and: [ aKeyboardEvent shiftPressed ])
		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 9/2/2009 13:27'!
search: aKeyboardEvent
	"Invoked by Ctrl-S.  Same as 'again', but always uses the existing FindText
	 and ChangeText regardless of the last edit."

	self closeTypeIn.
	self
		againOrSame: true "true means use same keys"
		many: aKeyboardEvent shiftPressed.
	^true! !

!TextEditor methodsFor: 'private' stamp: 'jmv 9/2/2009 13:31'!
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: false.

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

!TextEditor methodsFor: 'private' stamp: 'jmv 9/2/2009 13:26'!
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.

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

!TextEditor methodsFor: 'private' stamp: 'jmv 9/2/2009 13:17'!
moveCursor: directionBlock forward: forward specialBlock: specialBlock event: aKeyboardEvent

	super moveCursor: directionBlock forward: forward specialBlock: specialBlock event: aKeyboardEvent.
	self setEmphasisHereFromTextForward: forward! !


!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:40'!
browseIt: aKeyboardEvent
	"Triggered by Cmd-B; browse the thing represented by the current selection, if plausible.  1/18/96 sw"

	self browseIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:40'!
browseItHere: aKeyboardEvent
	"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"

	self browseItHere.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
doIt: aKeyboardEvent
	"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"

	self doIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
exploreIt: aKeyboardEvent
	"Explore the selection -- invoked via cmd-shift-I.  If there is no current selection, use the current line."

	self exploreIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
fileItIn: aKeyboardEvent
	"File in the selection; invoked via a keyboard shortcut, -- for now, cmd-shift-G."

	self fileItIn.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
implementorsOfIt: aKeyboardEvent
	"Triggered by Cmd-m; browse implementors of the selector represented by the current selection, if plausible. 2/1/96 sw"

	self implementorsOfIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
inspectIt: aKeyboardEvent
	"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"

	self inspectIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
methodNamesContainingIt: aKeyboardEvent
	"Browse methods whose selectors containing the selection in their names"

	self methodNamesContainingIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
methodStringsContainingIt: aKeyboardEvent
	"Invoked from cmd-E -- open a browser on all methods holding string constants containing it."

	self methodStringsContainingit.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
pasteInitials: aKeyboardEvent 
	"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."

	self closeTypeIn.
	self replace: self selectionInterval with: (Text fromString: Utilities changeStamp) and: [self selectAt: self stopIndex].
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
printIt: aKeyboardEvent
	"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"

	self printIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
referencesToIt: aKeyboardEvent
	"Triggered by Cmd-N; browse references to the current selection"

	self referencesToIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
save: aKeyboardEvent
	"Submit the current text.  Equivalent to 'accept' 1/18/96 sw"

	self closeTypeIn.
	self accept.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
sendersOfIt: aKeyboardEvent
	"Triggered by Cmd-n; browse implementors of the selector represented by the current selection, if plausible. 2/1/96 sw"

	self sendersOfIt.
	^ true! !

!SmalltalkEditor methodsFor: 'editing keys' stamp: 'jmv 9/2/2009 12:41'!
spawnIt: aKeyboardEvent
	"Triggered by Cmd-o; spawn a new code window, if it makes sense."

	self spawn.
	^ true! !

!SmalltalkEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:40'!
argAdvance: aKeyboardEvent
	"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 |
	self closeTypeIn.
	start _ paragraph text findString: ': ' startingAt: self stopIndex.
	start = 0 ifTrue: [ start _ paragraph text size + 1].
	self selectAt: start + 2.
	^true! !

!SmalltalkEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:40'!
displayIfFalse: aKeyboardEvent
	"Replace the current text selection with the text 'ifFalse:'--initiated by 
	ctrl-f."

	self addString: 'ifFalse:'.
	^false! !

!SmalltalkEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:40'!
displayIfTrue: aKeyboardEvent
	"Replace the current text selection with the text 'ifTrue:'--initiated by 
	ctrl-t."

	self addString: 'ifTrue:'.
	^false! !

!SmalltalkEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:41'!
doAgainOnce: aKeyboardEvent 
	"Do the previous thing again once. 1/26/96 sw"

	self closeTypeIn.
	self again.
	^ true! !

!SmalltalkEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 9/2/2009 12:41'!
querySymbol: aKeyboardEvent
	"Invoked by Ctrl-q to query the Symbol table and display alternate symbols.
	 See comment in completeSymbol:lastOffering: for details."

	self closeTypeIn.
	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! !


!SmalltalkEditor class methodsFor: 'keyboard shortcut tables' stamp: 'jmv 9/2/2009 13:33'!
initializeShiftedYellowButtonMenu
	"Initialize the yellow button pop-up menu and corresponding messages."

	"SmalltalkEditor initialize"

	shiftedYellowButtonMenu _ SelectionMenu fromArray: {
		{'explain' translated.						#explain}.
		{'pretty print' translated.					#prettyPrint}.
		{'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}.
		#-.
		{'more...' translated.						#yellowButtonActivity}.
	}! !


!TextMorph methodsFor: 'editing' stamp: 'jmv 9/2/2009 13:51'!
handleInteraction: interactionBlock
	"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 ParagraphEditor to Morphic keyboard events"
	| oldEditor oldParagraph oldSize |
	oldEditor _ editor.
	oldParagraph _ paragraph.
	oldSize _ oldParagraph text size.

	self selectionChanged.  "Note old selection"

		interactionBlock value.

	(oldParagraph == paragraph) ifTrue:[
		"this will not work if the paragraph changed"
		editor _ oldEditor.     "since it may have been changed while in block"
	].
	self selectionChanged.  "Note new selection"
	(oldSize = paragraph text size)
		ifFalse:[self updateFromParagraph].
! !

!TextMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
keyStroke: evt
	"Handle a keystroke event."
	| action |
	(evt commandKeyPressed and: [ self focusKeyboardFor: evt ])
		ifTrue: [ ^ self ].
	evt keyValue = 13 ifTrue:["CR - check for special action"
		action _ self crAction.
		action ifNotNil:[
			"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."
			evt hand newKeyboardFocus: nil.
			^action value]].
	self handleInteraction: [ editor processKeyStroke: evt ].
	self updateFromParagraph.
	super keyStroke: evt  "sends to keyStroke event handler, if any"! !

!TextMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
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]! !

!TextMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
mouseMove: evt
	evt redButtonPressed ifFalse: [^ self enterClickableRegion: evt].
	self handleInteraction: [editor mouseMove: evt]! !

!TextMorph methodsFor: 'event handling' stamp: 'jmv 9/2/2009 13:53'!
mouseUp: evt
	self handleInteraction: [editor mouseUp: evt]! !

!TextMorph methodsFor: 'menu' stamp: 'jmv 9/2/2009 13:33'!
addCustomMenuItems: aCustomMenu hand: aHandMorph 
	"Add text-related menu items to the menu"

	| outer |
	super addCustomMenuItems: aCustomMenu hand: aHandMorph.
	aCustomMenu 
		addUpdating: #autoFitString
		target: self
		action: #autoFitOnOff.
	aCustomMenu 
		addUpdating: #wrapString
		target: self
		action: #wrapOnOff.
	aCustomMenu add: 'text margins...' translated action: #changeMargins:.
	Preferences simpleMenus
		ifFalse: [
			aCustomMenu add: 'code pane menu...' translated
				action: #yellowButtonActivity.
			aCustomMenu add: 'code pane shift menu....' translated
				action: #shiftedYellowButtonActivity].
	outer := self owner.
	((outer isKindOf: PolygonMorph) and: [outer isOpen]) 
		ifFalse: [
			(container isNil or: [container fillsOwner not]) 
				ifTrue: [
					aCustomMenu add: 'fill owner''s shape' translated action: #fillingOnOff]
				ifFalse: [
					aCustomMenu add: 'rectangular bounds' translated action: #fillingOnOff].
			(container isNil or: [container avoidsOcclusions not]) 
				ifTrue: [
					aCustomMenu add: 'avoid occlusions' translated action: #occlusionsOnOff]
				ifFalse: [
					aCustomMenu add: 'ignore occlusions' translated action: #occlusionsOnOff]]! !


!TextMorphForEditView methodsFor: 'editing' stamp: 'jmv 9/2/2009 13:52'!
handleInteraction: interActionBlock
	"Overridden to pass along a model to the editor for, eg, link resolution, doits, etc"

	self editor model: editView model.  "For evaluateSelection, etc"
	^ super handleInteraction: interActionBlock! !

TextMorphForEditView removeSelector: #handleInteraction:fromEvent:!
TextMorph removeSelector: #handleInteraction:fromEvent:!
TextEditor removeSelector: #blinkPrevParen!
TextEditor removeSelector: #cursorDown:!
TextEditor removeSelector: #dispatchOnCharacter:with:!
TextEditor removeSelector: #insertTypeAhead:!
TextEditor removeSelector: #moveCursor:forward:specialBlock:!
TextEditor removeSelector: #readKeyboard!
CellStyleEditor removeSelector: #dispatchOnCharacter:with:!
SimpleEditor removeSelector: #dispatchOnCharacter:with:!
SimpleEditor removeSelector: #insertTypeAhead:!
SimpleEditor removeSelector: #readKeyboard!
OneLineEditorMorph removeSelector: #handleInteraction:fromEvent:!
InputSensor removeSelector: #eventQueue:!
InputSensor removeSelector: #inputProcess!
HandMorph removeSelector: #checkForMoreKeyboard!
CellStyleEntryFieldLW removeSelector: #handleInteraction:fromEvent:!
EntryField2LW removeSelector: #handleInteraction:fromEvent:!
Editor removeSelector: #closeTypeIn:!
Editor removeSelector: #initialize!
Editor removeSelector: #moveCursor:forward:specialBlock:!
Editor removeSelector: #sensor:!
!classDefinition: #Editor category: #'System-Text'!
Object subclass: #Editor
	instanceVariableNames: 'morph selectionShowing'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'System-Text'!
Smalltalk removeClassNamed: #KeyboardBuffer!
