From jrobbins@nation.ICS.UCI.EDU Sat Feb 25 22:20 CST 1995
Received: from a.cs.uiuc.edu by sal.cs.uiuc.edu with SMTP id AA03499
  (5.67b/IDA-1.5 for <smarch@sal.cs.uiuc.edu>); Sat, 25 Feb 1995 22:20:49 -0600
Received: from speedy.cs.uiuc.edu by a.cs.uiuc.edu with SMTP id AA06882
  (5.67a/IDA-1.5 for <smarch@cs.uiuc.edu>); Sat, 25 Feb 1995 22:20:41 -0600
Received: by speedy.cs.uiuc.edu (5.17/Tek) 
	id AA16762; Sat, 25 Feb 95 22:20:08 CST
Received: from nation.ics.uci.edu by paris.ics.uci.edu id aa20831;
          25 Feb 95 20:18 PST
Return-Path: <jrobbins@nation.ICS.UCI.EDU>
To: archive-manager@speedy.cs.uiuc.edu
Subject: st archive submission: smalltalk-emacs 0.5
Date: Sat, 25 Feb 1995 20:18:25 -0800
From: Jason Elliot Robbins <jrobbins@nation.ICS.UCI.EDU>
Message-Id:  <9502252018.aa20831@paris.ics.uci.edu>
Content-Type: text
Content-Length: 34619
Status: RO


Please install my submission in the appropriate directory for
VisualWorks2.0 tools.

It is a set of key bindings that makes life easier for emacs users.

-jason

BEGIN--cut-here--cut-here

"	NAME		smalltalk-emacs
	AUTHOR		Jason Robbins <jrobbins@ics.uci.edu>
	CONTRIBUTOR	Jason Robbins <jrobbins@ics.uci.edu>
	FUNCTION 	emacs keys and behavior for text editing in ParagraphEditor
	ST-VERSIONS	4.1
	PREREQUISITES	 
	CONFLICTS	 
	DISTRIBUTION	world
	VERSION		0.5
	DATE		Feb 1995
	SUMMARY		smalltalk-emacs

Thanks to Markus Geltz <geltz@informatik.uni-stuttgart.de> for providing 
the basic key bindings. 

Jason's contributions are in the implementation of many of the more 
complex commands that make emacs so useful: the kill ring, the universal 
argument, the buffer list, command and name completion, abbreviations, 
dynamic abbreviations, multiple levels of undo. In most cases that names 
of the methods are very similiar to the names of the corresponding 
functions in emacs and they really do share some of the rich power of 
emacs.  

This package is free for use to use. It is copyrighted by Jason 
Robbins (c) 1994. You are allowed to use it under the following terms: 
  You may not redistribute it for direct commericial advantage.
  You may make your own modifications and redistribute so long as I am
  mentioned and appropriate credit is given.
  I would enjoy a postcard or email thank-you note.

This package is free. But if you think that I do good work and would
like to support me in developing future smalltalk enhancements:
Hire me as a consultant, send me your requests, or send checks to 
      Jason Robbins
      230 Venado Ave.
      Thousand Oaks, CA, 91320"

"The following key bindings are availible by default. They implement a
strict subset of the default emacs key bindings.

Ctrl-f				forward_character
Ctrl-b				backward_character
Ctrl-n				next_line
Ctrl-p				previous_line
Ctrl-a				begining_of_line
Ctrl-e				end_of_line
ESC-f				forward_word
ESC-b				backward_word
Ctrl-d				delete_character
ESC-d				kill_word_forward
ESC-Delete			kill_word_backward
Ctrl-v				scroll_up
ESC-v				scroll_down
Ctrl-t				transpose_characters
Ctrl-k				kill_line
Ctrl-y				yank
ESC-<				begining_of_buffer
ESC->				end_of_buffer
M-y				yank_pop
Ctrl-u				universal_argument
C-Space 			set_mark
C-x C-s 			exchange_point_and_mark
C-x C-s				accept
M-/				expand_dabbrev
C-w				kill_region (cut)
M-w				copy_region_as_kill (copy)
M-space				just_one_space
C-x +				add_global_abbrev
C-l				refresh_view
-------- future commands
C-x C-k 			kill_buffer
C-x (				begin_kbd_macro
C-x )				end_kbd_macro
C-x e				execute_kbd_macro
M-x				execute_extended_command
C-o				open_line
C-s				isearch_forward
C-x b				switch_to_method
C-x C-b				method_list (findMethod)
C-x C-e				evaluate_expression (printIt)
M-x global_set_key 		bind a key to a command
M-l				lowercase_word
M-u				uppercase_word
"

''!

ControllerWithMenu subclass: #ParagraphEditor
  instanceVariableNames: 'beginTypeInIndex emphasisHere dispatchTable 
    charComposer textHasChanged undoHistory universalArgument isRecording 
    point marks lastCmd runLength editorState searchString dabbrevSearchIndex'
  classVariableNames: 'CodeYellowButtonMenu CompilationErrorSignal 
    Keyboard PreviousSelections TextEditorYellowButtonMenu UndoSelection 
    AbbrevDict PoppedKill'
  poolDictionaries: 'TextConstants '
  category: 'Interface-Text'!


!ParagraphEditor methodsFor: 'selecting'!

refresh
	view invalidate!

selectAt: characterIndex 
	"Place the caret before the character at position characterIndex.  Make
	certain the selection is in the view."

	point := characterIndex.
	view selectAt: characterIndex! !

!ParagraphEditor methodsFor: 'sensor access'!

processRedButton
	"The user pressed the select mouse button, meaning create a new text selection.
	Highlighting the selection is carried out by the paragraph itself.  Double
	clicking causes a selection of the area between the nearest enclosing delimiters;
	extension is based on both ends if different."

	self trackMouseSelection.
	point := self selectionStopIndex.
	self setEmphasisHere! !


!ParagraphEditor methodsFor: 'accessing'!

region_contents
  "return the Text between point and mark"
  ^self text
		copyFrom: (self point min: self mark)
		to: (self point max: self mark)-1!

region_contents_string
  "return the Text between point and mark"
  ^self text string
		copyFrom: (self point min: self mark)
		to: (self point max: self mark)-1!

poppedKill
	PoppedKill isNil ifTrue: [poppedKill := ''].
  ^ poppedKill!

poppedKill: aString
	PoppedKill := aString!

runLength
	runLength isNil ifTrue: [runLength := 0].
  ^ runLength!

runLength: anInt
  runLength := anInt!

universalArgument
  universalArgument isNil ifTrue: [universalArgument := 1].
  ^ universalArgument!

universalArgument: aNum
  universalArgument := aNum!
  
isRecording
	^isRecording == true!
	
isRecording: aBool
	isRecording := aBool!

lastCmd
  lastCmd isNil ifTrue: [lastCmd := #noCommand ].
 	^ lastCmd!

lastCmd: aCmd
	lastCmd := aCmd!

marks
	marks isNil ifTrue: [ marks := OrderedCollection new: 5 ].
	^ marks!
	
marks: aCltn
	marks := aCltn!

killRing: aCltn
	^ self class killRing: aCltn!
	
killRing
	^ self class previousSelections! !

!ParagraphEditor methodsFor: 'editing-abbrevs'!

abbrevDict
	^ self class abbrevDict!

add_abbrev: aShortString for: aLongString
	"define a new abbreviation"
	self abbrevDict at: aShortString asUppercase put: aLongString asUppercase.
	self abbrevDict at: aShortString asLowercase put: aLongString asLowercase.
	self abbrevDict at: aShortString put: aLongString.
  !
	
find_abbrev: aShortString
	^ self abbrevDict at: aShortString ifAbsent: [aShortString]!
	
add_global_abbrev: universalArg
	"the user has just typed a long string, and the command to define an 
	abbrev, now prompt for the short string and add it. Use the universal 
	argument to indicate how many words backwards to use as the long string"
	"Jason Robbins 7/24/94"
	| longStr shortStr |
	self interactive.
	self saveExcursion: [
		self set_mark.
		universalArg timesRepeat: [ self backward_word ].
		longStr := self region_contents_string.
		shortStr := DialogView request: 'Enter the short form'.
		(shortStr notNil and: [shortStr size > 0])
			ifTrue: [ self add_abbrev: shortStr for: longStr ] ]!

expand_abbrev: aChar
	"The user just typed in a short form and the command to expand. If the 
	short word just before point is a valid short form of some abbrev then 
	replace it with the long form, otherwise leave it as is. In either case, 
	insert the command character after the (expanded) text, iff it is a 
	printable character"
	"Jason Robbins 7/24/94"
	| short long |
	self interactive.
	self someSelected
		ifTrue: [ ^ self replaceSelectionWithString: aChar keyCharacter asSymbol asString].
	self set_mark; backward_word; exchange_point_and_mark.
	short := self region_contents_string.
	long := self find_abbrev: short .
	self select_region; replaceSelectionWithString: long.
	aChar notNil
		ifTrue: [ self appendAfterSelection: aChar keyCharacter asSymbol asString ].
	self pop_mark
	!

expand_dabbrev
  "the user has just typed part of a word and would like the editor to 
  type the rest of the word automatically based on occurances of words 
  with that prefix in the same piece of text. If there is more than one 
  word with that prefix then cycle between them. Emacs searches 
  backwards, I search forward for now."
  "Jason Robbins 7/30/94"  
  
  | longString tmpSearchIndex |
  self interactive.
  self deselect.
  editorState = #dabbrevCycle
  	ifTrue: [ dabbrevSearchIndex := dabbrevSearchIndex + 1 ]
    ifFalse:[ 
    	  editorState = #dabbrevAbandoned
    	  	ifTrue: [ self expand_abbrev: nil ]
    	  	ifFalse:[ | res | 
			  dabbrevSearchIndex := 1.
			  res := self previousWord.
	  		  searchString := res at: 1.
			  searchStartIndex := (res at: 2) - 1] ].

  tmpSearchIndex := 
  	self text findString: ' ', searchString 
  		startingAt: dabbrevSearchIndex.
(tmpSearchIndex = 0 or: [tmpSearchIndex = searchStartIndex])
  	ifTrue: [   tmpSearchIndex := 
  	self text findString: '	', searchString 
  		startingAt: dabbrevSearchIndex].
(tmpSearchIndex = 0 or: [tmpSearchIndex = searchStartIndex])
  	ifTrue: [   tmpSearchIndex := 
  	self text findString: '#', searchString 
  		startingAt: dabbrevSearchIndex].
(tmpSearchIndex = 0  or: [tmpSearchIndex = searchStartIndex])
  	ifTrue: [   tmpSearchIndex := 
  	self text findString: '(', searchString 
  		startingAt: dabbrevSearchIndex].
(tmpSearchIndex = 0 or: [tmpSearchIndex = searchStartIndex])
  	ifTrue: [   tmpSearchIndex := 
  	self text findString: '[', searchString 
  		startingAt: dabbrevSearchIndex].

 dabbrevSearchIndex := tmpSearchIndex.
  (dabbrevSearchIndex = 0 or: [tmpSearchIndex = searchStartIndex])
  	ifTrue: [ longString := searchString.
  				"if none are satisfactory, go back to original"
  				editorState := #dabbrevAbandoned.
					dabbrevSearchIndex := 1]
  	ifFalse:[ longString := self wordAt: dabbrevSearchIndex + 1. 
  				editorState := #dabbrevCycle ].
  self set_mark; backward_word; exchange_point_and_mark; select_region; 
  	replaceSelectionWithString: longString; pop_mark! 
  	
wordAt: anIndex
	"return a string of the word starting at anIndex"
	"Jason Robbins 7/30/94"
	
	| word |	
	self saveExcursion: [ 
		self goto_character: anIndex;
			set_mark; forward_word; select_region.
		word := self selection. self deselect ].
	^ word!

previousWord
	"return a string with the word just before point"
	"Jason Robbins 7/30/94"

	| word start |
	self saveExcursion: [
		self set_mark; backward_word; exchange_point_and_mark; select_region.
		start := self mark. word := self selection. self deselect ].
	^ Array with: word with: start! !	

!ParagraphEditor methodsFor: 'dispatch'!

readKeyboard
  "read keystrokes from the sensor and accumulate until a command is 
  given or there are no more keystrokes then insert those characters. If 
  a command is given execute it and continue processing remaning 
  keystrokes."
  "modified by Jason Robbins 7/16/94"
  
  | string stream |
  self deselect.
  stream := (string := String new:1) writeStream.
  [self sensor keyboardPressed] whileTrue: [
  	self dispatchTable 
  		add: self sensor keyboardEvent
  		do: [:char :cmd |
  			(cmd = #normalCharacterKey:)
  				ifTrue: [ self normalKeyboardEvent: char
  										do: [:c | stream nextPut: c ] ]
  				ifFalse:[ self flushChars: string or: stream.
									  stream := (string := String new:1) writeStream.
  						  		self executeCommand: cmd with: char ]	  
  	]]. "end while"
  self flushChars: string or: stream. 
  view selectAndScroll!
  
executeCommand: aCmd with: anArg
	"run the given command with the given argument. Also maintain 
	 undo info and support macro recording. Even normal typing is recorded 
	 via this mechanism" 
	"Jason Robbins 7/16/94"
	
	"do commands have to be selectors or can I have a command object?"

	self recordStatePriorTo: aCmd.
	self addToMacroIfRecording: aCmd with: anArg.
	(self acceptsUniversalArgument: aCmd)
		ifTrue: [ (self acceptsArg: aCmd)
					ifTrue: [ self 	perform: aCmd "selector"
									with: anArg 
									with: self universalArgument ]
					ifFalse:[ self 	perform: aCmd "selector" 
									with: self universalArgument ] ]
		ifFalse:[ (self acceptsArg: aCmd)
					ifTrue: [ self universalArgument 
								timesRepeat: [ self	perform: aCmd "selector"
									             with: anArg.
								               self lastCmd: aCmd. ] ]
					ifFalse:[ self universalArgument 
								timesRepeat: [ self	perform: aCmd "selector".
								               self lastCmd: aCmd.] ] ].
	
	(aCmd ~= #universal_argument:)
		ifTrue: [self universalArgument: 1. self lastCmd: aCmd.]!

acceptsUniversalArgument: aCmd
	"Does the given command know how to interpert the universal argument in 
	some special way. By default, no"
	^ #(add_global_abbrev: universal_argument:
	next_line: previous_line: kill_line: ) includes: aCmd!
	
acceptsArg: aCmd
	"does the given command accept an argument such as the character that 
	invoked the command?"
	^ #( expand_abbrev: appendToSelection: insertString: cursorLeftKey:
		cursorRightKey:
		cursorDownKey: cursorUpKey: displayIfTrueKey: displayIfFalseKey:
		ignoreInputKey: changeEmphaisiKey: replaceSelectionWithString:
		processCRKey: ) includes: aCmd!	

flushChars: string or: stream
	"insert the characters stored in one of the two places given"
	stream isEmpty ifTrue: [^ self ].
	editorState := #normal.
	self 
		executeCommand: #replaceSelectionWithString:
		with: (stream position = string size
				ifTrue: [ string ] ifFalse: [stream contents ])!

isKill: aCmd
	"reply true iff the given command is some kind of kill"
	^#(kill_line: kill_word_backward kill_word_forward) includes: aCmd!

mayCauseChange: aCmd
	"reply true iff the comamnd would modify the text"
	^(#(forward_character backward_character next_line: previous_line:
	begining_of_line end_of_line begining_of_buffer 
	end_of_buffer forward_word backward_word isearch isearch_add_char
	isearch_find_next add_global_abbrev:
	) includes: aCmd) not! !
	
		
!ParagraphEditor methodsFor: 'editing-macros'!

addToMacroIfRecording: aCmd with: anArg
	(self isRecording)
		ifTrue: [ self addToMacro: aCmd with: anArg]!
		
addToMacro: aCmd with: anArg
	^ self needsMoreWork.!
	
start_kbd_macro
	self isRecording: true!
	
end_kbd_macro
	self isRecording: false! !
	
	

!ParagraphEditor methodsFor: 'editing-undo'!

recordStatePriorTo: aCmd
	"record the current state of the text in a cltn of past states (not all 
	that space effiecent I know, but usually text is fairly small). Same 
	some states if the same kind of command is issued multiple times in a 
	row then those sequences can be grouped into 'runs'. EX: sequences of 
	appendToSelection: and delete_character_backward and delete_character 
	are grouped into runs of length <= 20"
	"Jason Robbins 7/16/94"

	(self mayCauseChange: aCmd) ifFalse: [ ^ self ].
	(aCmd "commandKind" = self lastCmd "commandKind"
			and: [ self runLength <= self class maxRunLength ])
			ifTrue: [ self runLength: self runLength + 1 ]
			ifFalse: [ self recordState. self runLength: 0 ]!
			
recordState
	"record the current state of the text in a cltn of past states, but 
	limit the size of the undo history"
	
	| hist state |
	hist := self undoHistory.
	(hist size > self class undoHistoryMaxSize)
		ifTrue: [hist removeFirst].
	state := Array new:3.
	state at: 1 put: 	self text copy. "??????"
	state at: 2 put: 	self selectionStartIndex.
	state at: 3 put: 	self selectionStopIndex.
	hist addLast: state.
	^ self!

undo
	"restore the state of the editor to where it was previously"
	"Jason Robbins 7/30/94"
	| hist state |
	self interactive.
	hist := self undoHistory.
	hist isEmpty ifTrue: [^view flash].
	state := 	self undoHistory removeLast.
	self paragraph text: (state at: 1).
	self selectionStartIndex: (state at: 2).
	self selectionStopIndex: (state at: 3).
	self refresh !

undoHistory
	"for now use an OC. But really a rolling buffer class would be better 
	and simple to make"
	undoHistory isNil ifTrue: [ undoHistory := OrderedCollection new ].
	^ undoHistory! !
	
!ParagraphEditor methodsFor: 'editing-searching'!

isearch
	self interactive.
	editorState := #isearchStart!

isearch_add_char: anEvent
	editorState = #isearchStart 
		ifTrue: [ searchString := ''. editorState := #isearchContinue ].
	searchString := searchString , anEvent keyCharacter asSymbol asString.
	self findAndSelect: searchString!
	
findAndSelect: aString
	"use selectionStartIndex instead of stop"
	"beep/flash if not found xxxxxxxx"!
	
isearch_find_next
	editorState := #isearchContinue.
	self selectionStartIndex: self selectionStartIndex + 1.
	self findAndSelect: searchString! !
	
!ParagraphEditor methodsFor: 'editing-internals'!
	
max_point
	^ self paragraph text size + 1.!
	
min_point
	^ 1!

set_mark 
	self push_mark: self point.!
	
mark
	^ self marks last!

pop_mark
	^ self marks isEmpty
		ifFalse: [ self marks removeLast]
		ifTrue: [ self point ]!

someSelected
	"Returns true if some text is selected"
	^(self selectionStartIndex  ~= self selectionStopIndex)!
	
push_mark: pos
	self marks addLast: pos!
	
replaceSelectionWithString: aString
	"replace the selected text with the given string"
	self point >= self selectionStartIndex
		ifTrue: [ point := self point - (self selection size) + aString size ].
	self replaceSelectionWith: 
			(Text	string: aString
					emphasis: emphasisHere).
	self selectAt: point!

replaceRegionWithString: aString
	"replace the region text with the given string"
	| p m |
	p := self point. m := self mark.
	self replaceFrom: (p min: m)
		to: (p max: m)-1
		with: (Text	string: aString
			emphasis: emphasisHere).
	p > m
		ifTrue: [ point := m + aString size ]!

appendAfterSelection: aString
	"deselect the current selection and append the given string after it"
	"Jason Robbins 7/24/94"
	
	self selectAt: self point.
	self replaceSelectionWithString: aString.
	self goto_character: self point.
	self selectAt: self point!
	
point
	point isNil
		ifTrue: [point :=
			self selectionStartIndex max: self selectionStopIndex].
	^ point !

point: pos
	self goto_character: pos!

characterAt: pos
 ^(self paragraph characterBlockForIndex: pos) character!
 
interactive
	"do nothing, just a way to mark methods that the user can execute directly"
	^self!


saveExcursion: aBlock
	"save the state of the editor, then execute aBlock then restore the 
	state. This make writting some commands much easier"
	"Jason Robbins 7/24/94"
	
	| marksCopy killsCopy pointCopy  res |
	marksCopy := self marks copy.
	killsCopy := self killRing copy.
	pointCopy := self point.
	res := aBlock value.
	self marks: marksCopy.
	self killRing: killsCopy.
	self point: pointCopy.
	^ res!

insertString: aString
	"put the given string into the buffer at point"
	self selectFrom: self point to: self point -1.
	self replaceSelectionWithString: aString.
	"self point: self point + aString size"!

accumulateKill: aString
	"if the last command was also a kill then append to the front or enfd of 
	the current top of the kill ring, if not then make a new kill ring entry"
	"Jason Robbins 7/17/94"
	
	(self isKill: lastCmd)
		ifTrue: [ self appendToLastKill: aString ]
		ifFalse:[ self addKill: aString ]!

prependKill: aString
	"if the last command was also a kill then append to the front or enfd of 
	the current top of the kill ring, if not then make a new kill ring entry"
	"Jason Robbins 7/17/94"
	
	(self isKill: lastCmd)
		ifTrue: [ self prependToLastKill: aString ]
		ifFalse:[ self addKill: aString ]!

appendToLastKill: aString
	"instead of adding aString as a seperate kill on the killRing,
	append it to the last kill to make one big kill"
	"Jason Robbins 8/2/94"

	self class informWindowSystemOfNewSelection:
		(self class appendToLastKill: aString)!
	
prependToLastKill: aString
	"instead of adding aString as a seperate kill on the killRing,
	append it to the last kill to make one big kill"
	"Jason Robbins 8/2/94"

	self class informWindowSystemOfNewSelection:
		(self class prependToLastKill: aString)!
	

addKill: aString
	"add a new entry to the kill ring"

	self class informWindowSystemOfNewSelection: aString.
	self class addKill: aString!

skipForwardWhile: aBlock
	self skip: 1 while: aBlock!
	
skipBackwardWhile: aBlock
	self skip: -1 while: aBlock.!


skip: dir while: aBlock
	| pos maxpoint minpoint |
	pos := self point.
	maxpoint := self max_point. 
	minpoint := self min_point.
	[pos >= minpoint and: [pos < maxpoint and: [ 
		aBlock value: (self characterAt: pos) ] ] ]
		whileTrue: [ pos := pos + dir ].
  (pos < minpoint) ifTrue: [pos := minpoint].
  (pos > maxpoint) ifTrue: [pos := maxpoint].
	self goto_character: pos! !

!ParagraphEditor methodsFor: 'editing-cut and paste'!

kill_word_backward  
	self interactive.
	self set_mark .
	self backward_word .
	self exchange_point_and_mark.
	self kill_region_and_prepend!

copy_region_as_kill
	self interactive.
	self select_region .
	self accumulateKill: self selection.
	self deselect!
	
copy_region_as_kill_and_prepend
	self interactive.
	self select_region .
	self prependKill: self selection.
	self deselect!
	
kill_region 
	self interactive.
	self copy_region_as_kill.
	self delete_region!

kill_region_and_prepend 
	self interactive.
	self copy_region_as_kill_and_prepend.
	self delete_region!

kill_word_forward    
	self interactive.
	self set_mark; forward_word; kill_region; pop_mark!

kill_line: uarg  
	"Kill until the end of line. If we are already at the end of line then 
	 kill the newline character itself."
	self interactive.
	self set_mark.
	uarg = 1
		ifTrue: [ 
			(self characterAt: self point) = CR
				ifTrue: [ self forward_character ]
				ifFalse:[ self end_of_line ] ]
		ifFalse: [	
			self next_line: uarg-1;
				end_of_line; forward_character ].
	self kill_region.
	self pop_mark! 

popKillRing
	(self previousSelections isEmpty)
		ifTrue: [ self error: 'No selection to pop'].
	^ self previousSelections removeFirst!

popKillRingAndRemember
	self poppedKill: self popKillRing!
	
yank 
	| ps |
	self interactive.
	ps := self previousSelections.
	(ps isEmpty or: [ ps first isNil ] )
		ifTrue: [ ^ DialogView warn: 'Nothing to paste' ].
	self set_mark_command.
	self replaceSelectionWithString: (ps first)!
	
yank_pop 
	| ps |
	self interactive.
	ps := self previousSelections.
	(lastCmd = #yank or: [lastCmd = #yank_pop])
		ifTrue: [ self popKillRingAndRemember.
							ps addLast: self poppedKill.
		          self delete_region.
		          self yank ]! !

!ParagraphEditor methodsFor: 'private'!

silentCut
	"Remove the current selection and place it in the paste buffer.  Do not
	affect visual selection."

	| cur |
	self replaceSelectionWithString: ''.
	cur := self undoSelection copy.
	self currentSelection: cur.! !

!ParagraphEditor methodsFor: 'editing-other'!

just_one_space  
	"replace all spaces and tabs around point with a single space"
	self interactive.
	self replaceSurroundingSpacesWith: ' '!
	
delete_spaces
	self interactive.
	self replaceSurroundingSpacesWith: ''!

replaceSurroundingSpacesWith: replacementString
	"replace all spaces and tabs around point with the given string"
	"Jason Robbins 7/30/94"
	
	self backward_character;
		skipBackwardWhile: [:c | c = Space or: [ c = Tab ] ];
		forward_character;
		set_mark;
		skipForwardWhile: [:c | c = Space or: [ c = Tab ] ];
		select_region;
		replaceSelectionWithString: replacementString;
		goto_character: self selectionStopIndex;
		pop_mark!

delete_region 
	self interactive.
	self select_region.
	self replaceSelectionWithString: String new!	

delete_character_forward    
	self interactive.
	(self point <= self max_point)
		ifTrue: [ 	self forward_character .
					self delete_character_backward  ]!

delete_character_backward 
	| startIndex |
	self interactive.
	self selectionStartIndex < self selectionStopIndex
		ifTrue: [self silentCut]
		ifFalse: 
			[startIndex := 1 max: self selectionStartIndex - 1.
			self selectionStartIndex: startIndex.
			self replaceSelectionWithString: '']!

transpose_characters  
	self interactive.
	self set_mark; backward_character; kill_region.
	self deselect; forward_character; forward_character; yank.
	self popKillRing; pop_mark!

universal_argument: oldua 
	"Set the universal argument to 4 times the prevous value, and allow 
	 a number to be typed in. For now just do the 4, 16, 64 thing"
	"Jason Robbins 7/12/94"
	 
	self interactive.
	self universalArgument: oldua * 4.!


execute_extended_command 
	"Let the user type in the name of a command to be executed. Use the 
	request:withCompleteion: if it is available."
	"jer 7/12/94"
	| cmdName |
	
	self interactive.
	cmdName := ((DialogView respondsTo: #request:withCompletions:)
				ifTrue: [DialogView request: 'enter command name'
									withCompletions: (Smalltalk sendersOf: #interactive)]
				ifFalse: [DialogView request: 'enter command name']).
	cmdName notNil
		ifTrue: [(self respondsTo: cmdName asSymbol)
					ifTrue: [ self perform: cmdName asSymbol]
					ifFalse: [ DialogView warn: 'Unknown command "', cmdName ,'"'] ].
	! !					

		
!ParagraphEditor methodsFor: 'editing-moving'!

set_mark_command
	self interactive; pop_mark; push_mark: self point!

select_region 
	self interactive.
	self selectFrom: 
		((self point min: self mark) max: self min_point)
	to: 
		((self point max: self mark) min: self max_point)-1!

goto_end_of_selection
	self goto_character: (self selectionStartIndex max: self selectionStopIndex).
	self selectAt: point!

exchange_point_and_mark
	| tmp |
	self interactive.
	tmp := self mark.
	self set_mark_command.
	self goto_character: tmp!

goto_character: pos
	point := (pos max: self min_point) min: self max_point.
	self selectionStartIndex: point.
	self selectionStopIndex: point!

next_line: uarg
	self interactive.
	uarg timesRepeat: [ self cursorDownKey: nil ]!

previous_line: uarg
	self interactive.
	uarg timesRepeat: [ self cursorUpKey: nil ]!

scroll_down
	self interactive.
	self notImplementedYet!

scroll_up
	self interactive.
	self notImplementedYet!
		
forward_character
	self interactive.
	self goto_character: self point + 1!
		
backward_character
	self interactive.
	self goto_character: self point - 1!
		
		
begining_of_buffer
	self interactive.
	self goto_character: self min_point!

end_of_buffer
	self interactive.
	self goto_character: self max_point!

begining_of_line
	self interactive.
	self backward_character;
		skipBackwardWhile: [:c | (c = CR) not].
  (self characterAt: self point) = CR
		ifTrue: [ self forward_character ]!

end_of_line
	self interactive.
	self skipForwardWhile: [:c | (c = Ctrlm) not]!


backward_word
	| c |
	self interactive.
	self backward_character;
		skipBackwardWhile: [:c | (self isTokenish: c) not ];
		skipBackwardWhile: [:c | (self isTokenish: c) ].
	c := (self characterAt: point).
	(c isNil or: [(self isTokenish: c)])
		ifFalse: [self forward_character]!
	
forward_word 
	self interactive.
	self skipForwardWhile: [:c | (self isTokenish: c) not ].
	self skipForwardWhile: [:c | (self isTokenish: c) ]! !
		

!ParagraphEditor class methodsFor: 'accessing'!

undoHistoryMaxSize
	^30!

maxRunLength
	^20!

maxKillRingSize
	^20!

killRing
	^ self previousSelections!

killRing: aCltn
	PreviousSelections := aCltn!

abbrevDict
	AbbrevDict isNil
		ifTrue: [ AbbrevDict := Dictionary new.
		          AbbrevDict at: 'teh' put: 'the'.
		          AbbrevDict at: 'OC' put: 'OrderedCollection'.
		          AbbrevDict at: 'Tr' put: 'Transcript'.
		          AbbrevDict at: 'Jaosn' put: 'Jason'.].
	^ AbbrevDict! !

!ParagraphEditor class methodsFor: 'private-selection access'!

appendToLastKill: aText
	"instead of adding aString as a seperate kill on the killRing,
	append it to the last kill to make one big kill"
	"Jason Robbins 8/2/94"
	| bigKill |

	PreviousSelections isEmpty
		ifTrue: [ ^ self addKill: aText ]
		ifFalse:[ bigKill := PreviousSelections removeFirst , aText.
			        ^ self addKill: bigKill ]!

prependToLastKill: aText
	"instead of adding aString as a seperate kill on the killRing,
	append it to the last kill to make one big kill"
	"Jason Robbins 8/2/94"
	| bigKill |

	PreviousSelections isEmpty
		ifTrue: [ ^ self addKill: aText ]
		ifFalse:[ bigKill :=  aText, PreviousSelections removeFirst.
			        ^ self addKill: bigKill ]!

addKill: aText 
	(PreviousSelections includes: aText)
		ifTrue: [PreviousSelections remove: aText].
	PreviousSelections size >= self maxKillRingSize
		ifTrue: [PreviousSelections removeLast].
	PreviousSelections addFirst: aText.
	^ aText! !



!ParagraphEditor class methodsFor: 'class initialization'!

informWindowSystemOfNewSelection: aString
	"let X Windows know about the current selection"

	Object errorSignal 
		handle: [:e |
			Screen default putExternalSelection: ''.
			e return ]
		do: [ Screen default putExternalSelection: aString ]!

global_set_key: aKeySequence toCmd: aSelector
	"Bind a key sequence to a command. Currently only one and two character 
	sequences are supported"
	"Jason Robbins 7/24/94"
	"Maybe I should parse the strings that emacs uses, e.g. '\C-f'"
		
	((aKeySequence isKindOf: Character) or: [ aKeySequence isKindOf: Symbol ])
		ifTrue: [^ Keyboard bindValue: aSelector to: aKeySequence].
	(aKeySequence isKindOf: Collection)
		ifTrue: [
			aKeySequence size = 1
				ifTrue: [^ Keyboard bindValue: aSelector to: aKeySequence first].
			aKeySequence size = 2
				ifTrue: [^ Keyboard	bindValue: aSelector 
									to: (aKeySequence at: 1)
									followedBy: (aKeySequence at: 2)] ].!
			

bindEmacsKeys
	"ParagraphEditor bindEmacsKeys"  

	(Keyboard := DispatchTable new) defaultForCharacters: #normalCharacterKey:.
	Keyboard defaultForNonCharacters: #ignoreInputKey:.
	self global_set_key: Ctrla toCmd: #begining_of_line.
	self global_set_key: #Home toCmd: #begining_of_line.
	self global_set_key: Ctrld toCmd: #delete_character_forward.
	self global_set_key: BS toCmd: #delete_character_backward.
	self global_set_key: "delete" 127 asCharacter
		toCmd: #delete_character_backward.
	self global_set_key: Ctrle toCmd: #end_of_line.
	self global_set_key: #End toCmd: #end_of_line.
	self global_set_key: Ctrlf toCmd: #forward_character.
	self global_set_key: Ctrlb toCmd: #backward_character.
	self global_set_key: Ctrlp toCmd: #previous_line:.
	self global_set_key: Ctrln toCmd: #next_line:.
	self global_set_key: #Right toCmd: #forward_character.
	self global_set_key: #Left toCmd: #backward_character.
	self global_set_key: #Up toCmd: #previous_line:.
	self global_set_key: #Down toCmd: #next_line:.
	self global_set_key: Ctrlt toCmd: #transpose_characters.
	self global_set_key: Ctrlv toCmd: #scroll_up.
	"self global_set_key: '\M-v' toCmd: #scroll_down."
	self global_set_key: Ctrlk toCmd: #kill_line:.
	self global_set_key: Ctrly toCmd: #yank.
	self global_set_key: Ctrlu toCmd: #universal_argument:.
	self global_set_key: Ctrlw toCmd: #kill_region.
	self global_set_key: Ctrll toCmd: #refresh.
	self global_set_key: Ctrlo toCmd: #select_region.
	self global_set_key: 0 asCharacter toCmd: #set_mark_command. "Ctrl space"
	self global_set_key: 31 asCharacter toCmd: #undo. "Ctrl underscore"

	self global_set_key: $  toCmd: #expand_abbrev:.
	self global_set_key: $( toCmd: #expand_abbrev:.
	self global_set_key: $) toCmd: #expand_abbrev:.
	self global_set_key: $] toCmd: #expand_abbrev:.
	self global_set_key: $[ toCmd: #expand_abbrev:.
	self global_set_key: $. toCmd: #expand_abbrev:.


	self global_set_key: (Array with: Ctrlx with: Ctrls) 
		toCmd: #accept.
	self global_set_key: (Array with: Ctrlx with: Ctrlk) 
		toCmd: #closeRequest.
	self global_set_key: (Array with: Ctrlx with: $u) 
		toCmd: #undo.
	self global_set_key: (Array with: Ctrlx with: Ctrlx) 
		toCmd: #exchange_point_and_mark.
	self global_set_key: (Array with: Ctrlx with: $e) 
		toCmd: #execute_kbd_macro.
	self global_set_key: (Array with: Ctrlx with: $+) 
		toCmd: #add_global_abbrev:.

	self global_set_key: (Array with: ESC with: Ctrlt) 
		toCmd: #displayIfTrueKey:.
	self global_set_key: (Array with: ESC with: Ctrlf) 
		toCmd: #displayIfFalseKey:.

	self global_set_key: (Array with: ESC with: $<)
		toCmd: #begining_of_buffer.
	self global_set_key: (Array with: ESC with: $>)
		toCmd: #end_of_buffer.

	self global_set_key: (Array with: ESC with: $y) 
		toCmd: #yank_pop.
	self global_set_key: (Array with: ESC with: $w) 
		toCmd: #copy_region_as_kill.	
	self global_set_key: (Array with: ESC with: $f) 
		toCmd: #forward_word.
	self global_set_key: (Array with: ESC with: $b) 
		toCmd: #backward_word.
	self global_set_key: (Array with: ESC with: $ ) 
		toCmd: #just_one_space.
	self global_set_key: (Array with: ESC with: $d) 
		toCmd: #kill_word_forward.
	self global_set_key: (Array with: ESC with: BS) 
		toCmd: #kill_word_backward.
	self global_set_key: (Array with: ESC with: 127 asCharacter) 
		toCmd: #kill_word_backward.
	self global_set_key: (Array with: ESC with: $/) 
		toCmd: #expand_dabbrev.
	self global_set_key: (Array with: ESC with: $\) 
		toCmd: #delete_spaces.
! !

!DispatchTable methodsFor: 'accessing'!

add: charEvent do: aBlock
	" Accept a character event from the input stream.
	Evaluate aBlock with each character that
	results from this.  (Normally 1, might be 0 if
	we are in the middle of a composition sequence,
	or 1 also in the case of an unrecognized sequence.)
	If we evaluated aBlock, answer the result of the
	evaluation, otherwise answer nil. "

	| char meta |
	char := charEvent keyValue.
	meta := charEvent metaState.
	^firstCharacter isNil
		ifTrue:
			[charEvent hasMeta
				ifTrue: [firstCharacter := ESC.
					    ^self add: (KeyboardEvent code: char
													meta: (meta maskClear: InputState metaMask))
							do: aBlock]
				ifFalse: [(self compose1: char)	"Check first character"
							ifTrue: [firstCharacter := char.  nil]
							ifFalse: [aBlock value: charEvent
															value: (self lookup: char meta: meta)]]]
		ifFalse:
			[| comp value |
			"Middle of a two character sequence"
			value := (comp := self compose2: firstCharacter with: char) == nil
				ifTrue: [aBlock value: charEvent value: (self lookup: char meta: meta)]
				ifFalse: [aBlock value: charEvent value: comp].
			firstCharacter := nil.
			value]! !

ParagraphEditor bindEmacsKeys.!

TextItemEditor initialize!

ParagraphEditor withAllSubclasses do:
	 [:class | class allInstances do: 
		[:edit | edit dispatchTable become: edit class dispatchTable]]!


"To Do:
  - Command Objects
  + select words.
    shift key to select on move
   yank and yank_pop
  - blink matching delimiter
  + cursor keys
  dynamic abbrevs never show same thing twice on one cycle
  dynamic abbrve targets without spaces
  subclass from ParagraphEditor into EmacsEditor and change
  "

Dialog warn: 'This package is free for you to use. It is copyrighted by Jason
Robbins (c) 1994. You are allowed to use it under the following terms: 
1.  You may not redistribute it for direct commericial advantage.
2.  You may make your own modifications and redistribute so long as I am
    mentioned and appropriate credit is given.
3.  I would enjoy a postcard or email thank-you note.

This package is free. But if you think that I do good work and would
like to support me in developing future smalltalk enhancements:
+  Hire me as a consultant (2+ years smalltalk, future PhD in S.E.)
+  Send your ideas, or
+  Send checks to
      Jason Robbins
      230 Venado Ave.
      Thousand Oaks, CA, 91320
      jrobbins@ics.uci.edu

See the top of the file-in for more details'!

END--cut-here--cut-here



