[Pkg] The Inbox: Tools-ul.134.mcz
commits at source.squeak.org
commits at source.squeak.org
Fri Oct 9 02:48:57 UTC 2009
A new version of Tools was added to project The Inbox:
http://source.squeak.org/inbox/Tools-ul.134.mcz
==================== Summary ====================
Name: Tools-ul.134
Author: ul
Time: 9 October 2009, 1:01:52 am
UUID: dcfa44c0-cf09-ce46-ac34-8348c991b9d1
Ancestors: Tools-ar.133
- added BlockClosure >> #timeProfile which opens a TimeProfileBrowser on the block
==================== Snapshot ====================
SystemOrganization addCategory: #'Tools-ArchiveViewer'!
SystemOrganization addCategory: #'Tools-Base'!
SystemOrganization addCategory: #'Tools-Browser'!
SystemOrganization addCategory: #'Tools-Browser-Tests'!
SystemOrganization addCategory: #'Tools-Changes'!
SystemOrganization addCategory: #'Tools-Debugger'!
SystemOrganization addCategory: #'Tools-Debugger-Tests'!
SystemOrganization addCategory: #'Tools-Explorer'!
SystemOrganization addCategory: #'Tools-File Contents Browser'!
SystemOrganization addCategory: #'Tools-FileList'!
SystemOrganization addCategory: #'Tools-FileList-Tests'!
SystemOrganization addCategory: #'Tools-Inspector'!
SystemOrganization addCategory: #'Tools-Process Browser'!
SystemOrganization addCategory: #'Tools-Protocols'!
----- Method: Utilities class>>browseRecentSubmissions (in category '*Tools') -----
browseRecentSubmissions
"Open up a browser on the most recent methods submitted in the image. 5/96 sw."
"Utilities browseRecentSubmissions"
| recentMessages |
self recentMethodSubmissions size == 0 ifTrue:
[^ self inform: 'There are no recent submissions'].
recentMessages := RecentSubmissions copy reversed.
RecentMessageSet
openMessageList: recentMessages
name: 'Recent submissions -- youngest first '
autoSelect: nil!
----- Method: Utilities class>>closeAllDebuggers (in category '*Tools') -----
closeAllDebuggers
"Utilities closeAllDebuggers"
Smalltalk isMorphic
ifTrue:
[(SystemWindow allSubInstances select: [:w | w model isKindOf: Debugger])
do: [:w | w delete]]
ifFalse:
[(StandardSystemController allInstances select: [:w | w model isKindOf: Debugger])
do: [:w | w closeAndUnscheduleNoTerminate]]!
----- Method: Utilities class>>openRecentSubmissionsBrowser (in category '*Tools') -----
openRecentSubmissionsBrowser
"Open up a browser on the most recent methods submitted in the image; reuse any existing one found in the world."
self currentWorld openRecentSubmissionsBrowser: nil!
----- Method: Utilities class>>recentSubmissionsWindow (in category '*Tools') -----
recentSubmissionsWindow
"Answer a SystemWindow holding recent submissions"
| recentMessages messageSet |
recentMessages := RecentSubmissions copy reversed.
messageSet := RecentMessageSet messageList: recentMessages.
messageSet autoSelectString: nil.
^ ToolBuilder build: messageSet
"Utilities recentSubmissionsWindow openInHand"
!
Object subclass: #ChangesOrganizer
instanceVariableNames: ''
classVariableNames: 'ChangeSetCategories ChangeSetNamesInRelease RecentUpdateMarker'
poolDictionaries: ''
category: 'Tools-Changes'!
!ChangesOrganizer commentStamp: 'pk 10/17/2006 09:25' prior: 0!
Changes organizer!
----- Method: ChangesOrganizer class>>allChangeSetNames (in category 'enumerating') -----
allChangeSetNames
^ self allChangeSets collect: [:c | c name]!
----- Method: ChangesOrganizer class>>allChangeSets (in category 'enumerating') -----
allChangeSets
"Return the list of all current ChangeSets"
^ChangeSet allChangeSets!
----- Method: ChangesOrganizer class>>allChangeSetsWithClass:selector: (in category 'enumerating') -----
allChangeSetsWithClass: class selector: selector
class ifNil: [^ #()].
^ self allChangeSets select:
[:cs | (cs atSelector: selector class: class) ~~ #none]!
----- Method: ChangesOrganizer class>>assuredChangeSetNamed: (in category 'services') -----
assuredChangeSetNamed: aName
"Answer a change set of the given name. If one already exists, answer that, else create a new one and answer it."
| existing |
^ (existing := self changeSetNamed: aName)
ifNotNil:
[existing]
ifNil:
[self basicNewChangeSet: aName]!
----- Method: ChangesOrganizer class>>basicNewChangeSet: (in category 'adding') -----
basicNewChangeSet: newName
^ChangeSet basicNewChangeSet: newName!
----- Method: ChangesOrganizer class>>belongsInAdditions: (in category 'class initialization') -----
belongsInAdditions: aChangeSet
"Answer whether a change set belongs in the Additions category, which is fed by all change sets that are neither numbered nor in the initial release"
^ (((self belongsInProjectsInRelease: aChangeSet) or:
[self belongsInNumbered: aChangeSet])) not!
----- Method: ChangesOrganizer class>>belongsInAll: (in category 'class initialization') -----
belongsInAll: aChangeSet
"Answer whether a change set belongs in the All category"
^ true !
----- Method: ChangesOrganizer class>>belongsInMyInitials: (in category 'class initialization') -----
belongsInMyInitials: aChangeSet
"Answer whether a change set belongs in the MyInitials category. "
^ aChangeSet name endsWith: ('-', Utilities authorInitials)!
----- Method: ChangesOrganizer class>>belongsInNumbered: (in category 'class initialization') -----
belongsInNumbered: aChangeSet
"Answer whether a change set belongs in the Numbered category. "
^ aChangeSet name startsWithDigit!
----- Method: ChangesOrganizer class>>belongsInProjectChangeSets: (in category 'class initialization') -----
belongsInProjectChangeSets: aChangeSet
"Answer whether a change set belongs in the MyInitials category. "
^ aChangeSet belongsToAProject!
----- Method: ChangesOrganizer class>>belongsInProjectsInRelease: (in category 'class initialization') -----
belongsInProjectsInRelease: aChangeSet
"Answer whether a change set belongs in the ProjectsInRelease category. You can hand-tweak this to suit your working style. This just covers the space of project names in the 2.9, 3.0, and 3.1a systems"
| aString |
^ ((aString := aChangeSet name) beginsWith: 'Play With Me') or: [self changeSetNamesInReleaseImage includes: aString]!
----- Method: ChangesOrganizer class>>belongsInRecentUpdates: (in category 'class initialization') -----
belongsInRecentUpdates: aChangeSet
"Answer whether a change set belongs in the RecentUpdates category."
^ aChangeSet name startsWithDigit and:
[aChangeSet name asInteger >= self recentUpdateMarker]!
----- Method: ChangesOrganizer class>>buildAggregateChangeSet (in category 'services') -----
buildAggregateChangeSet
"Establish a change-set named Aggregate which bears the union of all the changes in all the existing change-sets in the system (other than any pre-existing Aggregate). This can be useful when wishing to discover potential conflicts between a disk-resident change-set and an image. Formerly very useful, now some of its unique contributions have been overtaken by new features"
| aggregateChangeSet |
aggregateChangeSet := self existingOrNewChangeSetNamed: 'Aggregate'.
aggregateChangeSet clear.
self allChangeSets do:
[:aChangeSet | aChangeSet == aggregateChangeSet ifFalse:
[aggregateChangeSet assimilateAllChangesFoundIn: aChangeSet]]
"ChangeSorter buildAggregateChangeSet"
!
----- Method: ChangesOrganizer class>>changeSet:containsClass: (in category 'enumerating') -----
changeSet: aChangeSet containsClass: aClass
| theClass |
theClass := Smalltalk classNamed: aClass.
theClass ifNil: [^ false].
^ aChangeSet containsClass: theClass!
----- Method: ChangesOrganizer class>>changeSetCategoryNamed: (in category 'class initialization') -----
changeSetCategoryNamed: aName
"Answer the changeSetCategory of the given name, or nil if none"
^ ChangeSetCategories elementAt: aName asSymbol !
----- Method: ChangesOrganizer class>>changeSetNamed: (in category 'enumerating') -----
changeSetNamed: aName
"Return the change set of the given name, or nil if none found. 1/22/96 sw"
^ChangeSet named: aName!
----- Method: ChangesOrganizer class>>changeSetNamesInReleaseImage (in category 'class initialization') -----
changeSetNamesInReleaseImage
"Answer a list of names of project change sets that come pre-shipped in the latest sytem release. On the brink of shipping a new release, call 'ChangeSorter noteChangeSetsInRelease' "
^ ChangeSetNamesInRelease ifNil:
[ChangeSetNamesInRelease := self changeSetNamesInThreeOh]!
----- Method: ChangesOrganizer class>>changeSetNamesInThreeOh (in category 'class initialization') -----
changeSetNamesInThreeOh
"Hard-coded: answer a list of names of project change sets that came pre-shipped in Squeak 3.0"
^ #('The Worlds of Squeak' 'Fun with Morphic' 'Games' 'Fun With Music' 'Building with Squeak' 'Squeak and the Internet' 'Squeak in 3D' 'More About Sound' ) !
----- Method: ChangesOrganizer class>>changeSetsNamedSuchThat: (in category 'enumerating') -----
changeSetsNamedSuchThat: nameBlock
^ChangeSet changeSetsNamedSuchThat: nameBlock!
----- Method: ChangesOrganizer class>>countOfChangeSetsWithClass:andSelector: (in category 'services') -----
countOfChangeSetsWithClass: aClass andSelector: aSelector
"Answer how many change sets record a change for the given class and selector"
^ (self allChangeSetsWithClass: aClass selector: aSelector) size!
----- Method: ChangesOrganizer class>>deleteChangeSetsNumberedLowerThan: (in category 'removing') -----
deleteChangeSetsNumberedLowerThan: anInteger
"Delete all changes sets whose names start with integers smaller than anInteger"
self removeChangeSetsNamedSuchThat:
[:aName | aName first isDigit and: [aName initialIntegerOrNil < anInteger]].
"ChangesOrganizer deleteChangeSetsNumberedLowerThan: (ChangeSorter highestNumberedChangeSet name initialIntegerOrNil - 500)"
!
----- Method: ChangesOrganizer class>>doesAnyChangeSetHaveClass:andSelector: (in category 'services') -----
doesAnyChangeSetHaveClass: aClass andSelector: aSelector
"Answer whether any known change set bears a change for the given class and selector"
^ (self countOfChangeSetsWithClass: aClass andSelector: aSelector) > 0!
----- Method: ChangesOrganizer class>>existingOrNewChangeSetNamed: (in category 'enumerating') -----
existingOrNewChangeSetNamed: aName
^ChangeSet existingOrNewChangeSetNamed: aName!
----- Method: ChangesOrganizer class>>fileOutChangeSetsNamed: (in category 'utilities') -----
fileOutChangeSetsNamed: nameList
"File out the list of change sets whose names are provided"
"ChangeSorter fileOutChangeSetsNamed: #('New Changes' 'miscTidies-sw')"
| notFound aChangeSet infoString empty |
notFound := OrderedCollection new.
empty := OrderedCollection new.
nameList do:
[:aName | (aChangeSet := self changeSetNamed: aName)
ifNotNil:
[aChangeSet isEmpty
ifTrue:
[empty add: aName]
ifFalse:
[aChangeSet fileOut]]
ifNil:
[notFound add: aName]].
infoString := (nameList size - notFound size) printString, ' change set(s) filed out'.
notFound size > 0 ifTrue:
[infoString := infoString, '
', notFound size printString, ' change set(s) not found:'.
notFound do:
[:aName | infoString := infoString, '
', aName]].
empty size > 0 ifTrue:
[infoString := infoString, '
', empty size printString, ' change set(s) were empty:'.
empty do:
[:aName | infoString := infoString, '
', aName]].
self inform: infoString!
----- Method: ChangesOrganizer class>>gatherChangeSets (in category 'enumerating') -----
gatherChangeSets "ChangeSorter gatherChangeSets"
^ChangeSet gatherChangeSets!
----- Method: ChangesOrganizer class>>highestNumberedChangeSet (in category 'enumerating') -----
highestNumberedChangeSet
"ChangeSorter highestNumberedChangeSet"
| aList |
aList := (ChangeSet allChangeSetNames select: [:aString | aString startsWithDigit] thenCollect:
[:aString | aString initialIntegerOrNil]).
^ (aList size > 0)
ifTrue:
[aList max]
ifFalse:
[nil]
!
----- Method: ChangesOrganizer class>>initialize (in category 'class initialization') -----
initialize
"Initialize the class variables"
ChangeSetCategories ifNil:
[self initializeChangeSetCategories].
RecentUpdateMarker := 0.
!
----- Method: ChangesOrganizer class>>initializeChangeSetCategories (in category 'class initialization') -----
initializeChangeSetCategories
"Initialize the set of change-set categories"
"ChangeSorter initializeChangeSetCategories"
| aCategory |
ChangeSetCategories := ElementCategory new categoryName: #ChangeSetCategories.
aCategory := ChangeSetCategory new categoryName: #All.
aCategory membershipSelector: #belongsInAll:.
aCategory documentation: 'All change sets known to the system'.
ChangeSetCategories addCategoryItem: aCategory.
aCategory := ChangeSetCategory new categoryName: #Additions.
aCategory membershipSelector: #belongsInAdditions:.
aCategory documentation: 'All unnumbered change sets except those representing projects in the system as initially released.'.
ChangeSetCategories addCategoryItem: aCategory.
aCategory := ChangeSetCategory new categoryName: #MyInitials.
aCategory membershipSelector: #belongsInMyInitials:.
aCategory documentation: 'All change sets whose names end with the current author''s initials.'.
ChangeSetCategories addCategoryItem: aCategory.
aCategory := ChangeSetCategory new categoryName: #Numbered.
aCategory membershipSelector: #belongsInNumbered:.
aCategory documentation: 'All change sets whose names start with a digit -- normally these will be the official updates to the system.'.
ChangeSetCategories addCategoryItem: aCategory.
aCategory := ChangeSetCategory new categoryName: #ProjectChangeSets.
aCategory membershipSelector: #belongsInProjectChangeSets:.
aCategory documentation: 'All change sets that are currently associated with projects present in the system right now.'.
ChangeSetCategories addCategoryItem: aCategory.
aCategory := ChangeSetCategory new categoryName: #ProjectsInRelease.
aCategory membershipSelector: #belongsInProjectsInRelease:.
aCategory documentation: 'All change sets belonging to projects that were shipped in the initial release of this version of Squeak'.
ChangeSetCategories addCategoryItem: aCategory.
aCategory := ChangeSetCategory new categoryName: #RecentUpdates.
aCategory membershipSelector: #belongsInRecentUpdates:.
aCategory documentation: 'Updates whose numbers are at or beyond the number I have designated as the earliest one to qualify as Recent'.
ChangeSetCategories addCategoryItem: aCategory.
ChangeSetCategories elementsInOrder do: [:anElem | anElem reconstituteList] !
----- Method: ChangesOrganizer class>>mostRecentChangeSetWithChangeForClass:selector: (in category 'enumerating') -----
mostRecentChangeSetWithChangeForClass: class selector: selector
| hits |
hits := self allChangeSets select:
[:cs | (cs atSelector: selector class: class) ~~ #none].
hits isEmpty ifTrue: [^ 'not in any change set'].
^ 'recent cs: ', hits last name!
----- Method: ChangesOrganizer class>>newChangeSet (in category 'adding') -----
newChangeSet
"Prompt the user for a name, and establish a new change set of
that name (if ok), making it the current changeset. Return nil
of not ok, else return the actual changeset."
| newName newSet |
newName := UIManager default
request: 'Please name the new change set:'
initialAnswer: ChangeSet defaultName.
newName isEmptyOrNil ifTrue:
[^ nil].
newSet := self basicNewChangeSet: newName.
newSet ifNotNil:
[ChangeSet newChanges: newSet].
^ newSet!
----- Method: ChangesOrganizer class>>newChangeSet: (in category 'adding') -----
newChangeSet: aName
"Makes a new change set called aName, add author initials to try to
ensure a unique change set name."
| newName |
newName := aName , FileDirectory dot , Utilities authorInitials.
^ self basicNewChangeSet: newName!
----- Method: ChangesOrganizer class>>newChangesFromStream:named: (in category 'adding') -----
newChangesFromStream: aStream named: aName
^ChangeSet newChangesFromStream: aStream named: aName
!
----- Method: ChangesOrganizer class>>noteChangeSetsInRelease (in category 'class initialization') -----
noteChangeSetsInRelease
"Freshly compute what the change sets in the release are; to be called manually just before a release"
ChangeSetNamesInRelease := (Project allProjects collect: [:p | p name]) asSet asOrderedCollection.
"ChangeSorter noteChangeSetsInRelease"!
----- Method: ChangesOrganizer class>>promoteToTop: (in category 'enumerating') -----
promoteToTop: aChangeSet
"Make aChangeSet the first in the list from now on"
^ChangeSet promoteToTop: aChangeSet!
----- Method: ChangesOrganizer class>>recentUpdateMarker (in category 'services') -----
recentUpdateMarker
"Answer the number representing the threshold of what counts as 'recent' for an update number. This allow you to use the RecentUpdates category in a ChangeSorter to advantage"
^ RecentUpdateMarker ifNil: [RecentUpdateMarker := 0]!
----- Method: ChangesOrganizer class>>recentUpdateMarker: (in category 'services') -----
recentUpdateMarker: aNumber
"Set the recent update marker as indicated"
^ RecentUpdateMarker := aNumber!
----- Method: ChangesOrganizer class>>removeChangeSet: (in category 'removing') -----
removeChangeSet: aChangeSet
"Remove the given changeSet. Caller must assure that it's cool to do this"
^ChangeSet removeChangeSet: aChangeSet!
----- Method: ChangesOrganizer class>>removeChangeSetsNamedSuchThat: (in category 'removing') -----
removeChangeSetsNamedSuchThat: nameBlock
(self changeSetsNamedSuchThat: nameBlock)
do: [:cs | self removeChangeSet: cs]!
----- Method: ChangesOrganizer class>>removeEmptyUnnamedChangeSets (in category 'removing') -----
removeEmptyUnnamedChangeSets
"Remove all change sets that are empty, whose names start with Unnamed,
and which are not nailed down by belonging to a Project."
"ChangeSorter removeEmptyUnnamedChangeSets"
| toGo |
(toGo := (self changeSetsNamedSuchThat: [:csName | csName beginsWith: 'Unnamed'])
select: [:cs | cs isEmpty and: [cs okayToRemoveInforming: false]])
do: [:cs | self removeChangeSet: cs].
self inform: toGo size printString, ' change set(s) removed.'!
----- Method: ChangesOrganizer class>>reorderChangeSets (in category 'services') -----
reorderChangeSets
"Change the order of the change sets to something more convenient:
First come the project changesets that come with the release. These are mostly empty.
Next come all numbered updates.
Next come all remaining changesets
In a ChangeSorter, they will appear in the reversed order."
"ChangeSorter reorderChangeSets"
| newHead newMid newTail |
newHead := OrderedCollection new.
newMid := OrderedCollection new.
newTail := OrderedCollection new.
ChangeSet allChangeSets do:
[:aChangeSet |
(self belongsInProjectsInRelease: aChangeSet)
ifTrue:
[newHead add: aChangeSet]
ifFalse:
[(self belongsInNumbered: aChangeSet)
ifTrue:
[newMid add: aChangeSet]
ifFalse:
[newTail add: aChangeSet]]].
ChangeSet allChangeSets: newHead, newMid, newTail.
Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]!
----- Method: ChangesOrganizer class>>secondaryChangeSet (in category 'services') -----
secondaryChangeSet
^ChangeSet secondaryChangeSet!
----- Method: ChangesOrganizer class>>unload (in category 'class initialization') -----
unload
"Unload the receiver from global registries"
self environment at: #FileList ifPresent: [:cl |
cl unregisterFileReader: self].
self environment at: #Flaps ifPresent: [:cl |
cl unregisterQuadsWithReceiver: self] !
Object subclass: #DebuggerMethodMap
instanceVariableNames: 'timestamp methodReference methodNode abstractSourceRanges sortedSourceMap'
classVariableNames: 'MapCache MapCacheEntries'
poolDictionaries: ''
category: 'Tools-Debugger'!
!DebuggerMethodMap commentStamp: '<historical>' prior: 0!
I am a place-holder for information needed by the Debugger to inspect method activations. I insulate the debugger from details of code generation such as exact bytecode offsets and temporary variable locations. I have two concreate subclasses, one for methods compiled using BlueBook blocks and one for methods compiled using Closures. These classes deal with temporary variable access. My function is to abstract the source map away from actual bytecode pcs to abstract bytecode pcs.
To reduce compilation time I try and defer as much computation to access time as possible as instances of me will be created after each compilation.
I maintain a WeakIdentityDictionary of method to DebuggerMethodMap to cache maps. I refer to my method through a WeakArray to keep the map cache functional. If the reference from a DebuggerMethodMap to its method were strong then the method would never be dropped from the cache because the reference from its map would keep it alive.!
----- Method: DebuggerMethodMap class>>cacheDebugMap:forMethod: (in category 'debugger support') -----
cacheDebugMap: aDebuggerMethodMap forMethod: aCompiledMethod
MapCache finalizeValues.
[MapCache size >= MapCacheEntries] whileTrue:
[| mapsByAge |
mapsByAge := MapCache keys asSortedCollection:
[:m1 :m2|
(MapCache at: m1) timestamp
< (MapCache at: m2) timestamp].
mapsByAge notEmpty ifTrue: "There be race conditions and reentrancy issues here"
[MapCache removeKey: mapsByAge last]].
^MapCache
at: aCompiledMethod
put: aDebuggerMethodMap!
----- Method: DebuggerMethodMap class>>forMethod: (in category 'instance creation') -----
forMethod: aMethod "<CompiledMethod>"
"Answer a DebuggerMethodMap suitable for debugging activations of aMethod.
Answer an existing instance from the cache if it exists, cacheing a new one if required."
^MapCache
at: aMethod
ifAbsent: [self
cacheDebugMap:
(self
forMethod: aMethod
methodNode: aMethod methodNode)
forMethod: aMethod]!
----- Method: DebuggerMethodMap class>>forMethod:methodNode: (in category 'instance creation') -----
forMethod: aMethod "<CompiledMethod>" methodNode: methodNode "<MethodNode>"
"Uncached instance creation method for private use or for tests.
Please consider using forMethod: instead."
^(aMethod isBlueBookCompiled
ifTrue: [DebuggerMethodMapForBlueBookMethods]
ifFalse: [DebuggerMethodMapForClosureCompiledMethods]) new
forMethod: aMethod
methodNode: methodNode!
----- Method: DebuggerMethodMap class>>initialize (in category 'class initialization') -----
initialize
"DebuggerMethodMap initialize"
self voidMapCache!
----- Method: DebuggerMethodMap class>>voidMapCache (in category 'class initialization') -----
voidMapCache
MapCache := WeakIdentityKeyDictionary new.
MapCacheEntries := 16!
----- Method: DebuggerMethodMap>>abstractSourceMap (in category 'source mapping') -----
abstractSourceMap
"Answer with a Dictionary of abstractPC <Integer> to sourceRange <Interval>."
| theMethodToScan rawSourceRanges concreteSourceRanges abstractPC scanner client |
abstractSourceRanges ifNotNil:
[^abstractSourceRanges].
"If the methodNode hasn't had a method generated it doesn't have pcs set in its
nodes so we must generate a new method and might as well use it for scanning."
methodNode rawSourceRangesAndMethodDo:
[:ranges :method|
rawSourceRanges := ranges.
theMethodToScan := method].
concreteSourceRanges := Dictionary new.
rawSourceRanges keysAndValuesDo:
[:node :range|
node pc ~= 0 ifTrue:
[concreteSourceRanges at: node pc put: range]].
abstractPC := 1.
abstractSourceRanges := Dictionary new.
scanner := InstructionStream on: theMethodToScan.
client := InstructionClient new.
[(concreteSourceRanges includesKey: scanner pc) ifTrue:
[abstractSourceRanges at: abstractPC put: (concreteSourceRanges at: scanner pc)].
abstractPC := abstractPC + 1.
scanner interpretNextInstructionFor: client.
scanner atEnd] whileFalse.
^abstractSourceRanges!
----- Method: DebuggerMethodMap>>forMethod:methodNode: (in category 'initialize-release') -----
forMethod: aMethod "<CompiledMethod>" methodNode: theMethodNode "<MethodNode>"
methodReference := WeakArray with: aMethod.
methodNode := theMethodNode.
self markRecentlyUsed!
----- Method: DebuggerMethodMap>>markRecentlyUsed (in category 'accessing') -----
markRecentlyUsed
timestamp := Time totalSeconds!
----- Method: DebuggerMethodMap>>method (in category 'accessing') -----
method
^methodReference at: 1!
----- Method: DebuggerMethodMap>>namedTempAt:in: (in category 'accessing') -----
namedTempAt: index in: aContext
"Answer the value of the temp at index in aContext where index is relative
to the array of temp names answered by tempNamesForContext:"
self subclassResponsibility!
----- Method: DebuggerMethodMap>>namedTempAt:put:in: (in category 'accessing') -----
namedTempAt: index put: aValue in: aContext
"Assign the value of the temp at index in aContext where index is relative
to the array of temp names answered by tempNamesForContext:"
self subclassResponsibility!
----- Method: DebuggerMethodMap>>rangeForPC:contextIsActiveContext: (in category 'source mapping') -----
rangeForPC: contextsConcretePC contextIsActiveContext: contextIsActiveContext
"Answer the indices in the source code for the supplied pc.
If the context is the actve context (is at the hot end of the stack)
then its pc is the current pc. But if the context isn't, because it is
suspended sending a message, then its current pc is the previous pc."
| pc i end |
pc := self method abstractPCForConcretePC: (contextIsActiveContext
ifTrue: [contextsConcretePC]
ifFalse: [(self method pcPreviousTo: contextsConcretePC)
ifNotNil: [:prevpc| prevpc]
ifNil: [contextsConcretePC]]).
(self abstractSourceMap includesKey: pc) ifTrue:
[^self abstractSourceMap at: pc].
sortedSourceMap ifNil:
[sortedSourceMap := self abstractSourceMap.
sortedSourceMap := (sortedSourceMap keys collect:
[:key| key -> (sortedSourceMap at: key)]) asSortedCollection].
(sortedSourceMap isNil or: [sortedSourceMap isEmpty]) ifTrue: [^1 to: 0].
i := sortedSourceMap indexForInserting: (pc -> nil).
i < 1 ifTrue: [^1 to: 0].
i > sortedSourceMap size ifTrue:
[end := sortedSourceMap inject: 0 into:
[:prev :this | prev max: this value last].
^end+1 to: end].
^(sortedSourceMap at: i) value
"| method source scanner map |
method := DebuggerMethodMap compiledMethodAt: #rangeForPC:contextIsActiveContext:.
source := method getSourceFromFile asString.
scanner := InstructionStream on: method.
map := method debuggerMap.
Array streamContents:
[:ranges|
[scanner atEnd] whileFalse:
[| range |
range := map rangeForPC: scanner pc contextIsActiveContext: true.
((map abstractSourceMap includesKey: scanner abstractPC)
and: [range first ~= 0]) ifTrue:
[ranges nextPut: (source copyFrom: range first to: range last)].
scanner interpretNextInstructionFor: InstructionClient new]]"!
----- Method: DebuggerMethodMap>>sourceText (in category 'source mapping') -----
sourceText
self method ifNotNil:
[:method|
method holdsTempNames ifTrue:
[^method
getSourceFor: (method selector ifNil: [method defaultSelector])
in: method methodClass]].
^methodNode sourceText!
----- Method: DebuggerMethodMap>>tempNamesForContext: (in category 'accessing') -----
tempNamesForContext: aContext
"Answer an Array of all the temp names in scope in aContext starting with
the home's first local (the first argument or first temporary if no arguments)."
self subclassResponsibility!
----- Method: DebuggerMethodMap>>tempsAndValuesForContext: (in category 'accessing') -----
tempsAndValuesForContext: aContext
"Return a string of the temporary variabls and their current values"
| aStream |
aStream := WriteStream on: (String new: 100).
(self tempNamesForContext: aContext) doWithIndex:
[:title :index |
aStream nextPutAll: title; nextPut: $:; space; tab.
aContext print: (self namedTempAt: index in: aContext) on: aStream.
aStream cr].
^aStream contents!
----- Method: DebuggerMethodMap>>timestamp (in category 'accessing') -----
timestamp
^timestamp!
DebuggerMethodMap subclass: #DebuggerMethodMapForBlueBookMethods
instanceVariableNames: 'tempNames'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Debugger'!
!DebuggerMethodMapForBlueBookMethods commentStamp: '<historical>' prior: 0!
I am a place-holder for information needed by the Debugger to inspect method activations. See my superclass's comment. I map methods compiled using Closures.!
----- Method: DebuggerMethodMapForBlueBookMethods>>forMethod:methodNode: (in category 'initialize-release') -----
forMethod: aMethod "<CompiledMethod>" methodNode: aMethodNode "<MethodNode>"
super forMethod: aMethod methodNode: aMethodNode.
tempNames := methodNode encoder tempNames!
----- Method: DebuggerMethodMapForBlueBookMethods>>namedTempAt:in: (in category 'accessing') -----
namedTempAt: index in: aContext
"Answer the value of the temp at index in aContext where index is relative
to the array of temp names answered by tempNamesForContext:"
^aContext tempAt: index!
----- Method: DebuggerMethodMapForBlueBookMethods>>namedTempAt:put:in: (in category 'accessing') -----
namedTempAt: index put: aValue in: aContext
"Assign the value of the temp at index in aContext where index is relative
to the array of temp names answered by tempNamesForContext:"
^aContext tempAt: index put: aValue!
----- Method: DebuggerMethodMapForBlueBookMethods>>tempNamesForContext: (in category 'accessing') -----
tempNamesForContext: aContext
"Answer an Array of all the temp names in scope in aContext starting with
the home's first local (the first argument or first temporary if no arguments)."
^tempNames!
DebuggerMethodMap subclass: #DebuggerMethodMapForClosureCompiledMethods
instanceVariableNames: 'blockExtentsToTempRefs startpcsToTempRefs'
classVariableNames: 'FirstTime'
poolDictionaries: ''
category: 'Tools-Debugger'!
!DebuggerMethodMapForClosureCompiledMethods commentStamp: '<historical>' prior: 0!
I am a place-holder for information needed by the Debugger to inspect method activations. See my superclass's comment. I map methods compiled using BlueBook blocks.
Instance variables
blockExtentsToTempsRefs <Dictionary of: Interval -> Array of: (Array with: String with: (Integer | (Array with: Integer with: Integer)))>
maps a block extent to an Array of temp references for that block/method.
Each reference is a pair of temp name and index, where the index can itself be a pair for a remote temp.
startpcsToTempRefs <Dictionary of: Integer -> Array of: (Array with: String with: temp reference)> where
temp reference ::= Integer
| (Array with: Integer with: Integer)
| (Array with: #outer with: temp reference)!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>ensureExtentsMapsInitialized (in category 'private') -----
ensureExtentsMapsInitialized
| encoderTempRefs "<Dictionary of: Interval -> <Array of: <String | <Array of: String>>>>" |
blockExtentsToTempRefs ifNotNil: [^self].
blockExtentsToTempRefs := Dictionary new.
startpcsToTempRefs := Dictionary new.
encoderTempRefs := methodNode blockExtentsToTempRefs.
encoderTempRefs keysAndValuesDo:
[:blockExtent :tempVector|
blockExtentsToTempRefs
at: blockExtent
put: (Array streamContents:
[:stream|
tempVector withIndexDo:
[:nameOrSequence :index|
nameOrSequence isString
ifTrue:
[stream nextPut: {nameOrSequence. index}]
ifFalse:
[nameOrSequence withIndexDo:
[:name :indirectIndex|
stream nextPut: { name. { index. indirectIndex }}]]]])]!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>namedTempAt:in: (in category 'accessing') -----
namedTempAt: index in: aContext
"Answer the value of the temp at index in aContext where index is relative
to the array of temp names answered by tempNamesForContext:"
^self
privateTempAt: index
in: aContext
startpcsToBlockExtents: aContext method startpcsToBlockExtents!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>namedTempAt:put:in: (in category 'accessing') -----
namedTempAt: index put: aValue in: aContext
"Assign the value of the temp at index in aContext where index is relative
to the array of temp names answered by tempNamesForContext:.
If the value is a copied value we also need to set it along the lexical chain."
^self
privateTempAt: index
in: aContext
put: aValue
startpcsToBlockExtents: aContext method startpcsToBlockExtents!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>privateDereference:in: (in category 'private') -----
privateDereference: tempReference in: aContext
"Fetch the temporary with reference tempReference in aContext.
tempReference can be
integer - direct temp reference
#( indirectionVectorIndex tempIndex ) - remote temp in indirectionVector at index
#( outer. temp reference ) - a temp reference in an outer context."
^tempReference isInteger
ifTrue: [aContext tempAt: tempReference]
ifFalse:
[tempReference first == #outer
ifTrue: [self privateDereference: tempReference last
in: aContext outerContext]
ifFalse: [(aContext tempAt: tempReference first)
at: tempReference second]]!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>privateDereference:in:put: (in category 'private') -----
privateDereference: tempReference in: aContext put: aValue
"Assign the temporary with reference tempReference in aContext.
tempReference can be
integer - direct temp reference
#( indirectionVectorIndex tempIndex ) - remote temp in indirectionVector at index
#( outer. temp reference ) - a temp reference in an outer context."
^tempReference isInteger
ifTrue: [aContext tempAt: tempReference put: aValue]
ifFalse:
[tempReference first == #outer
ifTrue: [self privateDereference: tempReference last
in: aContext outerContext
put: aValue]
ifFalse: [(aContext tempAt: tempReference first)
at: tempReference second
put: aValue]]!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>privateTempAt:in:put:startpcsToBlockExtents: (in category 'private') -----
privateTempAt: index in: aContext put: aValue startpcsToBlockExtents: theContextsStartpcsToBlockExtents
| nameRefPair |
nameRefPair := (self privateTempRefsForContext: aContext
startpcsToBlockExtents: theContextsStartpcsToBlockExtents)
at: index
ifAbsent: [aContext errorSubscriptBounds: index].
^self privateDereference: nameRefPair last in: aContext put: aValue!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>privateTempAt:in:startpcsToBlockExtents: (in category 'private') -----
privateTempAt: index in: aContext startpcsToBlockExtents: theContextsStartpcsToBlockExtents
| nameRefPair |
nameRefPair := (self privateTempRefsForContext: aContext
startpcsToBlockExtents: theContextsStartpcsToBlockExtents)
at: index
ifAbsent: [aContext errorSubscriptBounds: index].
^self privateDereference: nameRefPair last in: aContext!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>privateTempRefsForContext:startpcsToBlockExtents: (in category 'private') -----
privateTempRefsForContext: aContext startpcsToBlockExtents: theContextsStartpcsToBlockExtents
"Answer the sequence of temps in scope in aContext in the natural order,
outermost arguments and temporaries first, innermost last. Each temp is
a pair of the temp's name followed by a reference. The reference can be
integer - index of temp in aContext
#( indirectionVectorIndex tempIndex ) - remote temp in indirectionVector at index in aContext
#( outer. temp reference ) - a temp reference in an outer context."
blockExtentsToTempRefs ifNil:
[blockExtentsToTempRefs := (aContext method holdsTempNames
ifTrue: [aContext method]
ifFalse: [methodNode]) blockExtentsToTempsMap.
startpcsToTempRefs := Dictionary new].
^startpcsToTempRefs
at: aContext startpc
ifAbsentPut:
[| localRefs |
localRefs := blockExtentsToTempRefs at: (theContextsStartpcsToBlockExtents at: aContext startpc).
aContext outerContext
ifNil: [localRefs]
ifNotNil:
[:outer| | outerTemps |
"Present temps in the order outermost to innermost left-to-right, but replace
copied outermost temps with their innermost copies"
outerTemps := (self
privateTempRefsForContext: outer
startpcsToBlockExtents: theContextsStartpcsToBlockExtents) collect:
[:outerPair|
localRefs
detect: [:localPair| outerPair first = localPair first]
ifNone: [{ outerPair first. { #outer. outerPair last } }]].
outerTemps,
(localRefs reject: [:localPair| outerTemps anySatisfy: [:outerPair| localPair first = outerPair first]])]]!
----- Method: DebuggerMethodMapForClosureCompiledMethods>>tempNamesForContext: (in category 'accessing') -----
tempNamesForContext: aContext
"Answer an Array of all the temp names in scope in aContext starting with
the home's first local (the first argument or first temporary if no arguments)."
^(self
privateTempRefsForContext: aContext
startpcsToBlockExtents: aContext method startpcsToBlockExtents) collect:
[:pair| pair first]!
Object subclass: #DummyToolWorkingWithFileList
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-FileList-Tests'!
!DummyToolWorkingWithFileList commentStamp: '<historical>' prior: 0!
I'm a dummy class for testing that the registration of the tool to the FileList of actually happens.
In the future the tests should cover that the class register when loaded in memory and unregister when unloaded.!
----- Method: DummyToolWorkingWithFileList class>>fileReaderServicesForFile:suffix: (in category 'class initialization') -----
fileReaderServicesForFile: fullName suffix: suffix
^ (suffix = 'kkk')
ifTrue: [ self services]
ifFalse: [#()] !
----- Method: DummyToolWorkingWithFileList class>>initialize (in category 'class initialization') -----
initialize
"self initialize"
FileList registerFileReader: self
!
----- Method: DummyToolWorkingWithFileList class>>loadAFileForTheDummyTool: (in category 'class initialization') -----
loadAFileForTheDummyTool: aFileListOrAPath
"attention. if the file list selects a file the argument will be a fullpath of the selected file else it will pass the filelist itself"!
----- Method: DummyToolWorkingWithFileList class>>serviceLoadAFilForDummyTool (in category 'class initialization') -----
serviceLoadAFilForDummyTool
"Answer a service for opening the Dummy tool"
^ SimpleServiceEntry
provider: self
label: 'menu label'
selector: #loadAFileForTheDummyTool:
description: 'Menu label for dummy tool'
buttonLabel: 'test'!
----- Method: DummyToolWorkingWithFileList class>>services (in category 'class initialization') -----
services
^ Array with: self serviceLoadAFilForDummyTool
!
----- Method: DummyToolWorkingWithFileList class>>unload (in category 'class initialization') -----
unload
FileList unregisterFileReader: self !
----- Method: DummyToolWorkingWithFileList class>>unregister (in category 'class initialization') -----
unregister
FileList unregisterFileReader: self.
!
----- Method: Object>>browse (in category '*tools-browser') -----
browse
self systemNavigation browseClass: self class!
----- Method: Object>>browseHierarchy (in category '*tools-browser') -----
browseHierarchy
self systemNavigation browseHierarchy: self class!
----- Method: Object>>exploreAndYourself (in category '*Tools-Explorer') -----
exploreAndYourself
"i.e. explore; yourself. Thisway i can peek w/o typing all the parentheses"
self explore.
^self!
----- Method: Object>>exploreWithLabel: (in category '*Tools-Explorer') -----
exploreWithLabel: label
^ ObjectExplorer new openExplorerFor: self withLabel:
label!
Object subclass: #StandardToolSet
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Base'!
!StandardToolSet commentStamp: '<historical>' prior: 0!
Main comment stating the purpose of this class and relevant relationship to other classes.
Possible useful expressions for doIt or printIt.
Structure:
instVar1 type -- comment about the purpose of instVar1
instVar2 type -- comment about the purpose of instVar2
Any further useful comments about the general approach of this implementation.!
----- Method: StandardToolSet class>>basicInspect: (in category 'inspecting') -----
basicInspect: anObject
"Open an inspector on the given object. The tool set must know which inspector type to use for which object - the object cannot possibly know what kind of inspectors the toolset provides."
^BasicInspector openOn: anObject!
----- Method: StandardToolSet class>>browse:selector: (in category 'browsing') -----
browse: aClass selector: aSelector
"Open a browser"
^SystemBrowser default fullOnClass: aClass selector: aSelector!
----- Method: StandardToolSet class>>browseChangeSetsWithClass:selector: (in category 'browsing') -----
browseChangeSetsWithClass: aClass selector: aSelector
"Browse all the change sets with the given class/selector"
^ChangeSorter browseChangeSetsWithClass: aClass selector: aSelector!
----- Method: StandardToolSet class>>browseHierarchy:selector: (in category 'browsing') -----
browseHierarchy: aClass selector: aSelector
"Open a browser"
| newBrowser |
(aClass == nil) ifTrue: [^ self].
(newBrowser := SystemBrowser default new) setClass: aClass selector: aSelector.
newBrowser spawnHierarchy.!
----- Method: StandardToolSet class>>browseMessageNames: (in category 'browsing') -----
browseMessageNames: aString
^(MessageNames methodBrowserSearchingFor: aString) openInWorld!
----- Method: StandardToolSet class>>browseMessageSet:name:autoSelect: (in category 'browsing') -----
browseMessageSet: messageList name: title autoSelect: autoSelectString
"Open a message set browser"
^MessageSet
openMessageList: messageList
name: title
autoSelect: autoSelectString!
----- Method: StandardToolSet class>>browseVersionsOf:selector: (in category 'browsing') -----
browseVersionsOf: aClass selector: aSelector
"Open a browser"
VersionsBrowser
browseVersionsOf: (aClass compiledMethodAt: aSelector)
class: aClass theNonMetaClass
meta: aClass isMeta
category: (aClass organization categoryOfElement: aSelector)
selector: aSelector!
----- Method: StandardToolSet class>>debug:context:label:contents:fullView: (in category 'debugging') -----
debug: aProcess context: aContext label: aString contents: contents fullView: aBool
"Open a debugger on the given process and context."
^Debugger openOn: aProcess context: aContext label: aString contents: contents fullView: aBool!
----- Method: StandardToolSet class>>debugContext:label:contents: (in category 'debugging') -----
debugContext: aContext label: aString contents: contents
"Open a debugger on the given process and context."
^Debugger openContext: aContext label: aString contents: contents!
----- Method: StandardToolSet class>>debugError: (in category 'debugging') -----
debugError: anError
"Handle an otherwise unhandled error"
^Processor activeProcess
debug: anError signalerContext
title: anError description!
----- Method: StandardToolSet class>>debugSyntaxError: (in category 'debugging') -----
debugSyntaxError: anError
"Handle a syntax error"
| notifier |
notifier := SyntaxError new
setClass: anError errorClass
code: anError errorCode
debugger: (Debugger context: anError signalerContext)
doitFlag: anError doitFlag.
notifier category: anError category.
SyntaxError open: notifier.!
----- Method: StandardToolSet class>>explore: (in category 'inspecting') -----
explore: anObject
"Open an explorer on the given object."
^ObjectExplorer new openExplorerFor: anObject!
----- Method: StandardToolSet class>>initialize (in category 'class initialization') -----
initialize
ToolSet register: self.
Preferences installMissingWindowColors.!
----- Method: StandardToolSet class>>inspect: (in category 'inspecting') -----
inspect: anObject
"Open an inspector on the given object. The tool set must know which inspector type to use for which object - the object cannot possibly know what kind of inspectors the toolset provides."
^(self inspectorClassOf: anObject) openOn: anObject!
----- Method: StandardToolSet class>>inspect:label: (in category 'inspecting') -----
inspect: anObject label: aString
"Open an inspector on the given object. The tool set must know which inspector type to use for which object - the object cannot possibly know what kind of inspectors the toolset provides."
^(self inspectorClassOf: anObject) openOn: anObject withEvalPane: true withLabel: aString!
----- Method: StandardToolSet class>>inspectorClassOf: (in category 'inspecting') -----
inspectorClassOf: anObject
"Answer the inspector class for the given object. The tool set must know which inspector type to use for which object - the object cannot possibly know what kind of inspectors the toolset provides."
| map |
map := Dictionary new.
#(
(CompiledMethod CompiledMethodInspector)
(CompositeEvent OrderedCollectionInspector)
(Dictionary DictionaryInspector)
(ExternalStructure ExternalStructureInspector)
(FloatArray OrderedCollectionInspector)
(OrderedCollection OrderedCollectionInspector)
(Set SetInspector)
(WeakSet WeakSetInspector)
) do:[:spec|
map at: spec first put: spec last.
].
anObject class withAllSuperclassesDo:[:cls|
map at: cls name ifPresent:[:inspectorName| ^Smalltalk classNamed: inspectorName].
].
^Inspector!
----- Method: StandardToolSet class>>interrupt:label: (in category 'debugging') -----
interrupt: aProcess label: aString
"Open a debugger on the given process and context."
Debugger
openInterrupt: aString
onProcess: aProcess!
----- Method: StandardToolSet class>>menuItems (in category 'menu') -----
menuItems
"Answer the menu items available for this tool set"
^#(
('class browser' #openClassBrowser)
('workspace' #openWorkspace)
('file list' #openFileList)
('package pane browser' #openPackagePaneBrowser)
('process browser' #openProcessBrowser)
-
('method finder' #openSelectorBrowser)
('message names' #openMessageNames)
-
('simple change sorter' #openChangeSorter)
('dual change sorter' #openDualChangeSorter)
)
!
----- Method: StandardToolSet class>>openChangeSorter (in category 'menu') -----
openChangeSorter
ToolBuilder open: ChangeSorter new!
----- Method: StandardToolSet class>>openChangedMessageSet: (in category 'browsing') -----
openChangedMessageSet: aChangeSet
"Open a ChangedMessageSet for aChangeSet"
ChangedMessageSet openFor: aChangeSet!
----- Method: StandardToolSet class>>openClassBrowser (in category 'menu') -----
openClassBrowser
SystemBrowser default open!
----- Method: StandardToolSet class>>openClassListBrowser:title: (in category 'browsing') -----
openClassListBrowser: anArray title: aString
"Open a class list browser"
^ClassListBrowser new initForClassesNamed: anArray title: aString
!
----- Method: StandardToolSet class>>openDualChangeSorter (in category 'menu') -----
openDualChangeSorter
ToolBuilder open: DualChangeSorter new!
----- Method: StandardToolSet class>>openFileList (in category 'menu') -----
openFileList
FileList open!
----- Method: StandardToolSet class>>openMessageNames (in category 'menu') -----
openMessageNames
"Bring a MessageNames tool to the front"
MessageNames openMessageNames!
----- Method: StandardToolSet class>>openPackagePaneBrowser (in category 'menu') -----
openPackagePaneBrowser
PackagePaneBrowser openBrowser.!
----- Method: StandardToolSet class>>openProcessBrowser (in category 'menu') -----
openProcessBrowser
ProcessBrowser open!
----- Method: StandardToolSet class>>openSelectorBrowser (in category 'menu') -----
openSelectorBrowser
ToolBuilder open: SelectorBrowser new!
----- Method: StandardToolSet class>>openWorkspace (in category 'menu') -----
openWorkspace
Workspace open!
----- Method: StandardToolSet class>>unload (in category 'class initialization') -----
unload
ToolSet unregister: self.!
----- Method: WeakSet>>inspectorClass (in category '*Tools-Inspector') -----
inspectorClass
^ WeakSetInspector!
SystemWindow subclass: #ArchiveViewer
instanceVariableNames: 'archive fileName memberIndex viewAllContents'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-ArchiveViewer'!
!ArchiveViewer commentStamp: '<historical>' prior: 0!
This is a viewer window that allows editing and viewing of Zip archives.!
----- Method: ArchiveViewer class>>addFileToNewZip: (in category 'instance creation') -----
addFileToNewZip: fullName
"Add the currently selected file to a new zip"
| zip |
zip := (ZipArchive new)
addFile: fullName
as: (FileDirectory localNameFor: fullName); yourself.
(self open) archive: zip
!
----- Method: ArchiveViewer class>>deleteTemporaryDirectory (in category 'class initialization') -----
deleteTemporaryDirectory
"
ArchiveViewer deleteTemporaryDirectory
"
| dir |
(dir := self temporaryDirectory) exists ifTrue: [ dir recursiveDelete ].!
----- Method: ArchiveViewer class>>descriptionForPartsBin (in category 'parts bin') -----
descriptionForPartsBin
^ self partName: 'Zip Tool'
categories: #(Tools)
documentation: 'A viewer and editor for Zip archive files'
!
----- Method: ArchiveViewer class>>extractAllFrom: (in category 'file list services') -----
extractAllFrom: aFileName
(self new) fileName: aFileName; extractAll!
----- Method: ArchiveViewer class>>fileReaderServicesForFile:suffix: (in category 'fileIn/Out') -----
fileReaderServicesForFile: fullName suffix: suffix
| services |
services := OrderedCollection new.
services add: self serviceAddToNewZip.
({'zip'.'sar'.'pr'. 'mcz'. '*'} includes: suffix)
ifTrue: [services add: self serviceOpenInZipViewer.
services add: self serviceExtractAll].
^ services!
----- Method: ArchiveViewer class>>initialize (in category 'class initialization') -----
initialize
"ArchiveViewer initialize"
FileList registerFileReader: self.
Smalltalk addToShutDownList: self.!
----- Method: ArchiveViewer class>>open (in category 'instance creation') -----
open
^(self new) createWindow; openInWorld.!
----- Method: ArchiveViewer class>>openOn: (in category 'instance creation') -----
openOn: aFileName
| newMe |
newMe := self new.
newMe createWindow; fileName: aFileName; openInWorld.
^newMe!
----- Method: ArchiveViewer class>>serviceAddToNewZip (in category 'file list services') -----
serviceAddToNewZip
"Answer a service for adding the file to a new zip"
^ FileModifyingSimpleServiceEntry
provider: self
label: 'add file to new zip'
selector: #addFileToNewZip:
description: 'add file to new zip'
buttonLabel: 'to new zip'!
----- Method: ArchiveViewer class>>serviceExtractAll (in category 'file list services') -----
serviceExtractAll
"Answer a service for opening in a zip viewer"
^ FileModifyingSimpleServiceEntry
provider: self
label: 'extract all to...'
selector: #extractAllFrom:
description: 'extract all files to a user-specified directory'
buttonLabel: 'extract all'!
----- Method: ArchiveViewer class>>serviceOpenInZipViewer (in category 'class initialization') -----
serviceOpenInZipViewer
"Answer a service for opening in a zip viewer"
^ SimpleServiceEntry
provider: self
label: 'open in zip viewer'
selector: #openOn:
description: 'open in zip viewer'
buttonLabel: 'open zip'!
----- Method: ArchiveViewer class>>services (in category 'fileIn/Out') -----
services
^ Array
with: self serviceAddToNewZip
with: self serviceOpenInZipViewer
!
----- Method: ArchiveViewer class>>shutDown: (in category 'class initialization') -----
shutDown: quitting
quitting ifTrue: [ self deleteTemporaryDirectory ].!
----- Method: ArchiveViewer class>>temporaryDirectory (in category 'fileIn/Out') -----
temporaryDirectory
"Answer a directory to use for unpacking files for the file list services."
^FileDirectory default directoryNamed: '.archiveViewerTemp'!
----- Method: ArchiveViewer class>>unload (in category 'initialize-release') -----
unload
FileList unregisterFileReader: self !
----- Method: ArchiveViewer>>addDirectory (in category 'member operations') -----
addDirectory
| directory |
self canAddMember ifFalse: [ ^self ].
directory := FileList2 modalFolderSelector.
directory
ifNil: [^ self].
archive addTree: directory removingFirstCharacters: directory pathName size + 1.
self memberIndex: 0.
self changed: #memberList.!
----- Method: ArchiveViewer>>addMember (in category 'member operations') -----
addMember
| result local full |
self canAddMember ifFalse: [ ^self ].
result := FileList2 modalFileSelector .
result ifNil: [ ^self ].
local := result directory localNameFor: result name.
full := result directory fullNameFor: result name.
(archive addFile: full as: local)
desiredCompressionMethod: ZipArchive compressionDeflated.
self memberIndex: self members size.
self changed: #memberList.!
----- Method: ArchiveViewer>>addMemberFromClipboard (in category 'member operations') -----
addMemberFromClipboard
| string newName |
self canAddMember ifFalse: [ ^self ].
string := Clipboard clipboardText asString.
newName := FillInTheBlankMorph
request: 'New name for member:'
initialAnswer: 'clipboardText'.
newName notEmpty ifTrue: [
(archive addString: string as: newName) desiredCompressionMethod: ZipArchive compressionDeflated.
self memberIndex: self members size.
self changed: #memberList.
]
!
----- Method: ArchiveViewer>>archive (in category 'accessing') -----
archive
^archive!
----- Method: ArchiveViewer>>archive: (in category 'initialization') -----
archive: aZipArchive
archive := aZipArchive.
self model: aZipArchive.
self setLabel: 'New Zip Archive'.
self memberIndex: 0.
self changed: #memberList!
----- Method: ArchiveViewer>>briefContents (in category 'initialization') -----
briefContents
"Trim to 5000 characters. If the member is longer, then point out that it is trimmed.
Also warn if the member has a corrupt CRC-32."
| stream subContents errorMessage |
self selectedMember ifNil: [^ ''].
errorMessage := ''.
stream := WriteStream on: (String new: (self selectedMember uncompressedSize min: 5500)).
[ self selectedMember uncompressedSize > 5000
ifTrue: [ | lastLineEndingIndex tempIndex |
subContents := self selectedMember contentsFrom: 1 to: 5000.
lastLineEndingIndex := subContents lastIndexOf: Character cr.
tempIndex := subContents lastIndexOf: Character lf.
tempIndex > lastLineEndingIndex ifTrue: [lastLineEndingIndex := tempIndex].
lastLineEndingIndex = 0
ifFalse: [subContents := subContents copyFrom: 1 to: lastLineEndingIndex]]
ifFalse: [ subContents := self selectedMember contents ]]
on: CRCError do: [ :ex |
errorMessage := String streamContents: [ :s |
s nextPutAll: '[ ';
nextPutAll: (ex messageText copyUpToLast: $( );
nextPutAll: ' ]' ].
ex proceed ].
(errorMessage isEmpty not or: [ self selectedMember isCorrupt ]) ifTrue: [
stream nextPutAll: '********** WARNING!! Member is corrupt!! ';
nextPutAll: errorMessage;
nextPutAll: ' **********'; cr ].
self selectedMember uncompressedSize > 5000
ifTrue: [
stream nextPutAll: 'File ';
print: self selectedMember fileName;
nextPutAll: ' is ';
print: self selectedMember uncompressedSize;
nextPutAll: ' bytes long.'; cr;
nextPutAll: 'Click the ''View All Contents'' button above to see the entire file.'; cr; cr;
nextPutAll: 'Here are the first ';
print: subContents size;
nextPutAll: ' characters...'; cr;
next: 40 put: $-; cr;
nextPutAll: subContents;
next: 40 put: $-; cr;
nextPutAll: '... end of the first ';
print: subContents size;
nextPutAll: ' characters.' ]
ifFalse: [ stream nextPutAll: self selectedMember contents ].
^stream contents
!
----- Method: ArchiveViewer>>buildWindowMenu (in category 'menu') -----
buildWindowMenu
| menu |
menu := super buildWindowMenu.
menu addLine.
menu add: 'inspect archive' target: archive action: #inspect.
menu add: 'write prepending file...' target: self action: #writePrependingFile.
^menu.!
----- Method: ArchiveViewer>>buttonColor (in category 'initialization') -----
buttonColor
^self defaultBackgroundColor darker!
----- Method: ArchiveViewer>>buttonOffColor (in category 'initialization') -----
buttonOffColor
^self defaultBackgroundColor darker!
----- Method: ArchiveViewer>>buttonOnColor (in category 'initialization') -----
buttonOnColor
^self defaultBackgroundColor!
----- Method: ArchiveViewer>>canAddMember (in category 'member operations') -----
canAddMember
^archive notNil!
----- Method: ArchiveViewer>>canCreateNewArchive (in category 'archive operations') -----
canCreateNewArchive
^true!
----- Method: ArchiveViewer>>canDeleteMember (in category 'member operations') -----
canDeleteMember
^memberIndex > 0!
----- Method: ArchiveViewer>>canExtractAll (in category 'archive operations') -----
canExtractAll
^self members notEmpty!
----- Method: ArchiveViewer>>canExtractMember (in category 'member operations') -----
canExtractMember
^memberIndex > 0!
----- Method: ArchiveViewer>>canOpenNewArchive (in category 'archive operations') -----
canOpenNewArchive
^true!
----- Method: ArchiveViewer>>canRenameMember (in category 'member operations') -----
canRenameMember
^memberIndex > 0!
----- Method: ArchiveViewer>>canSaveArchive (in category 'archive operations') -----
canSaveArchive
^archive notNil!
----- Method: ArchiveViewer>>canViewAllContents (in category 'member operations') -----
canViewAllContents
^memberIndex > 0 and: [ viewAllContents not ]!
----- Method: ArchiveViewer>>changeViewAllContents (in category 'member operations') -----
changeViewAllContents
(viewAllContents not and: [ self selectedMember notNil and: [ self selectedMember uncompressedSize > 50000 ]])
ifTrue: [ (self confirm: 'This member''s size is ',
(self selectedMember uncompressedSize asString),
'; do you really want to see all that data?')
ifFalse: [ ^self ]
].
viewAllContents := viewAllContents not.
self changed: #contents!
----- Method: ArchiveViewer>>commentArchive (in category 'archive operations') -----
commentArchive
| newName |
archive ifNil: [ ^self ].
newName := FillInTheBlankMorph
request: 'New comment for archive:'
initialAnswer: archive zipFileComment
centerAt: Sensor cursorPoint
inWorld: self world
onCancelReturn: archive zipFileComment
acceptOnCR: true.
archive zipFileComment: newName.!
----- Method: ArchiveViewer>>commentMember (in category 'member operations') -----
commentMember
| newName |
newName := FillInTheBlankMorph
request: 'New comment for member:'
initialAnswer: self selectedMember fileComment
centerAt: Sensor cursorPoint
inWorld: self world
onCancelReturn: self selectedMember fileComment
acceptOnCR: true.
self selectedMember fileComment: newName.!
----- Method: ArchiveViewer>>contents (in category 'initialization') -----
contents
| contents errorMessage |
self selectedMember ifNil: [^ ''].
viewAllContents ifFalse: [^ self briefContents].
[ contents := self selectedMember contents ]
on: CRCError
do: [ :ex | errorMessage := String streamContents: [ :stream |
stream nextPutAll: '********** WARNING!! Member is corrupt!! [ ';
nextPutAll: (ex messageText copyUpToLast: $( );
nextPutAll: '] **********'; cr ].
ex proceed ].
^self selectedMember isCorrupt
ifFalse: [ contents ]
ifTrue: [ errorMessage, contents ]!
----- Method: ArchiveViewer>>contents: (in category 'initialization') -----
contents: aText
self shouldNotImplement.!
----- Method: ArchiveViewer>>createButtonBar (in category 'initialization') -----
createButtonBar
| bar button narrowFont registeredFonts |
registeredFonts := OrderedCollection new.
TextStyle knownTextStylesWithoutDefault do:
[:st | (TextStyle named: st) fonts do: [:f | registeredFonts addLast: f]].
narrowFont := registeredFonts detectMin:
[:ea | ea widthOfString: 'Contents' from: 1 to: 8].
bar := AlignmentMorph newRow.
bar
color: self defaultBackgroundColor;
rubberBandCells: false;
vResizing: #shrinkWrap;
cellInset: 6 @ 0.
#(#('New\Archive' #canCreateNewArchive #createNewArchive 'Create a new, empty archive and discard this one') #('Load\Archive' #canOpenNewArchive #openNewArchive 'Open another archive and discard this one') #('Save\Archive As' #canSaveArchive #saveArchive 'Save this archive under a new name') #('Extract\All' #canExtractAll #extractAll 'Extract all this archive''s members into a directory') #('Add\File' #canAddMember #addMember 'Add a file to this archive') #('Add from\Clipboard' #canAddMember #addMemberFromClipboard 'Add the contents of the clipboard as a new file') #('Add\Directory' #canAddMember #addDirectory 'Add the entire contents of a directory, with all of its subdirectories') #('Extract\Member As' #canExtractMember #extractMember 'Extract the selected member to a file') #('Delete\Member' #canDeleteMember #deleteMember 'Remove the selected member from this archive') #('Rename\Member' #canRenameMember #renameMember 'Rename the selected member') #('View All\Contents' #canViewAllContents #changeViewAllContents 'Toggle the view of all the selected member''s contents'))
do:
[:arr |
| buttonLabel |
buttonLabel := (TextMorph new)
string: arr first withCRs
fontName: narrowFont familyName
size: narrowFont pointSize
wrap: false;
hResizing: #shrinkWrap;
lock;
yourself.
(button := PluggableButtonMorph
on: self
getState: arr second
action: arr third)
vResizing: #shrinkWrap;
hResizing: #spaceFill;
onColor: self buttonOnColor offColor: self buttonOffColor;
label: buttonLabel;
setBalloonText: arr fourth.
bar addMorphBack: button.
buttonLabel composeToBounds].
^bar!
----- Method: ArchiveViewer>>createListHeadingUsingFont: (in category 'initialization') -----
createListHeadingUsingFont: font
| sm |
sm := StringMorph contents: ' order uncomp comp CRC-32 date time file name'.
font ifNotNil: [ sm font: font ].
^(AlignmentMorph newColumn)
color: self defaultBackgroundColor;
addMorph: sm;
yourself.!
----- Method: ArchiveViewer>>createNewArchive (in category 'archive operations') -----
createNewArchive
self setLabel: '(new archive)'.
archive := ZipArchive new.
self memberIndex: 0.
self changed: #memberList.!
----- Method: ArchiveViewer>>createWindow (in category 'initialization') -----
createWindow
| list heading font text buttonBar |
font := (TextStyle named: #DefaultFixedTextStyle)
ifNotNilDo: [ :ts | ts fontArray first].
buttonBar := self createButtonBar.
self addMorph: buttonBar
fullFrame: (LayoutFrame fractions: (0 at 0 corner: 1.0 at 0.0) offsets: (0 at 0 corner: 0 at 44)).
self minimumExtent: (buttonBar fullBounds width + 20) @ 230.
self extent: self minimumExtent.
heading := self createListHeadingUsingFont: font.
self addMorph: heading
fullFrame: (LayoutFrame fractions: (0 at 0 corner: 1.0 at 0.0) offsets: (0 at 44 corner: 0 at 60)).
(list := PluggableListMorph new)
on: self list: #memberList
selected: #memberIndex changeSelected: #memberIndex:
menu: #memberMenu:shifted: keystroke: nil.
list color: self defaultBackgroundColor.
font ifNotNil: [list font: font].
self addMorph: list
fullFrame: (LayoutFrame fractions: (0 at 0 corner: 1.0 at 0.8) offsets: (0 at 60 corner: 0 at 0)).
text := PluggableTextMorph on: self
text: #contents accept: nil
readSelection: nil menu: nil.
self addMorph: text
frame: (0 at 0.8 corner: 1.0 at 1.0).
text lock.
self setLabel: 'Ned''s Zip Viewer'!
----- Method: ArchiveViewer>>deleteMember (in category 'member operations') -----
deleteMember
self canDeleteMember ifFalse: [ ^self ].
archive removeMember: self selectedMember.
self memberIndex: 0.
self changed: #memberList.
!
----- Method: ArchiveViewer>>directory (in category 'accessing') -----
directory
"For compatibility with file list."
^self error: 'should use readOnlyStream instead!!'!
----- Method: ArchiveViewer>>displayLineFor: (in category 'member list') -----
displayLineFor: aMember
| stream dateTime index |
index := self archive members indexOf: aMember.
stream := WriteStream on: (String new: 60).
dateTime := Time dateAndTimeFromSeconds: aMember lastModTime.
stream
nextPutAll: (index printString padded: #left to: 4 with: $ );
space;
nextPutAll: (aMember uncompressedSize printString padded: #left to: 8 with: $ );
space; space;
nextPutAll: (aMember compressedSize printString padded: #left to: 8 with: $ );
space; space;
nextPutAll: (aMember crc32String );
space; space.
dateTime first printOn: stream format: #(3 2 1 $- 2 1 2).
stream space; space.
dateTime second print24: true showSeconds: false on: stream.
stream space; space;
nextPutAll: (aMember fileName ).
^stream contents!
----- Method: ArchiveViewer>>downMember (in category 'member order') -----
downMember
| temp |
temp := (self archive members) at: memberIndex.
self archive members at: memberIndex put: (self archive members at: memberIndex + 1).
self archive members at: (memberIndex +1) put: temp.
self memberIndex: 0.
self changed: #memberList.!
----- Method: ArchiveViewer>>extractAll (in category 'archive operations') -----
extractAll
| directory |
self canExtractAll ifFalse: [^ self].
directory := FileList2 modalFolderSelector ifNil: [^ self].
archive extractAllTo: directory.!
----- Method: ArchiveViewer>>extractAllPossibleInDirectory: (in category 'archive operations') -----
extractAllPossibleInDirectory: directory
"Answer true if I can extract all the files in the given directory safely.
Inform the user as to problems."
| conflicts |
self canExtractAll ifFalse: [ ^false ].
conflicts := Set new.
self members do: [ :ea | | fullName |
fullName := directory fullNameFor: ea localFileName.
(ea usesFileNamed: fullName) ifTrue: [ conflicts add: fullName ].
].
conflicts notEmpty ifTrue: [ | str |
str := WriteStream on: (String new: 200).
str nextPutAll: 'The following file(s) are needed by archive members and cannot be overwritten:';
cr.
conflicts do: [ :ea | str nextPutAll: ea ] separatedBy: [ str cr ].
self inform: str contents.
^false.
].
conflicts := Set new.
self members do: [ :ea | | fullName |
fullName := directory relativeNameFor: ea localFileName.
(directory fileExists: fullName)
ifTrue: [ conflicts add: fullName ].
].
conflicts notEmpty ifTrue: [ | str |
str := WriteStream on: (String new: 200).
str nextPutAll: 'The following file(s) will be overwritten:'; cr.
conflicts do: [ :ea | str nextPutAll: ea ] separatedBy: [ str cr ].
str cr; nextPutAll: 'Is this OK?'.
^self confirm: str contents.
].
^true.
!
----- Method: ArchiveViewer>>extractDirectoriesIntoDirectory: (in category 'archive operations') -----
extractDirectoriesIntoDirectory: directory
(self members select: [:ea | ea isDirectory])
do: [:ea | ea extractInDirectory: directory]!
----- Method: ArchiveViewer>>extractFilesIntoDirectory: (in category 'archive operations') -----
extractFilesIntoDirectory: directory
(self members reject: [:ea | ea isDirectory])
do: [:ea | ea extractInDirectory: directory]!
----- Method: ArchiveViewer>>extractMember (in category 'member operations') -----
extractMember
"Extract the member after prompting for a filename.
Answer the filename, or nil if error."
| result name |
self canExtractMember ifFalse: [ ^nil ].
result := StandardFileMenu newFile.
result ifNil: [ ^nil ].
name := (result directory fullNameFor: result name).
(archive canWriteToFileNamed: name)
ifFalse: [ self inform: name, ' is used by one or more members
in your archive, and cannot be overwritten.
Try extracting to another file name'.
^nil ].
self selectedMember extractToFileNamed: name.
^name!
----- Method: ArchiveViewer>>fileName (in category 'accessing') -----
fileName
^fileName!
----- Method: ArchiveViewer>>fileName: (in category 'initialization') -----
fileName: aString
archive := ZipArchive new readFrom: aString.
self setLabel: aString.
self memberIndex: 0.
self changed: #memberList!
----- Method: ArchiveViewer>>fullName (in category 'accessing') -----
fullName
"For compatibility with FileList services.
If this is called, it means that a service that requires a real filename has been requested.
So extract the selected member to a temporary file and return that name."
| fullName dir |
self canExtractMember ifFalse: [ ^nil ].
dir := FileDirectory default directoryNamed: '.archiveViewerTemp'.
fullName := dir fullNameFor: self selectedMember localFileName.
self selectedMember extractInDirectory: dir.
^fullName!
----- Method: ArchiveViewer>>highlightMemberList:with: (in category 'member list') -----
highlightMemberList: list with: morphList
(morphList at: self memberIndex) color: Color red!
----- Method: ArchiveViewer>>initialize (in category 'initialization') -----
initialize
super initialize.
memberIndex := 0.
viewAllContents := false.
!
----- Method: ArchiveViewer>>initializeToStandAlone (in category 'parts bin') -----
initializeToStandAlone
self initialize createWindow.!
----- Method: ArchiveViewer>>inspectMember (in category 'member operations') -----
inspectMember
self selectedMember inspect!
----- Method: ArchiveViewer>>memberIndex (in category 'member list') -----
memberIndex
^memberIndex!
----- Method: ArchiveViewer>>memberIndex: (in category 'member list') -----
memberIndex: n
memberIndex := n.
viewAllContents := false.
self changed: #memberIndex.
self changed: #contents.!
----- Method: ArchiveViewer>>memberList (in category 'member list') -----
memberList
^ self members collect: [ :ea | self displayLineFor: ea ]!
----- Method: ArchiveViewer>>memberMenu:shifted: (in category 'member list') -----
memberMenu: menu shifted: shifted
| services |
menu
add: 'Comment archive' target: self selector: #commentArchive;
balloonTextForLastItem: 'Add a comment for the entire archive'.
self selectedMember ifNotNilDo: [ :member |
menu
addLine;
add: 'Inspect member' target: self selector: #inspectMember;
balloonTextForLastItem: 'Inspect the selected member';
add: 'Comment member' target: self selector: #commentMember;
balloonTextForLastItem: 'Add a comment for the selected member';
addLine;
add: 'member go up in order ' target: self selector: #upMember;
add: 'member go down in order ' target: self selector: #downMember;
add: 'select member order ' target: self selector: #toIndexPlace;
addLine.
services := FileList itemsForFile: member fileName.
menu addServices2: services for: self extraLines: #().
].
^menu!
----- Method: ArchiveViewer>>members (in category 'accessing') -----
members
^archive ifNil: [ #() asOrderedCollection ]
ifNotNil: [ archive members asOrderedCollection ]!
----- Method: ArchiveViewer>>openNewArchive (in category 'archive operations') -----
openNewArchive
| result |
result := FileList2 modalFileSelector .
result ifNil: [ ^self ].
self fileName: (result directory fullNameFor: result name).
!
----- Method: ArchiveViewer>>perform:orSendTo: (in category 'message handling') -----
perform: selector orSendTo: otherTarget
^ self perform: selector!
----- Method: ArchiveViewer>>readOnlyStream (in category 'accessing') -----
readOnlyStream
"Answer a read-only stream on the selected member.
For the various stream-reading services."
^self selectedMember ifNotNilDo: [ :mem | mem contentStream ascii ]!
----- Method: ArchiveViewer>>renameMember (in category 'member operations') -----
renameMember
| newName |
self canRenameMember ifFalse: [ ^self ].
newName := FillInTheBlankMorph
request: 'New name for member:'
initialAnswer: self selectedMember fileName.
newName notEmpty ifTrue: [
self selectedMember fileName: newName.
self changed: #memberList
]!
----- Method: ArchiveViewer>>saveArchive (in category 'archive operations') -----
saveArchive
| result name |
name := FileDirectory localNameFor: labelString .
self canSaveArchive ifFalse: [ ^self ].
result := UIManager default
request: 'Name this zip '
initialAnswer: name.
result ifNil: [ ^self ].
(archive canWriteToFileNamed: result)
ifFalse: [ self inform: name, ' is used by one or more members
in your archive, and cannot be overwritten.
Try writing to another file name'.
^self ].
[ archive writeToFileNamed: result ] on: Error do: [ :ex | self inform: ex description. ].
self setLabel: name asString.
self changed: #memberList "in case CRC's and compressed sizes got set"!
----- Method: ArchiveViewer>>selectedMember (in category 'accessing') -----
selectedMember
^memberIndex
ifNil: [ nil ]
ifNotNil: [ self members at: memberIndex ifAbsent: [ ] ]!
----- Method: ArchiveViewer>>stream: (in category 'initialization') -----
stream: aStream
archive := ZipArchive new readFrom: aStream.
self setLabel: aStream fullName.
self memberIndex: 0.
self changed: #memberList!
----- Method: ArchiveViewer>>toIndexPlace (in category 'member order') -----
toIndexPlace
| index max temp |
max := self archive members size.
index :=0.
[index := (UIManager default
request: 'To which index '
initialAnswer: '1') asInteger.
index between: 1 and: max] whileFalse.
temp := (self archive members) at: memberIndex.
self archive members at: memberIndex put: (self archive members at: index).
self archive members at: index put: temp.
self memberIndex: 0.
self changed: #memberList.!
----- Method: ArchiveViewer>>upMember (in category 'member order') -----
upMember
| temp |
temp := (self archive members) at: memberIndex.
self archive members at: memberIndex put: (self archive members at: memberIndex -1).
self archive members at: (memberIndex -1) put: temp.
self memberIndex: 0.
self changed: #memberList.!
----- Method: ArchiveViewer>>windowIsClosing (in category 'initialization') -----
windowIsClosing
archive ifNotNil: [ archive close ].!
----- Method: ArchiveViewer>>writePrependingFile (in category 'archive operations') -----
writePrependingFile
| result name prependedName |
self canSaveArchive ifFalse: [ ^self ].
result := (StandardFileMenu newFileMenu: FileDirectory default)
startUpWithCaption: 'Destination Zip File Name:'.
result ifNil: [ ^self ].
name := result directory fullNameFor: result name.
(archive canWriteToFileNamed: name)
ifFalse: [ self inform: name, ' is used by one or more members
in your archive, and cannot be overwritten.
Try writing to another file name'.
^self ].
result := (StandardFileMenu oldFileMenu: FileDirectory default)
startUpWithCaption: 'Prepended File:'.
result ifNil: [ ^self ].
prependedName := result directory fullNameFor: result name.
[ archive writeToFileNamed: name prependingFileNamed: prependedName ]
on: Error
do: [ :ex | self inform: ex description. ].
self changed: #memberList "in case CRC's and compressed sizes got set"!
StandardSystemController subclass: #DeferredActionStandardSystemController
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Process Browser'!
!DeferredActionStandardSystemController commentStamp: 'dtl 9/20/2009 18:39' prior: 0!
Deprecated (Sept 2009) - The DeferredActionStandardSystemController has been merged into Controller. This class remains as a stub to guard ensure that any external packages that depend on it remain functional.
This is a StandardSystemController that can queue up objects to be evaluated before its control loop.!
----- Method: ComponentLayout>>inspectModelInMorphic (in category '*Tools') -----
inspectModelInMorphic
| insp |
insp := InspectorBrowser openOn: self model.
self world addMorph: insp; startStepping: insp!
TestCase subclass: #BrowseTest
instanceVariableNames: 'originalBrowserClass originalHierarchyBrowserClass'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser-Tests'!
----- Method: BrowseTest>>currentBrowsers (in category 'private') -----
currentBrowsers
^ (ActiveWorld submorphs
select: [:each | (each isKindOf: SystemWindow)
and: [each model isKindOf: Browser]]) asSet!
----- Method: BrowseTest>>currentHierarchyBrowsers (in category 'private') -----
currentHierarchyBrowsers
^ (ActiveWorld submorphs
select: [:each | (each isKindOf: SystemWindow)
and: [each model isKindOf: HierarchyBrowser]]) asSet!
----- Method: BrowseTest>>ensureMorphic (in category 'private') -----
ensureMorphic
self isMorphic ifFalse: [self error: 'This test should be run in Morphic'].!
----- Method: BrowseTest>>isMorphic (in category 'private') -----
isMorphic
^Smalltalk isMorphic!
----- Method: BrowseTest>>setUp (in category 'running') -----
setUp
| systemNavigation |
systemNavigation := SystemNavigation default.
originalBrowserClass := systemNavigation browserClass.
originalHierarchyBrowserClass := systemNavigation hierarchyBrowserClass.
systemNavigation browserClass: nil.
systemNavigation hierarchyBrowserClass: nil.
!
----- Method: BrowseTest>>tearDown (in category 'running') -----
tearDown
| systemNavigation |
systemNavigation := SystemNavigation default.
systemNavigation browserClass: originalBrowserClass.
systemNavigation hierarchyBrowserClass: originalHierarchyBrowserClass.!
----- Method: BrowseTest>>testBrowseClass (in category 'testing') -----
testBrowseClass
"self debug: #testBrowseClass"
| browsersBefore browsersAfter opened |
self ensureMorphic.
browsersBefore := self currentBrowsers.
1 class browse.
browsersAfter := self currentBrowsers.
self assert: (browsersAfter size = (browsersBefore size + 1)).
opened := browsersAfter removeAll: browsersBefore; yourself.
self assert: (opened size = 1).
opened := opened asArray first.
self assert: (opened model selectedClass == SmallInteger).
opened delete
!
----- Method: BrowseTest>>testBrowseHierarchyClass (in category 'testing') -----
testBrowseHierarchyClass
"self debug: #testBrowseHierarchyClass"
| browsersBefore browsersAfter opened |
self ensureMorphic.
browsersBefore := self currentHierarchyBrowsers.
1 class browseHierarchy.
browsersAfter := self currentHierarchyBrowsers.
self assert: (browsersAfter size = (browsersBefore size + 1)).
opened := browsersAfter removeAll: browsersBefore; yourself.
self assert: (opened size = 1).
opened := opened asArray first.
self assert: (opened model selectedClass == SmallInteger).
opened delete
!
----- Method: BrowseTest>>testBrowseHierarchyInstance (in category 'testing') -----
testBrowseHierarchyInstance
"self debug: #testBrowseHierarchyInstance"
| browsersBefore browsersAfter opened |
self ensureMorphic.
browsersBefore := self currentHierarchyBrowsers.
1 browseHierarchy.
browsersAfter := self currentHierarchyBrowsers.
self assert: (browsersAfter size = (browsersBefore size + 1)).
opened := browsersAfter removeAll: browsersBefore; yourself.
self assert: (opened size = 1).
opened := opened asArray first.
self assert: (opened model selectedClass == SmallInteger).
opened delete
!
----- Method: BrowseTest>>testBrowseHierarchyMataclass (in category 'testing') -----
testBrowseHierarchyMataclass
"self debug: #testBrowseHierarchyMataclass"
| browsersBefore browsersAfter opened |
self ensureMorphic.
browsersBefore := self currentHierarchyBrowsers.
1 class class browseHierarchy.
browsersAfter := self currentHierarchyBrowsers.
self assert: (browsersAfter size = (browsersBefore size + 1)).
opened := browsersAfter removeAll: browsersBefore; yourself.
self assert: (opened size = 1).
opened := opened asArray first.
self assert: (opened model selectedClass == Metaclass).
opened delete
!
----- Method: BrowseTest>>testBrowseInstance (in category 'testing') -----
testBrowseInstance
"self debug: #testBrowseInstance"
| browsersBefore browsersAfter opened |
self ensureMorphic.
browsersBefore := self currentBrowsers.
1 browse.
browsersAfter := self currentBrowsers.
self assert: (browsersAfter size = (browsersBefore size + 1)).
opened := browsersAfter removeAll: browsersBefore; yourself.
self assert: (opened size = 1).
opened := opened asArray first.
self assert: (opened model selectedClass == SmallInteger).
opened delete
!
----- Method: BrowseTest>>testBrowseMetaclass (in category 'testing') -----
testBrowseMetaclass
"self debug: #testBrowseMetaclass"
| browsersBefore browsersAfter opened |
self ensureMorphic.
browsersBefore := self currentBrowsers.
1 class class browse.
browsersAfter := self currentBrowsers.
self assert: (browsersAfter size = (browsersBefore size + 1)).
opened := browsersAfter removeAll: browsersBefore; yourself.
self assert: (opened size = 1).
opened := opened asArray first.
self assert: (opened model selectedClass == Metaclass).
opened delete
!
TestCase subclass: #BrowserHierarchicalListTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser-Tests'!
!BrowserHierarchicalListTest commentStamp: 'rkrk 8/24/2009 05:11' prior: 0!
Tests the optional hierarchical class ordering of Browser.!
----- Method: BrowserHierarchicalListTest>>assertCorrectOrderOf:followedBy:in: (in category 'assertion') -----
assertCorrectOrderOf: classB followedBy: classA in: classCollection
"classB comes before classA. Assert that classB is a superclass of classB or that
a common superclass is in front of both"
| commonSuperclasses commonSuperclass classAIndex classBIndex superIndex |
classA == classB ifTrue: [^ self].
(classA inheritsFrom: classB) ifTrue: [^ self].
commonSuperclasses := classA withAllSuperclasses intersection: classB withAllSuperclasses.
commonSuperclass := commonSuperclasses first.
(classCollection includes: commonSuperclass) ifFalse: [^ self].
classAIndex := classCollection indexOf: classA.
classBIndex := classCollection indexOf: classB.
superIndex := classCollection indexOf: commonSuperclass.
(superIndex < classAIndex and: [superIndex < classBIndex]) ifTrue: [^self].
self fail.!
----- Method: BrowserHierarchicalListTest>>hierarchicalClassListForCategory: (in category 'helper') -----
hierarchicalClassListForCategory: category
| b index |
b := Browser new.
index := b systemCategoryList indexOf: category.
b systemCategoryListIndex: index.
^ b hierarchicalClassList.
!
----- Method: BrowserHierarchicalListTest>>nameToClass: (in category 'helper') -----
nameToClass: classNameWithIndent
^ Smalltalk classNamed: classNameWithIndent withoutLeadingBlanks asSymbol!
----- Method: BrowserHierarchicalListTest>>testListClassesHierarchically1 (in category 'tests') -----
testListClassesHierarchically1
| result classes category |
category := 'Collections-Abstract'.
result := self hierarchicalClassListForCategory: category.
self assert: (SystemOrganization listAtCategoryNamed: category) size equals: result size.
classes := result collect: [:ea | self nameToClass: ea].
classes withIndexDo: [:ea : i |
classes
from: 1 to: i
do: [:other | self assertCorrectOrderOf: other followedBy: ea in: classes]].!
----- Method: BrowserHierarchicalListTest>>testListClassesHierarchically2 (in category 'tests') -----
testListClassesHierarchically2
| result classes category |
category := 'Tools-Browser'.
result := self hierarchicalClassListForCategory: category.
self assert: (SystemOrganization listAtCategoryNamed: category) size equals: result size.
classes := result collect: [:ea | self nameToClass: ea].
classes withIndexDo: [:ea : i |
classes
from: 1 to: i
do: [:other | self assertCorrectOrderOf: other followedBy: ea in: classes]].!
----- Method: BrowserHierarchicalListTest>>testListClassesHierarchicallyIndent (in category 'tests') -----
testListClassesHierarchicallyIndent
| result dict indent |
result := self hierarchicalClassListForCategory: 'Tools-Browser'.
"Create class->indent mapping"
dict := result inject: Dictionary new into: [:classIndentMapping :className |
indent := className count: [:char | char = Character space or: [char = Character tab]].
classIndentMapping at: (self nameToClass: className) put: indent.
classIndentMapping].
"assert that indent of class is larger than indent of superclass"
dict keysAndValuesDo: [:class :myIndent |
dict at: class superclass ifPresent: [:superIndent |
self assert: myIndent > superIndent]].!
TestCase subclass: #DebuggerUnwindBug
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Debugger-Tests'!
----- Method: DebuggerUnwindBug>>testUnwindBlock (in category 'as yet unclassified') -----
testUnwindBlock
"test if unwind blocks work properly"
| sema process |
sema := Semaphore forMutualExclusion.
self assert: sema isSignaled.
"deadlock on the semaphore"
process := [sema critical:[sema wait]] forkAt: Processor userInterruptPriority.
self deny: sema isSignaled.
"terminate process"
process terminate.
self assert: sema isSignaled.
!
----- Method: DebuggerUnwindBug>>testUnwindDebugger (in category 'as yet unclassified') -----
testUnwindDebugger
"test if unwind blocks work properly when a debugger is closed"
| sema process debugger top |
sema := Semaphore forMutualExclusion.
self assert: sema isSignaled.
process := [sema critical:[sema wait]] forkAt: Processor userInterruptPriority.
self deny: sema isSignaled.
"everything set up here - open a debug notifier"
debugger := Debugger openInterrupt: 'test' onProcess: process.
"get into the debugger"
debugger debug.
top := debugger topView.
"set top context"
debugger toggleContextStackIndex: 1.
"close debugger"
top delete.
"and see if unwind protection worked"
self assert: sema isSignaled.!
----- Method: DebuggerUnwindBug>>testUnwindDebuggerWithStep (in category 'as yet unclassified') -----
testUnwindDebuggerWithStep
"test if unwind blocks work properly when a debugger is closed"
| sema process debugger top |
sema := Semaphore forMutualExclusion.
self assert: sema isSignaled.
process := [sema critical:[sema wait]] forkAt: Processor userInterruptPriority.
self deny: sema isSignaled.
"everything set up here - open a debug notifier"
debugger := Debugger openInterrupt: 'test' onProcess: process.
"get into the debugger"
debugger debug.
top := debugger topView.
"set top context"
debugger toggleContextStackIndex: 1.
"do single step"
debugger doStep.
"close debugger"
top delete.
"and see if unwind protection worked"
self assert: sema isSignaled.!
TestCase subclass: #FileList2ModalDialogsTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-FileList-Tests'!
!FileList2ModalDialogsTest commentStamp: '<historical>' prior: 0!
TestRunner open!
----- Method: FileList2ModalDialogsTest>>testModalFileSelector (in category 'running') -----
testModalFileSelector
| window fileList2 |
window := FileList2 morphicViewFileSelector.
window openCenteredInWorld.
fileList2 := window valueOfProperty: #fileListModel.
fileList2 fileListIndex: 1.
window delete.
self assert: fileList2 getSelectedFile isNil.
fileList2 okHit.
self deny: fileList2 getSelectedFile isNil
!
----- Method: FileList2ModalDialogsTest>>testModalFileSelectorForSuffixes (in category 'running') -----
testModalFileSelectorForSuffixes
| window fileList2 |
window := FileList2 morphicViewFileSelectorForSuffixes: nil.
window openCenteredInWorld.
fileList2 := window valueOfProperty: #fileListModel.
fileList2 fileListIndex: 1.
window delete.
self assert: fileList2 getSelectedFile isNil.
fileList2 okHit.
self deny: fileList2 getSelectedFile isNil
!
----- Method: FileList2ModalDialogsTest>>testModalFolderSelector (in category 'running') -----
testModalFolderSelector
| window fileList2 |
window := FileList2 morphicViewFolderSelector.
fileList2 := window model.
window openInWorld: self currentWorld extent: 300 at 400.
fileList2 fileListIndex: 1.
window delete.
self assert: fileList2 getSelectedDirectory withoutListWrapper isNil.
fileList2 okHit.
self deny: fileList2 getSelectedDirectory withoutListWrapper isNil
!
----- Method: FileList2ModalDialogsTest>>testModalFolderSelectorForProjectLoad (in category 'running') -----
testModalFolderSelectorForProjectLoad
| window fileList2 w |
window := FileList2
morphicViewProjectLoader2InWorld: self currentWorld
reallyLoad: false.
fileList2 := window valueOfProperty: #FileList.
w := self currentWorld.
window position: w topLeft + (w extent - window extent // 2).
window openInWorld: w.
window delete.
self assert: fileList2 getSelectedDirectory withoutListWrapper isNil.
fileList2 okHit.
self deny: fileList2 getSelectedDirectory withoutListWrapper isNil
!
FileList2 subclass: #FileChooser
instanceVariableNames: 'view caption captionMorph captionBox cancelButton okButton buttonPane captionPane directoryPane filePane showShortFileNames'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-FileList'!
!FileChooser commentStamp: 'miki 8/15/2005 12:07' prior: 0!
This class provides a simple "modal" dialog box to choose a file, with a directory tree, a file list, and open and cancel buttons. It is meant to be an improvement of FileList2 modalFileSelector.
An applictaion can customize the user interface in a straightforward way. Creation of the file choser is done in several steps by calling various helper methods..
The order of the method calls when creating a customized file chooser are important. The UI must be created before methods that change the attributes of the UI can be called. You can either start by creating the default UI, and them modify the morphs in the file chooser (there are methods to access the buttons and the panes).
You can also build a completely custom UI, by writing your own methods for creating the layout etc. One way to do this is to subclass FileChooser and override the methods you want to change, andother way is to supply your own morphic view to the file chooser. This must be an instance of MorphicModel or a subclass of it, because the file chooser uses the model functionality.
There are two varieties of the UI, one that is supposed to be like a dialog box (uses colors from the menu preferences in class Preference), and one is using a system window. The way a system window works turns out to be somehat different from how a plain Morphic Model works, and this is why there are separate methods for creating the dialog box UI and the system window UI.
On the class side, there are examples that shows differents ways to use this class.
On the to do list is adding support for a file save dialog box, with a directory tree and a text input field for typing a file name.
(Mikael Kindborg, 050815)
!
----- Method: FileChooser class>>example1 (in category 'examples') -----
example1
"Open file chooser with the standard dialog box UI."
"FileChooser example1"
| fc stream |
fc := FileChooser new.
fc initalizeAsDialogBox.
stream := fc open.
stream inspect.!
----- Method: FileChooser class>>example2 (in category 'examples') -----
example2
"Open file chooser with a system window UI."
"FileChooser example2"
| fc stream |
fc := FileChooser new.
fc initalizeAsSystemWindow.
stream := fc open.
stream inspect.!
----- Method: FileChooser class>>example3 (in category 'examples') -----
example3
"Open file chooser with a system window UI that has a caption pane and shows only picture files."
"FileChooser example3"
| fc stream |
fc := FileChooser new.
fc initalizeAsSystemWindowWithCaptionPane.
fc setCaption: 'Select a picture file' translated.
fc setSuffixes: {'png' . 'gif' . 'bmp' . 'jpg' . 'jpeg' }.
stream := fc open.
stream ifNotNil: [(Form fromBinaryStream: stream) asMorph openInHand].!
----- Method: FileChooser class>>example4 (in category 'examples') -----
example4
"Open file chooser with a customized dialog box UI. The order of the messages is important. In general, call the initialize method first, then modify things, and finally call open."
"FileChooser example4"
| fc stream |
fc := FileChooser new.
fc initalizeAsDialogBox.
fc setDirectory: FileDirectory root.
fc setSuffixes: {'png' . 'gif' . 'bmp' . 'jpg' . 'jpeg' }.
fc setCaption: 'Select a picture file' translated.
fc morphicView
borderColor: Color black;
borderWidth: 2;
color: Color white.
fc setPaneColor: Color gray muchLighter.
fc captionPane color: Color orange muchLighter.
fc okButton color: Color green muchLighter.
fc cancelButton color: Color blue muchLighter.
fc morphicView position: 20 at 20.
stream := fc open.
stream ifNotNil: [(Form fromBinaryStream: stream) asMorph openInHand].!
----- Method: FileChooser>>addFullPanesTo:from: (in category 'ui creation') -----
addFullPanesTo: aMorph from: aCollection
| frame |
aCollection do: [ :each |
frame := LayoutFrame
fractions: each second
offsets: each third.
aMorph addMorph: each first fullFrame: frame.
]!
----- Method: FileChooser>>buttonPane (in category 'accessing') -----
buttonPane
^buttonPane!
----- Method: FileChooser>>cancelButton (in category 'accessing') -----
cancelButton
^cancelButton!
----- Method: FileChooser>>caption (in category 'accessing') -----
caption
^caption!
----- Method: FileChooser>>captionPane (in category 'accessing') -----
captionPane
^captionPane!
----- Method: FileChooser>>centerMorphicView (in category 'ui creation') -----
centerMorphicView
self morphicView
fullBounds;
position: Display extent - self morphicView extent // 2.
!
----- Method: FileChooser>>createCancelButton (in category 'ui creation') -----
createCancelButton
cancelButton := SimpleButtonMorph new.
cancelButton
label: 'Cancel' translated;
color: Color transparent;
borderColor: Color black;
borderWidth: 1.
cancelButton
on: #mouseUp
send: #cancelHit
to: self.
^cancelButton
!
----- Method: FileChooser>>createDialogBoxButtonPane (in category 'ui creation') -----
createDialogBoxButtonPane
"Create buttons suitable for a MorphicModel file chooser."
buttonPane := AlignmentMorph new.
buttonPane
layoutPolicy: ProportionalLayout new;
color: Color transparent;
borderWidth: 0.
self createOkButton.
self createCancelButton.
buttonPane addMorph: self cancelButton
fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 0.49 @ 1.0)
offsets: (0 @ 0 corner: 0 @ 0)).
buttonPane addMorph: self okButton
fullFrame: (LayoutFrame fractions: (0.51 @ 0 corner: 1.0 @ 1.0)
offsets: (0 @ 0 corner: 0 @ 0)).
^buttonPane!
----- Method: FileChooser>>createDialogBoxCaptionPane (in category 'ui creation') -----
createDialogBoxCaptionPane
"Create a morph to hold the caption string. The caption is created in setCaption:"
| icon frame |
captionPane := AlignmentMorph new.
captionPane
color: Color transparent;
layoutPolicy: ProportionalLayout new.
"You can change the caption later by calling setCaption:"
self setCaption: 'Please select a file' translated.
self setCaptionFont: Preferences windowTitleFont.
icon := SketchMorph new.
icon form: MenuIcons openIcon.
captionPane addMorph: icon.
frame := LayoutFrame new.
frame
leftFraction: 0;
topFraction: 0.5;
leftOffset: icon form width // 2;
topOffset: (icon form width // 2) negated.
icon layoutFrame: frame.
^captionPane!
----- Method: FileChooser>>createDialogBoxLayout (in category 'ui creation') -----
createDialogBoxLayout
"Create a layout suitable for a MorphicModel file chooser."
| inset insetNeg captionTop captionBottom buttonsBottom buttonsTop contentTop contentBottom |
inset := 6.
insetNeg := inset negated.
captionTop := 0.
captionBottom := 33.
contentTop := captionBottom + inset.
contentBottom := -30 - inset - inset.
buttonsTop := contentBottom + inset.
buttonsBottom := insetNeg.
self addFullPanesTo: self morphicView
from: {
{
(self captionPane).
(0 @ 0 corner: 1 @ 0).
(0 @ captionTop corner: 0 @ captionBottom)
}.
{
(self buttonPane).
(0 @ 1 corner: 1 @ 1).
(inset @ buttonsTop corner: insetNeg @ buttonsBottom)
}.
{
(self directoryPane).
(0 @ 0 corner: 0.5 @ 1).
(inset @ contentTop corner: insetNeg @ contentBottom)
}.
{
(self filePane).
(0.5 @ 0 corner: 1 @ 1).
(inset @ contentTop corner: insetNeg @ contentBottom)
}
}!
----- Method: FileChooser>>createDialogBoxMorphicView (in category 'ui creation') -----
createDialogBoxMorphicView
| m |
m := MorphicModel new
layoutPolicy: ProportionalLayout new;
color: Preferences menuColor;
borderColor: Preferences menuBorderColor;
borderWidth: Preferences menuBorderWidth;
layoutInset: 0;
extent: 600 at 400.
self setMorphicView: m.
^m!
----- Method: FileChooser>>createDialogBoxUI (in category 'ui creation') -----
createDialogBoxUI
"This method creates UI components and a layout that are suitable for a MorphicModel. Also centers the morphic view in the world. Note that the order of the method calls are important if you modify this."
self
createDialogBoxMorphicView;
createDialogBoxCaptionPane;
createDialogBoxButtonPane;
createDirectoryPane;
createFilePane;
createDialogBoxLayout;
centerMorphicView.
^self morphicView!
----- Method: FileChooser>>createDirectoryPane (in category 'ui creation') -----
createDirectoryPane
directoryPane := self morphicDirectoryTreePane.
directoryPane borderWidth: 0.
^directoryPane!
----- Method: FileChooser>>createFilePane (in category 'ui creation') -----
createFilePane
filePane := self morphicFileListPane.
filePane borderWidth: 0.
^filePane!
----- Method: FileChooser>>createOkButton (in category 'ui creation') -----
createOkButton
okButton := SimpleButtonMorph new.
okButton
label: 'Open' translated;
color: Color transparent;
borderColor: Color black;
borderWidth: 1.
okButton
on: #mouseUp
send: #okHit
to: self.
^okButton!
----- Method: FileChooser>>createSystemWindowButtonPane (in category 'ui creation') -----
createSystemWindowButtonPane
"Create buttons suitable for a SystemWindow file chooser."
self optionalButtonSpecs: self okayAndCancelServices.
buttonPane := self optionalButtonRow.
okButton := buttonPane firstSubmorph.
cancelButton := buttonPane firstSubmorph.
^buttonPane!
----- Method: FileChooser>>createSystemWindowCaptionPane (in category 'ui creation') -----
createSystemWindowCaptionPane
"Create a morph to hold the caption string. The caption is created in setCaption:"
captionPane := AlignmentMorph new.
captionPane
color: Color transparent;
layoutPolicy: ProportionalLayout new.
"You can change the caption later by calling setCaption:"
self setCaption: 'Please select a file' translated.
^captionPane!
----- Method: FileChooser>>createSystemWindowLayout (in category 'ui creation') -----
createSystemWindowLayout
"Create a layout suitable for a SystemWindow file chooser."
| buttonsHeight |
buttonsHeight := 33.
self addFullPanesTo: self morphicView
from: {
{
(self buttonPane).
(0 @ 0 corner: 1 @ 0).
(0 @ 0 corner: 0 @ buttonsHeight)
}.
{
(self directoryPane).
(0 @ 0 corner: 0.5 @ 1).
(0 @ buttonsHeight corner: 0 @ 0)
}.
{
(self filePane).
(0.5 @ 0 corner: 1 @ 1).
(0 @ buttonsHeight corner: 0 @ 0)
}
}!
----- Method: FileChooser>>createSystemWindowLayoutWithCaptionPane (in category 'ui creation') -----
createSystemWindowLayoutWithCaptionPane
| buttonsHeight captionHeight |
buttonsHeight := 33.
captionHeight := 28.
self addFullPanesTo: self morphicView
from: {
{
(self captionPane).
(0 @ 0 corner: 1 @ 0).
(0 @ 0 corner: 0 @ captionHeight)
}.
{
(self buttonPane).
(0 @ 0 corner: 1 @ 0).
(0 @ captionHeight corner: 0 @ (captionHeight + buttonsHeight))
}.
{
(self directoryPane).
(0 @ 0 corner: 0.5 @ 1).
(0 @ (captionHeight + buttonsHeight) corner: 0 @ 0)
}.
{
(self filePane).
(0.5 @ 0 corner: 1 @ 1).
(0 @ (captionHeight + buttonsHeight) corner: 0 @ 0)
}
}!
----- Method: FileChooser>>createSystemWindowMorphicView (in category 'ui creation') -----
createSystemWindowMorphicView
| m |
m := SystemWindow labelled: 'Please select a file' translated. "self directory pathName."
"m deleteCloseBox."
self setMorphicView: m.!
----- Method: FileChooser>>createSystemWindowUI (in category 'ui creation') -----
createSystemWindowUI
"This method creates UI components and a layout that are suitable for a SystemWindow. Note that the order of the method calls are important."
self
createSystemWindowMorphicView;
createSystemWindowButtonPane;
createDirectoryPane;
createFilePane;
createSystemWindowLayout.
^self morphicView!
----- Method: FileChooser>>createSystemWindowUIWithCaptionPane (in category 'ui creation') -----
createSystemWindowUIWithCaptionPane
self
createSystemWindowMorphicView;
createSystemWindowCaptionPane;
createSystemWindowButtonPane;
createDirectoryPane;
createFilePane;
createSystemWindowLayoutWithCaptionPane.
^self morphicView!
----- Method: FileChooser>>directory (in category 'accessing') -----
directory
^super directory!
----- Method: FileChooser>>directoryPane (in category 'accessing') -----
directoryPane
^directoryPane!
----- Method: FileChooser>>fileNameFormattedFrom:sizePad: (in category 'updating') -----
fileNameFormattedFrom: entry sizePad: sizePad
"entry is a 5-element array of the form:
(name creationTime modificationTime dirFlag fileSize)"
"If the short file list flag is false, we send this on to the superclass."
| nameStr |
showShortFileNames
ifFalse: [^super fileNameFormattedFrom: entry sizePad: sizePad].
"Otherwise, just show the name of the file in the file list."
nameStr := (entry at: 4)
ifTrue: [entry first , self folderString]
ifFalse: [entry first].
^nameStr!
----- Method: FileChooser>>filePane (in category 'accessing') -----
filePane
^filePane!
----- Method: FileChooser>>initalizeAsDialogBox (in category 'initialization') -----
initalizeAsDialogBox
self initalizeBasicParameters.
self createDialogBoxUI.
self morphicView
useRoundedCorners;
color: Preferences menuColor;
adoptPaneColor: Preferences menuLineColor.
self
setCaptionColor: Preferences menuTitleColor;
setButtonColor: Preferences menuColor.!
----- Method: FileChooser>>initalizeAsSystemWindow (in category 'initialization') -----
initalizeAsSystemWindow
self initalizeBasicParameters.
self createSystemWindowUI.!
----- Method: FileChooser>>initalizeAsSystemWindowWithCaptionPane (in category 'initialization') -----
initalizeAsSystemWindowWithCaptionPane
self initalizeBasicParameters.
self createSystemWindowUIWithCaptionPane.!
----- Method: FileChooser>>initalizeBasicParameters (in category 'initialization') -----
initalizeBasicParameters
self showShortFileNames: true.
self setDirectory: FileDirectory default.!
----- Method: FileChooser>>morphicView (in category 'accessing') -----
morphicView
^view!
----- Method: FileChooser>>okButton (in category 'accessing') -----
okButton
^okButton!
----- Method: FileChooser>>open (in category 'open') -----
open
| model |
self postOpen. "Funny name in this context, should be renamed, but whatever..."
self morphicView openInWorld.
model := self morphicView model.
FileChooser modalLoopOn: self morphicView.
^ model getSelectedFile.
!
----- Method: FileChooser>>setButtonColor: (in category 'ui creation') -----
setButtonColor: aColor
self okButton color: aColor.
self cancelButton color: aColor.
!
----- Method: FileChooser>>setCaption: (in category 'ui creation') -----
setCaption: aString
| frame |
caption ifNil:
[caption := StringMorph new.
self captionPane addMorph: caption].
caption contents: aString.
frame := LayoutFrame new.
frame
leftFraction: 0.5;
topFraction: 0.5;
leftOffset: caption width negated // 2;
topOffset: caption height negated // 2.
caption layoutFrame: frame!
----- Method: FileChooser>>setCaptionColor: (in category 'ui creation') -----
setCaptionColor: aColor
self captionPane color: aColor!
----- Method: FileChooser>>setCaptionFont: (in category 'ui creation') -----
setCaptionFont: aFont
self caption font: aFont.
self setCaption: self caption contents asString.
!
----- Method: FileChooser>>setDirectory: (in category 'initialization') -----
setDirectory: aDir
^super directory: aDir!
----- Method: FileChooser>>setMorphicView: (in category 'initialization') -----
setMorphicView: aMorphicModel
view := aMorphicModel.
self modalView: view.
view model: self.!
----- Method: FileChooser>>setPaneColor: (in category 'ui creation') -----
setPaneColor: aColor
self morphicView
color: aColor;
adoptPaneColor: aColor.
!
----- Method: FileChooser>>setSuffixes: (in category 'initialization') -----
setSuffixes: aList
self fileSelectionBlock: [:entry :myPattern |
entry isDirectory
ifTrue:
[false]
ifFalse:
[aList includes: (FileDirectory extensionFor: entry name asLowercase)]] fixTemps!
----- Method: FileChooser>>showShortFileNames: (in category 'initialization') -----
showShortFileNames: aBoolean
showShortFileNames := aBoolean!
----- Method: FileChooser>>updateButtonRow (in category 'updating') -----
updateButtonRow
"Prevent updating of the the button row."!
PluggableTextMorph subclass: #BrowserCommentTextMorph
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
!BrowserCommentTextMorph commentStamp: '<historical>' prior: 0!
I am a PluggableTextMorph that knows enough to make myself invisible when necessary.!
----- Method: BrowserCommentTextMorph>>hideOrShowPane (in category 'displaying') -----
hideOrShowPane
(self model editSelection == #editClass)
ifTrue: [ self showPane ]
ifFalse: [ self hidePane ]!
----- Method: BrowserCommentTextMorph>>hidePane (in category 'displaying') -----
hidePane
| win |
self window ifNotNilDo: [:window | window removePaneSplitters].
self lowerPane ifNotNilDo:
[ :lp |
lp layoutFrame bottomFraction: self layoutFrame bottomFraction.
lp layoutFrame bottomOffset: SystemWindow borderWidth negated].
win := self window ifNil: [ ^self ].
self delete.
win updatePanesFromSubmorphs.
win addPaneSplitters!
----- Method: BrowserCommentTextMorph>>lowerPane (in category 'accessing') -----
lowerPane
"Answer the AlignmentMorph that I live beneath"
^self valueOfProperty: #browserLowerPane!
----- Method: BrowserCommentTextMorph>>noteNewOwner: (in category 'updating') -----
noteNewOwner: win
super noteNewOwner: win.
self setProperty: #browserWindow toValue: win.
win ifNil: [ ^self ].
win setProperty: #browserClassCommentPane toValue: self.
self setProperty: #browserLowerPane
toValue: (win submorphThat: [ :m |
m isAlignmentMorph
and: [ m layoutFrame bottomFraction notNil]
and: [ m layoutFrame bottomFraction >= self layoutFrame topFraction ]]
ifNone: [])!
----- Method: BrowserCommentTextMorph>>showPane (in category 'displaying') -----
showPane
owner ifNil: [
| win |
win := self window ifNil: [ ^self ].
win addMorph: self fullFrame: self layoutFrame.
win updatePanesFromSubmorphs ].
self lowerPane ifNotNilDo: [ :lp | lp layoutFrame bottomFraction: self layoutFrame topFraction ].
self window ifNotNilDo: [:win | win addPaneSplitters]!
----- Method: BrowserCommentTextMorph>>update: (in category 'updating') -----
update: anAspect
super update: anAspect.
anAspect == #editSelection ifFalse: [ ^self ].
self hideOrShowPane!
----- Method: BrowserCommentTextMorph>>window (in category 'accessing') -----
window
^self owner ifNil: [ self valueOfProperty: #browserWindow ].!
----- Method: ParagraphEditor>>browseChangeSetsWithSelector (in category '*Tools') -----
browseChangeSetsWithSelector
"Determine which, if any, change sets have at least one change for the selected selector, independent of class"
| aSelector |
self lineSelectAndEmptyCheck: [^ self].
(aSelector := self selectedSelector) == nil ifTrue: [^ view flash].
self terminateAndInitializeAround: [ChangeSorter browseChangeSetsWithSelector: aSelector]!
----- Method: ParagraphEditor>>browseItHere (in category '*Tools') -----
browseItHere
"Retarget the receiver's window to look at the selected class, if appropriate. 3/1/96 sw"
| aSymbol foundClass b |
(((b := model) isKindOf: Browser) and: [b couldBrowseAnyClass])
ifFalse: [^ view flash].
model okToChange ifFalse: [^ view flash].
self selectionInterval isEmpty ifTrue: [self selectWord].
(aSymbol := self selectedSymbol) isNil ifTrue: [^ view flash].
self terminateAndInitializeAround:
[foundClass := (Smalltalk at: aSymbol ifAbsent: [nil]).
foundClass isNil ifTrue: [^ view flash].
(foundClass isKindOf: Class)
ifTrue:
[model systemCategoryListIndex:
(model systemCategoryList indexOf: foundClass category).
model classListIndex: (model classList indexOf: foundClass name)]]!
----- Method: ParagraphEditor>>debug:receiver:in: (in category '*Tools') -----
debug: aCompiledMethod receiver: anObject in: evalContext
| selector guineaPig debugger context |
selector := evalContext isNil ifTrue: [#DoIt] ifFalse: [#DoItIn:].
anObject class addSelectorSilently: selector withMethod: aCompiledMethod.
guineaPig := evalContext isNil
ifTrue: [[anObject DoIt] newProcess]
ifFalse: [[anObject DoItIn: evalContext] newProcess].
context := guineaPig suspendedContext.
debugger := Debugger new
process: guineaPig
controller: ((Smalltalk isMorphic not and: [ScheduledControllers inActiveControllerProcess])
ifTrue: [ScheduledControllers activeController]
ifFalse: [nil])
context: context
isolationHead: nil.
debugger openFullNoSuspendLabel: 'Debug it'.
[debugger interruptedContext method == aCompiledMethod]
whileFalse: [debugger send].
anObject class basicRemoveSelector: selector!
----- Method: ParagraphEditor>>debugIt (in category '*Tools') -----
debugIt
| method receiver context |
(model respondsTo: #doItReceiver)
ifTrue:
[FakeClassPool adopt: model selectedClass.
receiver := model doItReceiver.
context := model doItContext]
ifFalse:
[receiver := context := nil].
self lineSelectAndEmptyCheck: [^self].
method := self compileSelectionFor: receiver in: context.
method notNil ifTrue:
[self debug: method receiver: receiver in: context].
FakeClassPool adopt: nil!
AbstractHierarchicalList subclass: #ObjectExplorer
instanceVariableNames: 'rootObject inspector monitorList'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Explorer'!
!ObjectExplorer commentStamp: '<historical>' prior: 0!
ObjectExplorer provides a hierarchical alternative to #inspect. Simply evaluate an expression like:
World explore
and enjoy.!
----- Method: ObjectExplorer class>>about (in category 'as yet unclassified') -----
about
StringHolder new textContents: self comment; openLabel: 'about ',self asString!
----- Method: ObjectExplorer>>chasePointers (in category 'menus') -----
chasePointers
"Open a PointerFinder on the selected item"
| path sel savedRoot saved |
path := OrderedCollection new.
sel := currentSelection.
[ sel isNil ] whileFalse: [ path addFirst: sel asString. sel := sel parent ].
path addFirst: #openPath.
path := path asArray.
savedRoot := rootObject.
saved := self object.
[ rootObject := nil.
self changed: #getList.
(Smalltalk includesKey: #PointerFinder)
ifTrue: [PointerFinder on: saved]
ifFalse: [self objectReferencesToSelection ]]
ensure: [ rootObject := savedRoot.
self changed: #getList.
self changed: path.
]!
----- Method: ObjectExplorer>>codePaneMenu:shifted: (in category 'menus') -----
codePaneMenu: aMenu shifted: shifted
"Note that unless we override perform:orSendTo:, PluggableTextController will respond to all menu items"
^ StringHolder basicNew codePaneMenu: aMenu shifted: shifted
!
----- Method: ObjectExplorer>>contentsSelection (in category 'accessing') -----
contentsSelection
"Return the interval of text in the code pane to select when I set the pane's contents"
^ 1 to: 0 "null selection"!
----- Method: ObjectExplorer>>defsOfSelection (in category 'menus') -----
defsOfSelection
"Open a browser on all defining references to the selected instance variable, if that's what's currently selected."
| aClass sel |
(aClass := self parentObject class) isVariable ifTrue: [^ self changed: #flash].
sel := self selector.
self systemNavigation browseAllStoresInto: sel from: aClass!
----- Method: ObjectExplorer>>doItContext (in category 'accessing') -----
doItContext
"Answer the context in which a text selection can be evaluated."
^nil!
----- Method: ObjectExplorer>>doItReceiver (in category 'accessing') -----
doItReceiver
"Answer the object that should be informed of the result of evaluating a
text selection."
currentSelection ifNil: [^rootObject].
^currentSelection withoutListWrapper
!
----- Method: ObjectExplorer>>doesNotUnderstand: (in category 'error handling') -----
doesNotUnderstand: aMessage
inspector ifNotNil: [ (inspector respondsTo: aMessage selector) ifTrue: [ ^inspector perform: aMessage selector withArguments: aMessage arguments ]].
^super doesNotUnderstand: aMessage!
----- Method: ObjectExplorer>>exploreSelection (in category 'menus') -----
exploreSelection
"Open an ObjectExplorer on the current selection"
self object explore!
----- Method: ObjectExplorer>>explorerFor: (in category 'accessing') -----
explorerFor: anObject
| window listMorph |
rootObject := anObject.
window := (SystemWindow labelled: self label) model: self.
window addMorph: (listMorph := SimpleHierarchicalListMorph
on: self
list: #getList
selected: #getCurrentSelection
changeSelected: #noteNewSelection:
menu: #genericMenu:
keystroke: #explorerKey:from:)
frame: (0 at 0 corner: 1 at 0.8).
window addMorph: ((PluggableTextMorph on: self text: #trash accept: #trash:
readSelection: #contentsSelection menu: #codePaneMenu:shifted:)
askBeforeDiscardingEdits: false)
frame: (0 at 0.8 corner: 1 at 1).
listMorph
autoDeselect: false.
^ window!
----- Method: ObjectExplorer>>explorerFor:withLabel: (in category 'user interface') -----
explorerFor: anObject withLabel: label
| window listMorph |
rootObject := anObject.
window := (SystemWindow labelled: label)
model: self.
window
addMorph: (listMorph := SimpleHierarchicalListMorph
on: self
list: #getList
selected: #getCurrentSelection
changeSelected: #noteNewSelection:
menu: #genericMenu:
keystroke: nil)
frame: (0 @ 0 corner: 1 @ 0.8).
window
addMorph: ((PluggableTextMorph
on: self
text: #trash
accept: #trash:
readSelection: #contentsSelection
menu: #codePaneMenu:shifted:)
askBeforeDiscardingEdits: false)
frame: (0 @ 0.8 corner: 1 @ 1).
listMorph autoDeselect: false.
^ window!
----- Method: ObjectExplorer>>explorerKey:from: (in category 'menus') -----
explorerKey: aChar from: view
"Similar to #genericMenu:..."
| insideObject parentObject |
currentSelection ifNotNil: [
insideObject := self object.
parentObject := self parentObject.
inspector ifNil: [inspector := Inspector new].
inspector
inspect: parentObject;
object: insideObject.
aChar == $i ifTrue: [^ self inspectSelection].
aChar == $I ifTrue: [^ self exploreSelection].
aChar == $b ifTrue: [^ inspector browseMethodFull].
aChar == $h ifTrue: [^ inspector classHierarchy].
aChar == $c ifTrue: [^ inspector copyName].
aChar == $p ifTrue: [^ inspector browseFullProtocol].
aChar == $N ifTrue: [^ inspector browseClassRefs].
aChar == $t ifTrue: [^ inspector tearOffTile].
aChar == $v ifTrue: [^ inspector viewerForValue]].
^ self arrowKey: aChar from: view!
----- Method: ObjectExplorer>>genericMenu: (in category 'menus') -----
genericMenu: aMenu
"Borrow a menu from my inspector"
| insideObject menu parentObject |
currentSelection
ifNil: [menu := aMenu.
menu
add: '*nothing selected*'
target: self
selector: #yourself]
ifNotNil: [insideObject := self object.
parentObject := self parentObject.
inspector
ifNil: [inspector := Inspector new].
inspector inspect: parentObject;
object: insideObject.
aMenu defaultTarget: inspector.
inspector fieldListMenu: aMenu.
aMenu items
do: [:i | (#(#inspectSelection #exploreSelection #referencesToSelection #defsOfSelection #objectReferencesToSelection #chasePointers ) includes: i selector)
ifTrue: [i target: self]].
aMenu addLine;
add: 'monitor changes'
target: self
selector: #monitor:
argument: currentSelection].
monitorList isEmptyOrNil
ifFalse: [aMenu addLine;
add: 'stop monitoring all'
target: self
selector: #stopMonitoring].
^ aMenu!
----- Method: ObjectExplorer>>getList (in category 'accessing') -----
getList
^Array with: (ObjectExplorerWrapper with: rootObject name: 'root' model: self parent: nil)
!
----- Method: ObjectExplorer>>initialExtent (in category 'user interface') -----
initialExtent
^300 at 500!
----- Method: ObjectExplorer>>inspectSelection (in category 'menus') -----
inspectSelection
"Open an Inspector on the current selection"
self object inspect!
----- Method: ObjectExplorer>>label (in category 'accessing') -----
label
^ rootObject printStringLimitedTo: 32!
----- Method: ObjectExplorer>>monitor: (in category 'monitoring') -----
monitor: anObjectExplorerWrapper
"Start stepping and watching the given wrapper for changes."
anObjectExplorerWrapper ifNil: [ ^self ].
self world ifNil: [ ^self ].
self monitorList at: anObjectExplorerWrapper put: anObjectExplorerWrapper asString.
self world startStepping: self at: Time millisecondClockValue selector: #step arguments: #() stepTime: 200.!
----- Method: ObjectExplorer>>monitorList (in category 'monitoring') -----
monitorList
^monitorList ifNil: [ monitorList := WeakIdentityKeyDictionary new ].!
----- Method: ObjectExplorer>>object (in category 'accessing') -----
object
^currentSelection ifNotNilDo: [ :cs | cs withoutListWrapper ]!
----- Method: ObjectExplorer>>objectReferencesToSelection (in category 'menus') -----
objectReferencesToSelection
"Open a browser on all references to the selected instance variable, if that's what currently selected. "
self systemNavigation
browseAllObjectReferencesTo: self object
except: (Array with: self parentObject with: currentSelection with: inspector)
ifNone: [:obj | self changed: #flash].
!
----- Method: ObjectExplorer>>openBrowser: (in category 'user interface') -----
openBrowser: aClass
ToolSet browse: aClass selector: nil!
----- Method: ObjectExplorer>>openExplorerFor: (in category 'user interface') -----
openExplorerFor: anObject
"
ObjectExplorer new openExplorerFor: Smalltalk
"
| win |
win := (self explorerFor: anObject) openInWorld.
Cursor wait showWhile:
[win submorphs do:
[:sm|
(sm respondsTo: #expandRoots) ifTrue:
[sm expandRoots]]].
^self
!
----- Method: ObjectExplorer>>openExplorerFor:withLabel: (in category 'user interface') -----
openExplorerFor: anObject withLabel: label
"ObjectExplorer new openExplorerFor: Smalltalk withLabel: 'Smalltalk'"
(self explorerFor: anObject withLabel: label)
openInWorld!
----- Method: ObjectExplorer>>parentObject (in category 'accessing') -----
parentObject
currentSelection ifNil: [ ^nil ].
currentSelection parent ifNil: [ ^rootObject ].
^currentSelection parent withoutListWrapper!
----- Method: ObjectExplorer>>referencesToSelection (in category 'menus') -----
referencesToSelection
"Open a browser on all references to the selected instance variable, if that's what's currently selected."
| aClass sel |
(aClass := self parentObject class) isVariable ifTrue: [^ self changed: #flash].
sel := self selector.
self systemNavigation browseAllAccessesTo: sel from: aClass!
----- Method: ObjectExplorer>>release (in category 'monitoring') -----
release
self world ifNotNil: [ self world stopStepping: self selector: #step ].
super release.!
----- Method: ObjectExplorer>>selectedClass (in category 'menus') -----
selectedClass
"Answer the class of the receiver's current selection"
^self doItReceiver class
!
----- Method: ObjectExplorer>>selector (in category 'accessing') -----
selector
^currentSelection ifNotNilDo: [ :cs | cs selector ]!
----- Method: ObjectExplorer>>shouldGetStepsFrom: (in category 'monitoring') -----
shouldGetStepsFrom: aWorld
^self monitorList notEmpty!
----- Method: ObjectExplorer>>step (in category 'monitoring') -----
step
"If there's anything in my monitor list, see if the strings have changed."
| string changes |
changes := false.
self monitorList keysAndValuesDo: [ :k :v |
k ifNotNil: [
k refresh.
(string := k asString) ~= v ifTrue: [ self monitorList at: k put: string. changes := true ].
]
].
changes ifTrue: [ | sel |
sel := currentSelection.
self changed: #getList.
self noteNewSelection: sel.
].
self monitorList isEmpty ifTrue: [ ActiveWorld stopStepping: self selector: #step ].!
----- Method: ObjectExplorer>>stopMonitoring (in category 'monitoring') -----
stopMonitoring
monitorList := nil.
self world stopStepping: self selector: #step!
----- Method: ObjectExplorer>>trash (in category 'menus') -----
trash
"What goes in the bottom pane"
^ ''!
----- Method: ObjectExplorer>>trash: (in category 'menus') -----
trash: newText
"Don't save it"
^ true!
----- Method: ObjectExplorer>>world (in category 'monitoring') -----
world
^ActiveWorld!
ObjectExplorer subclass: #PointerExplorer
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Explorer'!
!PointerExplorer commentStamp: 'avi 8/21/2004 20:01' prior: 0!
A variant on the ObjectExlorer that works "backwards": like the ObjectExplorer, it shows a tree of objects, but expanding a node won't show the objects which that node references, but rather the objects that reference that node. Its main use is to track down memory leaks: if you want to know why a particular object is still alive, open a PointerExplorer on it and drill down until you find the root object that's referencing it. For example, find all the references to the symbol #zot with:
PointerExplorer new openExplorerFor: #zot
For the "name" of the object, the PointerExplorer shows each object's identityHash, to allow the user to identify when two similar objects are identical and notice cycles.!
----- Method: PointerExplorer>>getList (in category 'accessing') -----
getList
^Array with: (PointerExplorerWrapper with: rootObject name: rootObject identityHash asString model: self)
!
----- Method: Dictionary>>inspectorClass (in category '*Tools-Inspector') -----
inspectorClass
"Answer the class of the inspector to be used on the receiver. Called by inspect;
use basicInspect to get a normal (less useful) type of inspector."
^ DictionaryInspector!
StringMorph subclass: #IndentingListItemMorph
instanceVariableNames: 'indentLevel isExpanded complexContents firstChild container nextSibling icon'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Explorer'!
!IndentingListItemMorph commentStamp: '<historical>' prior: 0!
An IndentingListItemMorph is a StringMorph that draws itself with an optional toggle at its left, as part of the display of the SimpleHierarchicalListMorph.
It will also display lines around the toggle if the #showLinesInHierarchyViews Preference is set.
Instance variables:
indentLevel <SmallInteger> the indent level, from 0 at the root and increasing by 1 at each level of the hierarchy.
isExpanded <Boolean> true if this item is expanded (showing its children)
complexContents <ListItemWrapper> an adapter wrapping my represented item that can answer its children, etc.
firstChild <IndentingListItemMorph|nil> my first child, or nil if none
container <SimpleHierarchicalListMorph> my container
nextSibling <IndentingListItemMorph|nil> the next item in the linked list of siblings, or nil if none.
Contributed by Bob Arning as part of the ObjectExplorer package.
Don't blame him if it's not perfect. We wanted to get it out for people to play with.!
----- Method: IndentingListItemMorph>>acceptDroppingMorph:event: (in category 'drag and drop') -----
acceptDroppingMorph: toDrop event: evt
complexContents acceptDroppingObject: toDrop complexContents.
toDrop delete.
self highlightForDrop: false.!
----- Method: IndentingListItemMorph>>addChildrenForList:addingTo:withExpandedItems: (in category 'private-container protocol') -----
addChildrenForList: hostList addingTo: morphList withExpandedItems: expandedItems
firstChild ifNotNil: [
firstChild withSiblingsDo: [ :aNode | aNode delete].
].
firstChild := nil.
complexContents hasContents ifFalse: [^self].
firstChild := hostList
addMorphsTo: morphList
from: complexContents contents
allowSorting: true
withExpandedItems: expandedItems
atLevel: indentLevel + 1.
!
----- Method: IndentingListItemMorph>>balloonText (in category 'accessing') -----
balloonText
^complexContents balloonText ifNil: [super balloonText]!
----- Method: IndentingListItemMorph>>boundsForBalloon (in category 'halos and balloon help') -----
boundsForBalloon
"some morphs have bounds that are way too big"
container ifNil: [^super boundsForBalloon].
^self boundsInWorld intersect: container boundsInWorld!
----- Method: IndentingListItemMorph>>canExpand (in category 'accessing') -----
canExpand
^complexContents hasContents!
----- Method: IndentingListItemMorph>>children (in category 'accessing') -----
children
| children |
children := OrderedCollection new.
self childrenDo: [:each | children add: each].
^children!
----- Method: IndentingListItemMorph>>childrenDo: (in category 'enumeration') -----
childrenDo: aBlock
firstChild ifNotNil: [
firstChild withSiblingsDo: [ :aNode | aBlock value: aNode].
]!
----- Method: IndentingListItemMorph>>complexContents (in category 'private-container protocol') -----
complexContents
^complexContents!
----- Method: IndentingListItemMorph>>drawLineToggleToTextOn:lineColor:hasToggle: (in category 'drawing') -----
drawLineToggleToTextOn: aCanvas lineColor: lineColor hasToggle: hasToggle
"If I am not the only item in my container, draw the line between:
- my toggle (if any) or my left edge (if no toggle)
- and my text left edge"
| myBounds myCenter hLineY hLineLeft |
self isSoleItem ifTrue: [ ^self ].
myBounds := self toggleBounds.
myCenter := myBounds center.
hLineY := myCenter y.
hLineLeft := myCenter x - 1.
"Draw line from toggle to text"
aCanvas
line: hLineLeft @ hLineY
to: myBounds right + 0 @ hLineY
width: 1
color: lineColor!
----- Method: IndentingListItemMorph>>drawLinesOn:lineColor: (in category 'drawing') -----
drawLinesOn: aCanvas lineColor: lineColor
| hasToggle |
hasToggle := self hasToggle.
"Draw line from toggle to text"
self drawLineToggleToTextOn: aCanvas lineColor: lineColor hasToggle: hasToggle.
"Draw the line from my toggle to the nextSibling's toggle"
self nextSibling ifNotNil: [ self drawLinesToNextSiblingOn: aCanvas lineColor: lineColor hasToggle: hasToggle ].
"If I have children and am expanded, draw a line to my first child"
(self firstChild notNil and: [ self isExpanded ])
ifTrue: [ self drawLinesToFirstChildOn: aCanvas lineColor: lineColor]!
----- Method: IndentingListItemMorph>>drawLinesToFirstChildOn:lineColor: (in category 'drawing') -----
drawLinesToFirstChildOn: aCanvas lineColor: lineColor
"Draw line from me to next sibling"
| vLineX vLineTop vLineBottom childBounds childCenter |
childBounds := self firstChild toggleBounds.
childCenter := childBounds center.
vLineX := childCenter x - 1.
vLineTop := bounds bottom.
self firstChild hasToggle
ifTrue: [vLineBottom := childCenter y - 7]
ifFalse: [vLineBottom := childCenter y].
aCanvas
line: vLineX @ vLineTop
to: vLineX @ vLineBottom
width: 1
color: lineColor!
----- Method: IndentingListItemMorph>>drawLinesToNextSiblingOn:lineColor:hasToggle: (in category 'drawing') -----
drawLinesToNextSiblingOn: aCanvas lineColor: lineColor hasToggle: hasToggle
| myBounds nextSibBounds vLineX myCenter vLineTop vLineBottom |
myBounds := self toggleBounds.
nextSibBounds := self nextSibling toggleBounds.
myCenter := myBounds center.
vLineX := myCenter x - 1.
vLineTop := myCenter y.
vLineBottom := nextSibBounds center y.
"Draw line from me to next sibling"
aCanvas
line: vLineX @ vLineTop
to: vLineX @ vLineBottom
width: 1
color: lineColor!
----- Method: IndentingListItemMorph>>drawOn: (in category 'drawing') -----
drawOn: aCanvas
| tRect sRect columnRect columnScanner columnData columnLeft |
tRect := self toggleRectangle.
sRect := bounds withLeft: tRect right + 4.
self drawToggleOn: aCanvas in: tRect.
icon isNil ifFalse:[
aCanvas
translucentImage: icon
at: sRect left @ (self top + (self height - icon height // 2)).
sRect := sRect left: sRect left + icon width + 2.
].
(container columns isNil or: [(contents asString indexOf: Character tab) = 0]) ifTrue: [
sRect := sRect top: sRect top + sRect bottom - self fontToUse height // 2.
aCanvas drawString: contents asString in: sRect font: self fontToUse color: color.
] ifFalse: [
columnLeft := sRect left.
columnScanner := ReadStream on: contents asString.
container columns do: [ :width |
columnRect := columnLeft @ sRect top extent: width @ sRect height.
columnData := columnScanner upTo: Character tab.
columnData isEmpty ifFalse: [
aCanvas drawString: columnData in: columnRect font: self fontToUse color: color
.
].
columnLeft := columnRect right + 5.
].
]
!
----- Method: IndentingListItemMorph>>drawToggleOn:in: (in category 'drawing') -----
drawToggleOn: aCanvas in: aRectangle
| aForm centeringOffset |
complexContents hasContents ifFalse: [^self].
aForm := isExpanded
ifTrue: [container expandedForm]
ifFalse: [container notExpandedForm].
centeringOffset := ((aRectangle height - aForm extent y) / 2.0) rounded.
^aCanvas
paintImage: aForm
at: (aRectangle topLeft translateBy: 0 @ centeringOffset).
!
----- Method: IndentingListItemMorph>>firstChild (in category 'accessing') -----
firstChild
^firstChild!
----- Method: IndentingListItemMorph>>hasIcon (in category 'accessing') -----
hasIcon
"Answer whether the receiver has an icon."
^ icon notNil!
----- Method: IndentingListItemMorph>>hasToggle (in category 'private') -----
hasToggle
^ complexContents hasContents!
----- Method: IndentingListItemMorph>>highlight (in category 'private-container protocol') -----
highlight
complexContents highlightingColor ifNotNil: [self color: complexContents highlightingColor].
self changed.
!
----- Method: IndentingListItemMorph>>icon (in category 'accessing') -----
icon
"answer the receiver's icon"
^ icon!
----- Method: IndentingListItemMorph>>inToggleArea: (in category 'mouse events') -----
inToggleArea: aPoint
^self toggleRectangle containsPoint: aPoint!
----- Method: IndentingListItemMorph>>indentLevel (in category 'accessing') -----
indentLevel
^indentLevel!
----- Method: IndentingListItemMorph>>initWithContents:prior:forList:indentLevel: (in category 'initialization') -----
initWithContents: anObject prior: priorMorph forList: hostList indentLevel: newLevel
container := hostList.
complexContents := anObject.
self initWithContents: anObject asString font: Preferences standardListFont emphasis: nil.
indentLevel := 0.
isExpanded := false.
nextSibling := firstChild := nil.
priorMorph ifNotNil: [
priorMorph nextSibling: self.
].
indentLevel := newLevel.
icon := anObject icon.
self extent: self minWidth @ self minHeight!
----- Method: IndentingListItemMorph>>initialize (in category 'initialization') -----
initialize
"initialize the state of the receiver"
super initialize.
""
indentLevel := 0.
isExpanded := false!
----- Method: IndentingListItemMorph>>isExpanded (in category 'accessing') -----
isExpanded
^isExpanded!
----- Method: IndentingListItemMorph>>isExpanded: (in category 'accessing') -----
isExpanded: aBoolean
isExpanded := aBoolean!
----- Method: IndentingListItemMorph>>isFirstItem (in category 'accessing') -----
isFirstItem
^owner submorphs first == self!
----- Method: IndentingListItemMorph>>isSoleItem (in category 'accessing') -----
isSoleItem
^self isFirstItem and: [ owner submorphs size = 1 ]!
----- Method: IndentingListItemMorph>>minHeight (in category 'layout') -----
minHeight
| iconHeight |
iconHeight := self hasIcon
ifTrue: [self icon height + 2]
ifFalse: [0].
^ self fontToUse height max: iconHeight !
----- Method: IndentingListItemMorph>>minWidth (in category 'layout') -----
minWidth
| iconWidth |
iconWidth := self hasIcon
ifTrue: [self icon width + 2]
ifFalse: [0].
^ (self fontToUse widthOfString: contents)
+ iconWidth !
----- Method: IndentingListItemMorph>>nextSibling (in category 'accessing') -----
nextSibling
^nextSibling!
----- Method: IndentingListItemMorph>>nextSibling: (in category 'accessing') -----
nextSibling: anotherMorph
nextSibling := anotherMorph!
----- Method: IndentingListItemMorph>>openPath: (in category 'private-container protocol') -----
openPath: anArray
| found |
anArray isEmpty
ifTrue: [^ container setSelectedMorph: nil].
found := nil.
self
withSiblingsDo: [:each | found
ifNil: [(each complexContents asString = anArray first
or: [anArray first isNil])
ifTrue: [found := each]]].
found
ifNil: ["try again with no case sensitivity"
self
withSiblingsDo: [:each | found
ifNil: [(each complexContents asString sameAs: anArray first)
ifTrue: [found := each]]]].
found
ifNotNil: [found isExpanded
ifFalse: [found toggleExpandedState.
container adjustSubmorphPositions].
found changed.
anArray size = 1
ifTrue: [^ container setSelectedMorph: found].
^ found firstChild
ifNil: [container setSelectedMorph: nil]
ifNotNil: [found firstChild openPath: anArray allButFirst]].
^ container setSelectedMorph: nil!
----- Method: IndentingListItemMorph>>recursiveAddTo: (in category 'private-container protocol') -----
recursiveAddTo: aCollection
firstChild ifNotNil: [
firstChild withSiblingsDo: [ :aNode | aNode recursiveAddTo: aCollection].
].
aCollection add: self
!
----- Method: IndentingListItemMorph>>recursiveDelete (in category 'private-container protocol') -----
recursiveDelete
firstChild ifNotNil: [
firstChild withSiblingsDo: [ :aNode | aNode recursiveDelete].
].
self delete
!
----- Method: IndentingListItemMorph>>toggleBounds (in category 'private') -----
toggleBounds
^self toggleRectangle!
----- Method: IndentingListItemMorph>>toggleExpandedState (in category 'private-container protocol') -----
toggleExpandedState
| newChildren toDelete c |
isExpanded := isExpanded not.
toDelete := OrderedCollection new.
firstChild ifNotNil: [
firstChild withSiblingsDo: [ :aNode | aNode recursiveAddTo: toDelete].
].
container noteRemovalOfAll: toDelete.
(isExpanded and: [complexContents hasContents]) ifFalse: [
^self changed
].
(c := complexContents contents) isEmpty ifTrue: [^self changed].
newChildren := container
addSubmorphsAfter: self
fromCollection: c
allowSorting: true.
firstChild := newChildren first.
!
----- Method: IndentingListItemMorph>>toggleRectangle (in category 'private') -----
toggleRectangle
| h |
h := bounds height.
^(bounds left + (12 * indentLevel)) @ bounds top extent: 12 at h!
----- Method: IndentingListItemMorph>>unhighlight (in category 'drawing') -----
unhighlight
complexContents highlightingColor ifNotNil: [self color: Color black].
self changed.
!
----- Method: IndentingListItemMorph>>userString (in category 'accessing') -----
userString
"Add leading tabs to my userString"
^ (String new: indentLevel withAll: Character tab), super userString
!
----- Method: IndentingListItemMorph>>withSiblingsDo: (in category 'private') -----
withSiblingsDo: aBlock
| node |
node := self.
[node isNil] whileFalse: [
aBlock value: node.
node := node nextSibling
].!
----- Method: IndentingListItemMorph>>withoutListWrapper (in category 'converting') -----
withoutListWrapper
^complexContents withoutListWrapper!
----- Method: StringMorph>>balloonTextForClassAndMethodString (in category '*Tools') -----
balloonTextForClassAndMethodString
"Answer suitable balloon text for the receiver thought of as an encoding of the form
<className> [ class ] <selector>"
| aComment |
Preferences balloonHelpInMessageLists
ifFalse: [^ nil].
MessageSet parse: self contents asString toClassAndSelector:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) ifTrue:
[aComment := aClass precodeCommentOrInheritedCommentFor: aSelector]].
^ aComment
!
----- Method: StringMorph>>balloonTextForLexiconString (in category '*Tools') -----
balloonTextForLexiconString
"Answer suitable balloon text for the receiver thought of as an encoding (used in Lexicons) of the form
<selector> <spaces> (<className>>)"
| aComment contentsString aSelector aClassName |
Preferences balloonHelpInMessageLists
ifFalse: [^ nil].
contentsString := self contents asString.
aSelector := contentsString upTo: $ .
aClassName := contentsString copyFrom: ((contentsString indexOf: $() + 1) to: ((contentsString indexOf: $)) - 1).
MessageSet parse: (aClassName, ' dummy') toClassAndSelector:
[:cl :sel | cl ifNotNil:
[aComment := cl precodeCommentOrInheritedCommentFor: aSelector]].
^ aComment
!
----- Method: StringMorph>>balloonTextForMethodString (in category '*Tools') -----
balloonTextForMethodString
"Answer suitable balloon text for the receiver thought of as a method belonging to the currently-selected class of a browser tool."
| aWindow aCodeHolder aClass |
Preferences balloonHelpInMessageLists
ifFalse: [^ nil].
aWindow := self ownerThatIsA: SystemWindow.
(aWindow isNil or: [((aCodeHolder := aWindow model) isKindOf: CodeHolder) not])
ifTrue: [^ nil].
((aClass := aCodeHolder selectedClassOrMetaClass) isNil or:
[(aClass includesSelector: contents asSymbol) not])
ifTrue: [^ nil].
^ aClass precodeCommentOrInheritedCommentFor: contents asSymbol
!
----- Method: PasteUpMorph>>defaultDesktopCommandKeyTriplets (in category '*Tools') -----
defaultDesktopCommandKeyTriplets
"Answer a list of triplets of the form
<key> <receiver> <selector> [+ optional fourth element, a <description> for use in desktop-command-key-help]
that will provide the default desktop command key handlers. If the selector takes an argument, that argument will be the command-key event"
| noviceKeys expertKeys |
noviceKeys := {
{ $o. ActiveWorld. #activateObjectsTool. 'Activate the "Objects Tool"'}.
{ $r. ActiveWorld. #restoreMorphicDisplay. 'Redraw the screen'}.
{ $z. self. #undoOrRedoCommand. 'Undo or redo the last undoable command'}.
{ $F. Project current. #toggleFlapsSuppressed. 'Toggle the display of flaps'}.
{ $N. self. #toggleClassicNavigatorIfAppropriate. 'Show/Hide the classic Navigator, if appropriate'}.
{ $M. self. #toggleShowWorldMainDockingBar. 'Show/Hide the Main Docking Bar'}.
}.
Preferences noviceMode
ifTrue:[^ noviceKeys].
expertKeys := {
{ $b. SystemBrowser. #defaultOpenBrowser. 'Open a new System Browser'}.
{ $k. StringHolder. #open. 'Open a new, blank Workspace'}.
{ $m. self. #putUpNewMorphMenu. 'Put up the "New Morph" menu'}.
{ $t. self. #findATranscript:. 'Make a System Transcript visible'}.
{ $w. SystemWindow. #closeTopWindow. 'Close the topmost window'}.
{ $C. self. #findAChangeSorter:. 'Make a Change Sorter visible'}.
{ $L. self. #findAFileList:. 'Make a File List visible'}.
{ $P. self. #findAPreferencesPanel:. 'Activate the Preferences tool'}.
{ $R. self. #openRecentSubmissionsBrowser:. 'Make a Recent Submissions browser visible'}.
{ $W. self. #findAMessageNamesWindow:. 'Make a MessageNames tool visible'}.
{ $Z. ChangeList. #browseRecentLog. 'Browse recently-logged changes'}.
{ $\. SystemWindow. #sendTopWindowToBack. 'Send the top window to the back'}.
}.
^ noviceKeys, expertKeys
!
----- Method: SystemNavigation>>browserClass (in category '*tools-browser') -----
browserClass
browserClass ifNil: [browserClass := self defaultBrowserClass].
^browserClass!
----- Method: SystemNavigation>>browserClass: (in category '*tools-browser') -----
browserClass: aBrowserClass
browserClass := aBrowserClass!
----- Method: SystemNavigation>>defaultBrowserClass (in category '*tools-browser') -----
defaultBrowserClass
^SystemBrowser default!
----- Method: SystemNavigation>>defaultHierarchyBrowserClass (in category '*tools-browser') -----
defaultHierarchyBrowserClass
^self class environment at: #HierarchyBrowser ifAbsent:[]!
----- Method: SystemNavigation>>hierarchyBrowserClass (in category '*tools-browser') -----
hierarchyBrowserClass
hierarchyBrowserClass ifNil: [hierarchyBrowserClass := self defaultHierarchyBrowserClass].
^hierarchyBrowserClass!
----- Method: SystemNavigation>>hierarchyBrowserClass: (in category '*tools-browser') -----
hierarchyBrowserClass: aBrowserClass
hierarchyBrowserClass := aBrowserClass!
----- Method: TraitBehavior>>browse (in category '*tools-browser') -----
browse
self systemNavigation browseClass: self!
StringHolder subclass: #CodeHolder
instanceVariableNames: 'currentCompiledMethod contentsSymbol'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Base'!
!CodeHolder commentStamp: '<historical>' prior: 0!
An ancestor class for all models which can show code. Eventually, much of the code that currently resides in StringHolder which only applies to code-holding StringHolders might get moved down here.!
CodeHolder subclass: #Browser
instanceVariableNames: 'systemOrganizer classOrganizer metaClassOrganizer systemCategoryListIndex classListIndex messageCategoryListIndex messageListIndex editSelection metaClassIndicated'
classVariableNames: 'ListClassesHierarchically RecentClasses'
poolDictionaries: ''
category: 'Tools-Browser'!
!Browser commentStamp: '<historical>' prior: 0!
I represent a query path into the class descriptions, the software of the system.!
----- Method: Browser class>>fullOnClass: (in category 'instance creation') -----
fullOnClass: aClass
"Open a new full browser set to class."
| brow |
brow := self new.
brow setClass: aClass selector: nil.
^ self
openBrowserView: (brow openEditString: nil)
label: 'System Browser'!
----- Method: Browser class>>fullOnClass:selector: (in category 'instance creation') -----
fullOnClass: aClass selector: aSelector
"Open a new full browser set to class."
| brow classToUse |
classToUse := SystemBrowser default.
brow := classToUse new.
brow setClass: aClass selector: aSelector.
^ classToUse
openBrowserView: (brow openEditString: nil)
label: brow labelString!
----- Method: Browser class>>initialize (in category 'class initialization') -----
initialize
"Browser initialize"
RecentClasses := OrderedCollection new.
self
registerInFlapsRegistry;
registerInAppRegistry !
----- Method: Browser class>>listClassesHierarchically (in category 'preferences') -----
listClassesHierarchically
<preference: 'List classes hierarchically'
category: 'browsing'
description: 'When enabled, the class list in the browser is arranged and indented with regard to the class hierarchy.'
type: #Boolean>
^ListClassesHierarchically ifNil: [false]
!
----- Method: Browser class>>listClassesHierarchically: (in category 'preferences') -----
listClassesHierarchically: aBool
ListClassesHierarchically := aBool!
----- Method: Browser class>>new (in category 'instance creation') -----
new
^super new systemOrganizer: SystemOrganization!
----- Method: Browser class>>newOnCategory: (in category 'instance creation') -----
newOnCategory: aCategory
"Browse the system category of the given name. 7/13/96 sw"
"Browser newOnCategory: 'Interface-Browser'"
| newBrowser catList |
newBrowser := self new.
catList := newBrowser systemCategoryList.
newBrowser systemCategoryListIndex:
(catList indexOf: aCategory asSymbol ifAbsent: [^ self inform: 'No such category']).
^ self
openBrowserView: (newBrowser openSystemCatEditString: nil)
label: 'Classes in category ', aCategory
!
----- Method: Browser class>>newOnClass: (in category 'instance creation') -----
newOnClass: aClass
"Open a new class browser on this class."
^ self newOnClass: aClass label: 'Class Browser: ', aClass name!
----- Method: Browser class>>newOnClass:label: (in category 'instance creation') -----
newOnClass: aClass label: aLabel
"Open a new class browser on this class."
| newBrowser |
newBrowser := self new.
newBrowser setClass: aClass selector: nil.
^ self
openBrowserView: (newBrowser openOnClassWithEditString: nil)
label: aLabel
!
----- Method: Browser class>>newOnClass:selector: (in category 'instance creation') -----
newOnClass: aClass selector: aSymbol
"Open a new class browser on this class."
| newBrowser |
newBrowser := self new.
newBrowser setClass: aClass selector: aSymbol.
^ self
openBrowserView: (newBrowser openOnClassWithEditString: nil)
label: 'Class Browser: ', aClass name
!
----- Method: Browser class>>open (in category 'instance creation') -----
open
^self openBrowser
!
----- Method: Browser class>>openBrowser (in category 'instance creation') -----
openBrowser
"Create and schedule a BrowserView with default browser label. The
view consists of five subviews, starting with the list view of system
categories of SystemOrganization. The initial text view part is empty."
| br |
br := self new.
^ self
openBrowserView: (br openEditString: nil)
label: br defaultBrowserTitle.
!
----- Method: Browser class>>openBrowserView:label: (in category 'instance creation') -----
openBrowserView: aBrowserView label: aString
"Schedule aBrowserView, labelling the view aString."
(aBrowserView isKindOf: ToolBuilderSpec) ifTrue:[
ToolBuilder open: aBrowserView label: aString.
] ifFalse:[
aBrowserView isMorph
ifTrue: [(aBrowserView setLabel: aString) openInWorld]
ifFalse: [aBrowserView label: aString.
aBrowserView minimumSize: 300 @ 200.
aBrowserView subViews do: [:each | each controller].
aBrowserView controller open].
].
^ aBrowserView model
!
----- Method: Browser class>>prototypicalToolWindow (in category 'instance creation') -----
prototypicalToolWindow
"Answer an example of myself seen in a tool window, for the benefit of parts-launching tools"
| aWindow |
aWindow := self new openEditString: nil.
^ ToolBuilder build: aWindow!
----- Method: Browser class>>registerInAppRegistry (in category 'class initialization') -----
registerInAppRegistry
"Register the receiver in the SystemBrowser AppRegistry"
SystemBrowser register: self.!
----- Method: Browser class>>registerInFlapsRegistry (in category 'class initialization') -----
registerInFlapsRegistry
"Register the receiver in the system's flaps registry"
self environment
at: #Flaps
ifPresent: [:cl | cl registerQuad: #(#Browser #prototypicalToolWindow 'Browser' 'A Browser is a tool that allows you to view all the code of all the classes in the system' )
forFlapNamed: 'Tools']!
----- Method: Browser class>>systemOrganizer: (in category 'instance creation') -----
systemOrganizer: anOrganizer
^(super new)
systemOrganizer: anOrganizer;
yourself!
----- Method: Browser class>>unload (in category 'class initialization') -----
unload
"Unload the receiver from global registries"
self environment at: #Flaps ifPresent: [:cl |
cl unregisterQuadsWithReceiver: self].
SystemBrowser unregister: self.!
----- Method: Browser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Browser' brightColor: #lightGreen pastelColor: #paleGreen helpMessage: 'The standard "system browser" tool that allows you to browse through all the code in the system'!
----- Method: Browser>>aboutToStyle: (in category 'code pane') -----
aboutToStyle: aStyler
"This is a notification that aStyler is about to re-style its text.
Set the classOrMetaClass in aStyler, so that identifiers
will be resolved correctly.
Answer true to allow styling to proceed, or false to veto the styling"
| type |
self isModeStyleable ifFalse: [^false].
type := self editSelection.
(#(newMessage editMessage editClass newClass) includes: type) ifFalse:[^false].
aStyler classOrMetaClass: (type = #editClass ifFalse:[self selectedClassOrMetaClass]).
^true!
----- Method: Browser>>addAllMethodsToCurrentChangeSet (in category 'class functions') -----
addAllMethodsToCurrentChangeSet
"Add all the methods in the selected class or metaclass to the current change set. You ought to know what you're doing before you invoke this!!"
| aClass |
(aClass := self selectedClassOrMetaClass) ifNotNil:
[aClass selectors do:
[:sel |
ChangeSet current adoptSelector: sel forClass: aClass].
self changed: #annotation]
!
----- Method: Browser>>addCategory (in category 'message category functions') -----
addCategory
"Present a choice of categories or prompt for a new category name and add it before the current selection, or at the end if no current selection"
| labels reject lines cats menuIndex oldIndex newName |
self okToChange ifFalse: [^ self].
classListIndex = 0 ifTrue: [^ self].
labels := OrderedCollection with: 'new...'.
reject := Set new.
reject
addAll: self selectedClassOrMetaClass organization categories;
add: ClassOrganizer nullCategory;
add: ClassOrganizer default.
lines := OrderedCollection new.
self selectedClassOrMetaClass allSuperclasses do: [:cls |
cls = Object ifFalse: [
cats := cls organization categories reject:
[:cat | reject includes: cat].
cats isEmpty ifFalse: [
lines add: labels size.
labels addAll: cats asSortedCollection.
reject addAll: cats]]].
newName := (labels size = 1 or: [
menuIndex := (UIManager default chooseFrom: labels lines: lines title: 'Add Category').
menuIndex = 0 ifTrue: [^ self].
menuIndex = 1])
ifTrue: [
self request: 'Please type new category name'
initialAnswer: 'category name']
ifFalse: [
labels at: menuIndex].
oldIndex := messageCategoryListIndex.
newName isEmpty
ifTrue: [^ self]
ifFalse: [newName := newName asSymbol].
self classOrMetaClassOrganizer
addCategory: newName
before: (messageCategoryListIndex = 0
ifTrue: [nil]
ifFalse: [self selectedMessageCategoryName]).
self changed: #messageCategoryList.
self messageCategoryListIndex:
(oldIndex = 0
ifTrue: [self classOrMetaClassOrganizer categories size + 1]
ifFalse: [oldIndex]).
self changed: #messageCategoryList.
!
----- Method: Browser>>addClassAndSwitchesTo:at:with: (in category 'toolbuilder') -----
addClassAndSwitchesTo: windowSpec at: frame with: builder
| listSpec panelSpec |
listSpec := self buildClassListWith: builder.
listSpec frame: (frame origin corner: frame right @ (frame bottom - 0.08)).
windowSpec children add: listSpec.
panelSpec := self buildSwitchesWith: builder.
panelSpec frame: (frame left@(frame bottom - 0.08) corner: frame corner).
windowSpec children addLast: panelSpec.!
----- Method: Browser>>addExtraShiftedItemsTo: (in category 'message functions') -----
addExtraShiftedItemsTo: aMenu
"The shifted selector-list menu is being built; some menu items are appropriate only for certain kinds of browsers, and this gives a hook for them to be added as approrpiate. If any is added here, a line should be added first -- browse reimplementors of this message for examples."
!
----- Method: Browser>>addModelItemsToWindowMenu: (in category 'user interface') -----
addModelItemsToWindowMenu: aMenu
"Add model-related items to the window menu"
super addModelItemsToWindowMenu: aMenu.
SystemBrowser addRegistryMenuItemsTo: aMenu inAccountOf: self.!
----- Method: Browser>>addSpecialMenu: (in category 'traits') -----
addSpecialMenu: aMenu
aMenu addList: #(
-
('new class' newClass)
('new trait' newTrait)
-).
self selectedClass notNil ifTrue: [
aMenu addList: #(
('add trait' addTrait)
-) ].
aMenu addList: #(-).
^ aMenu!
----- Method: Browser>>addSystemCategory (in category 'system category functions') -----
addSystemCategory
"Prompt for a new category name and add it before the
current selection, or at the end if no current selection"
| oldIndex newName |
self okToChange ifFalse: [^ self].
oldIndex := systemCategoryListIndex.
newName := self
request: 'Please type new category name'
initialAnswer: 'Category-Name'.
newName isEmpty
ifTrue: [^ self]
ifFalse: [newName := newName asSymbol].
systemOrganizer
addCategory: newName
before: (systemCategoryListIndex = 0
ifTrue: [nil]
ifFalse: [self selectedSystemCategoryName]).
self systemCategoryListIndex:
(oldIndex = 0
ifTrue: [self systemCategoryList size]
ifFalse: [oldIndex]).
self changed: #systemCategoryList.!
----- Method: Browser>>addTrait (in category 'traits') -----
addTrait
| input trait |
input := UIManager default request: 'add trait'.
input isEmptyOrNil ifFalse: [
trait := Smalltalk classNamed: input.
(trait isNil or: [trait isTrait not]) ifTrue: [
^self inform: 'Input invalid. ' , input , ' does not exist or is not a trait'].
self selectedClass addToComposition: trait.
self contentsChanged].
!
----- Method: Browser>>alphabetizeMessageCategories (in category 'message category functions') -----
alphabetizeMessageCategories
classListIndex = 0 ifTrue: [^ false].
self okToChange ifFalse: [^ false].
self classOrMetaClassOrganizer sortCategories.
self clearUserEditFlag.
self editClass.
self classListIndex: classListIndex.
^ true!
----- Method: Browser>>alphabetizeSystemCategories (in category 'system category functions') -----
alphabetizeSystemCategories
self okToChange ifFalse: [^ false].
systemOrganizer sortCategories.
self systemCategoryListIndex: 0.
self changed: #systemCategoryList.
!
----- Method: Browser>>annotation (in category 'annotation') -----
annotation
"Provide a line of content for an annotation pane, representing information about the method associated with the selected class and selector in the receiver."
| aSelector aClass |
(aClass := self selectedClassOrMetaClass) == nil ifTrue: [^ ''].
self editSelection == #editComment ifTrue:
[^ self annotationForSelector: #Comment ofClass: aClass].
self editSelection == #editClass ifTrue:
[^ self annotationForSelector: #Definition ofClass: aClass].
(aSelector := self selectedMessageName) ifNil: [^ ''].
^ self annotationForSelector: aSelector ofClass: aClass!
----- Method: Browser>>annotationForClassDefinitionFor: (in category 'class comment pane') -----
annotationForClassDefinitionFor: aClass
"Provide a line of content for an annotation pane, given that the receiver is pointing at the class definition of the given class."
^self classCommentText!
----- Method: Browser>>browseAllClasses (in category 'system category functions') -----
browseAllClasses
"Create and schedule a new browser on all classes alphabetically."
| newBrowser |
newBrowser := HierarchyBrowser new initAlphabeticListing.
self class openBrowserView: (newBrowser openSystemCatEditString: nil)
label: 'All Classes Alphabetically'!
----- Method: Browser>>buildClassListSingletonWith: (in category 'toolbuilder') -----
buildClassListSingletonWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #classListSingleton;
getIndex: #indexIsOne;
setIndex: #indexIsOne:;
menu: #classListMenu:;
keyPress: #classListKey:from:.
^listSpec
!
----- Method: Browser>>buildClassListWith: (in category 'toolbuilder') -----
buildClassListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #classList;
getIndex: #classListIndex;
setIndex: #classListIndex:;
menu: #classListMenu:;
keyPress: #classListKey:from:.
Preferences browseWithDragNDrop
ifTrue:[listSpec dragItem: #dragFromClassList:].
^listSpec
!
----- Method: Browser>>buildMessageCategoryBrowser (in category 'message category functions') -----
buildMessageCategoryBrowser
"Create and schedule a message category browser for the currently
selected message category."
self buildMessageCategoryBrowserEditString: nil!
----- Method: Browser>>buildMessageCategoryBrowserEditString: (in category 'message category functions') -----
buildMessageCategoryBrowserEditString: aString
"Create and schedule a message category browser for the currently
selected message category. The initial text view contains the characters
in aString."
"wod 6/24/1998: set newBrowser classListIndex so that it works whether the
receiver is a standard or a Hierarchy Browser."
| newBrowser |
messageCategoryListIndex ~= 0
ifTrue:
[newBrowser := Browser new.
newBrowser systemCategoryListIndex: systemCategoryListIndex.
newBrowser classListIndex: (newBrowser classList indexOf: self selectedClassName).
newBrowser metaClassIndicated: metaClassIndicated.
newBrowser messageCategoryListIndex: messageCategoryListIndex.
newBrowser messageListIndex: messageListIndex.
self class openBrowserView: (newBrowser openMessageCatEditString: aString)
label: 'Message Category Browser (' ,
newBrowser selectedClassOrMetaClassName , ')']!
----- Method: Browser>>buildMessageCategoryListWith: (in category 'toolbuilder') -----
buildMessageCategoryListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #messageCategoryList;
getIndex: #messageCategoryListIndex;
setIndex: #messageCategoryListIndex:;
menu: #messageCategoryMenu:;
keyPress: #arrowKey:from:.
Preferences browseWithDragNDrop ifTrue:[
listSpec
dropAccept: #wantsMessageCategoriesDrop:;
dropItem: #dropOnMessageCategories:at:].
^listSpec
!
----- Method: Browser>>buildMessageListCatSingletonWith: (in category 'toolbuilder') -----
buildMessageListCatSingletonWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #messageCatListSingleton;
getIndex: #indexIsOne;
setIndex: #indexIsOne:;
menu: #messageCategoryMenu:.
^listSpec
!
----- Method: Browser>>buildMessageListWith: (in category 'toolbuilder') -----
buildMessageListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #messageList;
getIndex: #messageListIndex;
setIndex: #messageListIndex:;
menu: #messageListMenu:shifted:;
keyPress: #messageListKey:from:.
Preferences browseWithDragNDrop
ifTrue:[listSpec dragItem: #dragFromMessageList:].
^listSpec
!
----- Method: Browser>>buildSwitchesWith: (in category 'toolbuilder') -----
buildSwitchesWith: builder
"Build the instance/comment/class switch"
| panelSpec buttonSpec |
panelSpec := builder pluggablePanelSpec new.
panelSpec children: OrderedCollection new.
buttonSpec := builder pluggableButtonSpec new.
buttonSpec
model: self;
label: 'instance';
state: #instanceMessagesIndicated;
action: #indicateInstanceMessages;
frame: (0 at 0 corner: 0.39 at 1).
panelSpec children addLast: buttonSpec.
buttonSpec := builder pluggableButtonSpec new.
buttonSpec
model: self;
label: '?';
state: #classCommentIndicated;
action: #plusButtonHit;
frame: (0.41 at 0 corner: 0.59 at 1).
panelSpec children addLast: buttonSpec.
buttonSpec := builder pluggableButtonSpec new.
buttonSpec
model: self;
label: 'class';
state: #classMessagesIndicated;
action: #indicateClassMessages;
frame: (0.61 at 0 corner: 1 at 1).
panelSpec children addLast: buttonSpec.
^panelSpec!
----- Method: Browser>>buildSystemCatListSingletonWith: (in category 'toolbuilder') -----
buildSystemCatListSingletonWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #systemCategorySingleton;
getIndex: #indexIsOne;
setIndex: #indexIsOne:;
menu: #systemCategoryMenu:;
keyPress: #systemCatSingletonKey:from:.
^listSpec!
----- Method: Browser>>buildSystemCategoryBrowser (in category 'system category functions') -----
buildSystemCategoryBrowser
"Create and schedule a new system category browser."
self buildSystemCategoryBrowserEditString: nil!
----- Method: Browser>>buildSystemCategoryBrowserEditString: (in category 'system category functions') -----
buildSystemCategoryBrowserEditString: aString
"Create and schedule a new system category browser with initial textual
contents set to aString."
| newBrowser |
systemCategoryListIndex > 0
ifTrue:
[newBrowser := self class new.
newBrowser systemCategoryListIndex: systemCategoryListIndex.
newBrowser setClass: self selectedClassOrMetaClass selector: self selectedMessageName.
self class openBrowserView: (newBrowser openSystemCatEditString: aString)
label: 'Classes in category ', newBrowser selectedSystemCategoryName]!
----- Method: Browser>>buildSystemCategoryListWith: (in category 'toolbuilder') -----
buildSystemCategoryListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #systemCategoryList;
getIndex: #systemCategoryListIndex;
setIndex: #systemCategoryListIndex:;
menu: #systemCategoryMenu:;
keyPress: #systemCatListKey:from:.
Preferences browseWithDragNDrop ifTrue:[
listSpec
dropAccept: #wantsSystemCategoriesDrop:;
dropItem: #dropOnSystemCategories:at:].
^listSpec!
----- Method: Browser>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"Create the ui for the browser"
| windowSpec max |
max := self wantsOptionalButtons ifTrue:[0.42] ifFalse:[0.5].
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 0.25 at max) -> [self buildSystemCategoryListWith: builder].
(0.25 at 0 corner: 0.5@(max-0.08)) -> [self buildClassListWith: builder].
(0.25@(max-0.08) corner: 0.5 at max) -> [self buildSwitchesWith: builder].
(0.5 at 0 corner: 0.75 at max) -> [self buildMessageCategoryListWith: builder].
(0.75 at 0 corner: 1 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.
^builder build: windowSpec!
----- Method: Browser>>canShowMultipleMessageCategories (in category 'message category functions') -----
canShowMultipleMessageCategories
"Answer whether the receiver is capable of showing multiple message categories"
^ true!
----- Method: Browser>>categorizeAllUncategorizedMethods (in category 'message category list') -----
categorizeAllUncategorizedMethods
"Categorize methods by looking in parent classes for a method category."
| organizer organizers |
organizer := self classOrMetaClassOrganizer.
organizers := self selectedClassOrMetaClass withAllSuperclasses collect: [:ea | ea organization].
(organizer listAtCategoryNamed: ClassOrganizer default) do: [:sel | | found |
found := (organizers collect: [ :org | org categoryOfElement: sel])
detect: [:ea | ea ~= ClassOrganizer default and: [ ea ~= nil]]
ifNone: [].
found ifNotNil: [organizer classify: sel under: found]].
self changed: #messageCategoryList!
----- Method: Browser>>categoryOfCurrentMethod (in category 'message category functions') -----
categoryOfCurrentMethod
"Determine the method category associated with the receiver at the current moment, or nil if none"
| aCategory |
^ super categoryOfCurrentMethod ifNil:
[(aCategory := self messageCategoryListSelection) == ClassOrganizer allCategory
ifTrue:
[nil]
ifFalse:
[aCategory]]!
----- Method: Browser>>changeMessageCategories: (in category 'message category functions') -----
changeMessageCategories: aString
"The characters in aString represent an edited version of the the message
categories for the selected class. Update this information in the system
and inform any dependents that the categories have been changed. This
message is invoked because the user had issued the categories command
and edited the message categories. Then the user issued the accept
command."
self classOrMetaClassOrganizer changeFromString: aString.
self clearUserEditFlag.
self editClass.
self classListIndex: classListIndex.
^ true!
----- Method: Browser>>changeSystemCategories: (in category 'system category functions') -----
changeSystemCategories: aString
"Update the class categories by parsing the argument aString."
systemOrganizer changeFromString: aString.
self changed: #systemCategoryList.
^ true!
----- Method: Browser>>classComment:notifying: (in category 'class comment pane') -----
classComment: aText notifying: aPluggableTextMorph
"The user has just entered aText.
It may be all red (a side-effect of replacing the default comment), so remove the color if it is."
| theClass cleanedText redRange |
theClass := self selectedClassOrMetaClass.
theClass
ifNotNil: [cleanedText := aText asText.
redRange := cleanedText rangeOf: TextColor red startingAt: 1.
redRange size = cleanedText size
ifTrue: [cleanedText
removeAttribute: TextColor red
from: 1
to: redRange last ].
theClass comment: aText stamp: Utilities changeStamp].
self changed: #classCommentText.
^ true!
----- Method: Browser>>classCommentIndicated (in category 'metaclass') -----
classCommentIndicated
"Answer true iff we're viewing the class comment."
^ editSelection == #editComment
!
----- Method: Browser>>classCommentText (in category 'class functions') -----
classCommentText
"return the text to display for the comment of the currently selected class"
| theClass |
theClass := self selectedClassOrMetaClass.
theClass ifNil: [ ^''].
^ theClass hasComment
ifTrue: [ theClass comment ]
ifFalse: [ self noCommentNagString ]!
----- Method: Browser>>classDefinitionText (in category 'class functions') -----
classDefinitionText
"return the text to display for the definition of the currently selected class"
| theClass |
^(theClass := self selectedClassOrMetaClass) ifNil: [''] ifNotNil: [theClass definition]!
----- Method: Browser>>classList (in category 'class list') -----
classList
^ self class listClassesHierarchically
ifTrue: [self hierarchicalClassList]
ifFalse: [self defaultClassList].!
----- Method: Browser>>classListIndex (in category 'class list') -----
classListIndex
"Answer the index of the current class selection."
^classListIndex!
----- Method: Browser>>classListIndex: (in category 'class list') -----
classListIndex: anInteger
"Set anInteger to be the index of the current class selection."
| className |
classListIndex := anInteger.
self setClassOrganizer.
messageCategoryListIndex := 0.
messageListIndex := 0.
self classCommentIndicated
ifTrue: []
ifFalse: [self editSelection: (anInteger = 0
ifTrue: [metaClassIndicated | (systemCategoryListIndex == 0)
ifTrue: [#none]
ifFalse: [#newClass]]
ifFalse: [#editClass])].
contents := nil.
self selectedClass isNil
ifFalse: [className := self selectedClass name.
(RecentClasses includes: className)
ifTrue: [RecentClasses remove: className].
RecentClasses addFirst: className.
RecentClasses size > 16
ifTrue: [RecentClasses removeLast]].
self changed: #classSelectionChanged.
self changed: #classCommentText.
self changed: #classListIndex. "update my selection"
self changed: #messageCategoryList.
self changed: #messageList.
self changed: #relabel.
self contentsChanged!
----- Method: Browser>>classListIndexOf: (in category 'class list') -----
classListIndexOf: className
| classList |
classList := self classList.
self class listClassesHierarchically
ifTrue: [classList := classList collect: [:ea | ea withoutLeadingBlanks asSymbol]].
^ classList indexOf: className.!
----- Method: Browser>>classListMenu: (in category 'class functions') -----
classListMenu: aMenu
"For backward compatibility with old browers stored in image segments"
^ self classListMenu: aMenu shifted: false!
----- Method: Browser>>classListMenu:shifted: (in category 'class functions') -----
classListMenu: aMenu shifted: shifted
"Set up the menu to apply to the receiver's class list, honoring the #shifted boolean"
ServiceGui browser: self classMenu: aMenu.
ServiceGui onlyServices ifTrue: [^aMenu].
shifted
ifTrue:
[^ self shiftedClassListMenu: aMenu].
aMenu addList: #(
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' spawnHierarchy)
('browse protocol (p)' browseFullProtocol)
-
('printOut' printOutClass)
('fileOut' fileOutClass)
-
('show hierarchy' hierarchy)
('show definition' editClass)
('show comment' editComment)
-
('inst var refs...' browseInstVarRefs)
('inst var defs...' browseInstVarDefs)
-
('class var refs...' browseClassVarRefs)
('class vars' browseClassVariables)
('class refs (N)' browseClassRefs)
-
('rename class ...' renameClass)
('copy class' copyClass)
('remove class (x)' removeClass)
-
('find method...' findMethod)
('find method wildcard...' findMethodWithWildcard)
-
('more...' offerShiftedClassListMenu)).
^ aMenu
!
----- Method: Browser>>classListSingleton (in category 'class list') -----
classListSingleton
| name |
name := self selectedClassName.
^ name ifNil: [Array new]
ifNotNil: [Array with: name]!
----- Method: Browser>>classMessagesIndicated (in category 'metaclass') -----
classMessagesIndicated
"Answer whether the messages to be presented should come from the
metaclass."
^ self metaClassIndicated and: [self classCommentIndicated not]!
----- Method: Browser>>classNotFound (in category 'system category functions') -----
classNotFound
self changed: #flash.!
----- Method: Browser>>classOrMetaClassOrganizer (in category 'metaclass') -----
classOrMetaClassOrganizer
"Answer the class organizer for the metaclass or class, depending on
which (instance or class) is indicated."
self metaClassIndicated
ifTrue: [^metaClassOrganizer]
ifFalse: [^classOrganizer]!
----- Method: Browser>>codePaneMenu:shifted: (in category 'code pane') -----
codePaneMenu: aMenu shifted: shifted
ServiceGui browser: self codePaneMenu: aMenu.
ServiceGui onlyServices ifTrue: [^ aMenu].
super codePaneMenu: aMenu shifted: shifted.
^ aMenu!
----- Method: Browser>>codeTextMorph (in category 'drag and drop') -----
codeTextMorph
^ self dependents
detect: [:dep | (dep isKindOf: PluggableTextMorph)
and: [dep getTextSelector == #contents]]
ifNone: []!
----- Method: Browser>>compileMessage:notifying: (in category 'code pane') -----
compileMessage: aText notifying: aController
"Compile the code that was accepted by the user, placing the compiled method into an appropriate message category. Return true if the compilation succeeded, else false."
| fallBackCategoryIndex fallBackMethodIndex originalSelectorName result |
self selectedMessageCategoryName ifNil:
[ self selectOriginalCategoryForCurrentMethod
ifFalse:["Select the '--all--' category"
self messageCategoryListIndex: 1]].
self selectedMessageCategoryName asSymbol = ClassOrganizer allCategory
ifTrue:
[ "User tried to save a method while the ALL category was selected"
fallBackCategoryIndex := messageCategoryListIndex.
fallBackMethodIndex := messageListIndex.
editSelection == #newMessage
ifTrue:
[ "Select the 'as yet unclassified' category"
messageCategoryListIndex := 0.
(result := self defineMessageFrom: aText notifying: aController)
ifNil:
["Compilation failure: reselect the original category & method"
messageCategoryListIndex := fallBackCategoryIndex.
messageListIndex := fallBackMethodIndex]
ifNotNil:
[self setSelector: result]]
ifFalse:
[originalSelectorName := self selectedMessageName.
self setOriginalCategoryIndexForCurrentMethod.
messageListIndex := fallBackMethodIndex := self messageList indexOf: originalSelectorName.
(result := self defineMessageFrom: aText notifying: aController)
ifNotNil:
[self setSelector: result]
ifNil:
[ "Compilation failure: reselect the original category & method"
messageCategoryListIndex := fallBackCategoryIndex.
messageListIndex := fallBackMethodIndex.
^ result notNil]].
self changed: #messageCategoryList.
^ result notNil]
ifFalse:
[ "User tried to save a method while the ALL category was NOT selected"
^ (self defineMessageFrom: aText notifying: aController) notNil]!
----- Method: Browser>>contents (in category 'accessing') -----
contents
"Depending on the current selection, different information is retrieved.
Answer a string description of that information. This information is the
method of the currently selected class and message."
| comment theClass latestCompiledMethod |
latestCompiledMethod := currentCompiledMethod.
currentCompiledMethod := nil.
editSelection == #newTrait
ifTrue: [^Trait newTemplateIn: self selectedSystemCategoryName].
editSelection == #none ifTrue: [^ ''].
editSelection == #editSystemCategories
ifTrue: [^ systemOrganizer printString].
editSelection == #newClass
ifTrue: [^ (theClass := self selectedClass)
ifNil:
[Class template: self selectedSystemCategoryName]
ifNotNil:
[Class templateForSubclassOf: theClass category: self selectedSystemCategoryName]].
editSelection == #editClass
ifTrue: [^self classDefinitionText].
editSelection == #editComment
ifTrue:
[(theClass := self selectedClass) ifNil: [^ ''].
comment := theClass comment.
currentCompiledMethod := theClass organization commentRemoteStr.
^ comment size = 0
ifTrue: ['This class has not yet been commented.']
ifFalse: [comment]].
editSelection == #hierarchy
ifTrue: [
self selectedClassOrMetaClass isTrait
ifTrue: [^'']
ifFalse: [^self selectedClassOrMetaClass printHierarchy]].
editSelection == #editMessageCategories
ifTrue: [^ self classOrMetaClassOrganizer printString].
editSelection == #newMessage
ifTrue:
[^ (theClass := self selectedClassOrMetaClass)
ifNil: ['']
ifNotNil: [theClass sourceCodeTemplate]].
editSelection == #editMessage
ifTrue:
[self showingByteCodes ifTrue: [^ self selectedBytecodes].
currentCompiledMethod := latestCompiledMethod.
^ self selectedMessage].
self error: 'Browser internal error: unknown edit selection.'!
----- Method: Browser>>contents:notifying: (in category 'accessing') -----
contents: input notifying: aController
"The retrieved information has changed and its source must now be
updated. The information can be a variety of things, depending on
the list selections (such as templates for class or message definition,
methods) or the user menu commands (such as definition, comment,
hierarchy). Answer the result of updating the source."
| aString aText theClass |
self changed: #annotation.
aString := input asString.
aText := input asText.
editSelection == #newTrait ifTrue: [^self defineTrait: input asString notifying: aController].
editSelection == #editSystemCategories ifTrue: [^ self changeSystemCategories: aString].
editSelection == #editClass | (editSelection == #newClass) ifTrue: [^ self defineClass: aString notifying: aController].
editSelection == #editComment
ifTrue:
[theClass := self selectedClass.
theClass
ifNil:
[self inform: 'You must select a class
before giving it a comment.'.
^ false].
theClass comment: aText stamp: Utilities changeStamp.
self changed: #classCommentText.
^ true].
editSelection == #hierarchy ifTrue: [^ true].
editSelection == #editMessageCategories ifTrue: [^ self changeMessageCategories: aString].
editSelection == #editMessage | (editSelection == #newMessage)
ifTrue:
[^ self okayToAccept
ifFalse:
[false]
ifTrue:
[self compileMessage: aText notifying: aController]].
editSelection == #none
ifTrue:
[self inform: 'This text cannot be accepted
in this part of the browser.'.
^ false].
self error: 'unacceptable accept'!
----- Method: Browser>>contentsSelection (in category 'accessing') -----
contentsSelection
"Return the interval of text in the code pane to select when I set the pane's contents"
messageCategoryListIndex > 0 & (messageListIndex = 0)
ifTrue: [^ 1 to: 500] "entire empty method template"
ifFalse: [^ 1 to: 0] "null selection"!
----- Method: Browser>>copyClass (in category 'class functions') -----
copyClass
| originalName copysName class oldDefinition newDefinition |
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
originalName := self selectedClass name.
copysName := self request: 'Please type new class name' initialAnswer: originalName.
copysName = '' ifTrue: [^ self]. " Cancel returns '' "
copysName := copysName asSymbol.
copysName = originalName ifTrue: [^ self].
(Smalltalk includesKey: copysName)
ifTrue: [^ self error: copysName , ' already exists'].
oldDefinition := self selectedClass definition.
newDefinition := oldDefinition copyReplaceAll: '#' , originalName asString with: '#' , copysName asString.
Cursor wait
showWhile: [class := Compiler evaluate: newDefinition logged: true.
class copyAllCategoriesFrom: (Smalltalk at: originalName).
class class copyAllCategoriesFrom: (Smalltalk at: originalName) class].
self classListIndex: 0.
self changed: #classList!
----- Method: Browser>>couldBrowseAnyClass (in category 'accessing') -----
couldBrowseAnyClass
"Answer whether the receiver is equipped to browse any class. This is in support of the system-brower feature that allows the browser to be redirected at the selected class name. This implementation is clearly ugly, but the feature it enables is handsome enough. 3/1/96 sw"
self dependents
detect: [:d |
((d isKindOf: PluggableListView) or: [d isKindOf: PluggableListMorph]) and:
[d getListSelector == #systemCategoryList]]
ifNone: [^ false].
^ true
!
----- Method: Browser>>createHierarchyTreeOf: (in category 'class list') -----
createHierarchyTreeOf: col
"Create a tree from a flat collection of classes"
| childs transformed val indexes |
transformed := col collect: [:ea |
childs := col select: [:class | class isTrait not and: [class superclass = ea]].
indexes := childs collect: [:child | col indexOf: child].
ea -> indexes].
transformed copy do: [:ea |
ea value: (ea value collect: [:idx |
val := transformed at: idx.
transformed at: idx put: nil.
val])].
^ transformed select: [:ea | ea notNil].
!
----- Method: Browser>>createInstVarAccessors (in category 'class functions') -----
createInstVarAccessors
"Create getters and setters for all inst vars defined at the level of the current class selection, except do NOT clobber or override any selectors already understood by the instances of the selected class"
| aClass newMessage setter |
(aClass := self selectedClassOrMetaClass) ifNotNil:
[aClass instVarNames do:
[:aName |
(aClass canUnderstand: aName asSymbol)
ifFalse:
[newMessage := aName, '
"Answer the value of ', aName, '"
^ ', aName.
aClass compile: newMessage classified: 'accessing' notifying: nil].
(aClass canUnderstand: (setter := aName, ':') asSymbol)
ifFalse:
[newMessage := setter, ' anObject
"Set the value of ', aName, '"
', aName, ' := anObject'.
aClass compile: newMessage classified: 'accessing' notifying: nil]]]!
----- Method: Browser>>defaultBrowserTitle (in category 'initialize-release') -----
defaultBrowserTitle
^ 'System Browser'!
----- Method: Browser>>defaultClassList (in category 'class list') -----
defaultClassList
"Answer an array of the class names of the selected category. Answer an
empty array if no selection exists."
^ systemCategoryListIndex = 0
ifTrue: [Array new]
ifFalse: [systemOrganizer listAtCategoryNumber: systemCategoryListIndex]!
----- Method: Browser>>defineClass:notifying: (in category 'class functions') -----
defineClass: defString notifying: aController
"The receiver's textual content is a request to define a new class. The
source code is defString. If any errors occur in compilation, notify
aController."
| oldClass class newClassName defTokens keywdIx envt |
oldClass := self selectedClassOrMetaClass.
defTokens := defString findTokens: Character separators.
((defTokens first = 'Trait' and: [defTokens second = 'named:'])
or: [defTokens second = 'classTrait'])
ifTrue: [^self defineTrait: defString notifying: aController].
keywdIx := defTokens findFirst: [:x | x beginsWith: 'category'].
envt := Smalltalk.
keywdIx := defTokens findFirst: [:x | '*subclass*' match: x].
newClassName := (defTokens at: keywdIx+1) copyWithoutAll: '#()'.
((oldClass isNil or: [oldClass theNonMetaClass name asString ~= newClassName])
and: [envt includesKey: newClassName asSymbol]) ifTrue:
["Attempting to define new class over existing one when
not looking at the original one in this browser..."
(self confirm: ((newClassName , ' is an existing class in this system.
Redefining it might cause serious problems.
Is this really what you want to do?') asText makeBoldFrom: 1 to: newClassName size))
ifFalse: [^ false]].
"ar 8/29/1999: Use oldClass superclass for defining oldClass
since oldClass superclass knows the definerClass of oldClass."
oldClass ifNotNil:[oldClass := oldClass superclass].
class := oldClass subclassDefinerClass
evaluate: defString
notifying: aController
logged: true.
(class isKindOf: Behavior)
ifTrue: [self changed: #systemCategoryList.
self changed: #classList.
self clearUserEditFlag.
self setClass: class selector: nil.
"self clearUserEditFlag; editClass."
^ true]
ifFalse: [^ false]!
----- Method: Browser>>defineMessage:notifying: (in category 'message functions') -----
defineMessage: aString notifying: aController
"Compile the expressions in aString. Notify aController if a syntax error
occurs. Install the compiled method in the selected class classified under
the currently selected message category name. Answer true if
compilation succeeds, false otherwise."
| selectedMessageName selector category oldMessageList |
selectedMessageName := self selectedMessageName.
oldMessageList := self messageList.
contents := nil.
selector := self selectedClassOrMetaClass
compile: aString
classified: (category := self selectedMessageCategoryName)
notifying: aController.
selector == nil ifTrue: [^ false].
contents := aString copy.
selector ~~ selectedMessageName
ifTrue:
[category = ClassOrganizer nullCategory
ifTrue: [self changed: #classSelectionChanged.
self changed: #classList.
self messageCategoryListIndex: 1].
self setClassOrganizer. "In case organization not cached"
(oldMessageList includes: selector)
ifFalse: [self changed: #messageList].
self messageListIndex: (self messageList indexOf: selector)].
^ true!
----- Method: Browser>>defineMessageFrom:notifying: (in category 'message functions') -----
defineMessageFrom: aString notifying: aController
"Compile the expressions in aString. Notify aController if a syntax error occurs. Install the compiled method in the selected class classified under the currently selected message category name. Answer the selector obtained if compilation succeeds, nil otherwise."
| selectedMessageName selector category oldMessageList |
selectedMessageName := self selectedMessageName.
oldMessageList := self messageList.
contents := nil.
selector := (self selectedClassOrMetaClass parserClass new parseSelector: aString).
(self metaClassIndicated
and: [(self selectedClassOrMetaClass includesSelector: selector) not
and: [Metaclass isScarySelector: selector]])
ifTrue: ["A frist-time definition overlaps the protocol of Metaclasses"
(self confirm: ((selector , ' is used in the existing class system.
Overriding it could cause serious problems.
Is this really what you want to do?') asText makeBoldFrom: 1 to: selector size))
ifFalse: [^nil]].
selector := self selectedClassOrMetaClass
compile: aString
classified: (category := self selectedMessageCategoryName)
notifying: aController.
selector == nil ifTrue: [^ nil].
contents := aString copy.
selector ~~ selectedMessageName
ifTrue:
[category = ClassOrganizer nullCategory
ifTrue: [self changed: #classSelectionChanged.
self changed: #classList.
self messageCategoryListIndex: 1].
self setClassOrganizer. "In case organization not cached"
(oldMessageList includes: selector)
ifFalse: [self changed: #messageList].
self messageListIndex: (self messageList indexOf: selector)].
^ selector!
----- Method: Browser>>defineTrait:notifying: (in category 'traits') -----
defineTrait: defString notifying: aController
| defTokens keywdIx envt oldTrait newTraitName trait |
oldTrait := self selectedClassOrMetaClass.
defTokens := defString findTokens: Character separators.
keywdIx := defTokens findFirst: [:x | x = 'category'].
envt := self selectedEnvironment.
keywdIx := defTokens findFirst: [:x | x = 'named:'].
newTraitName := (defTokens at: keywdIx+1) copyWithoutAll: '#()'.
((oldTrait isNil or: [oldTrait baseTrait name asString ~= newTraitName])
and: [envt includesKey: newTraitName asSymbol]) ifTrue:
["Attempting to define new class/trait over existing one when
not looking at the original one in this browser..."
(self confirm: ((newTraitName , ' is an existing class/trait in this system.
Redefining it might cause serious problems.
Is this really what you want to do?') asText makeBoldFrom: 1 to: newTraitName size))
ifFalse: [^ false]].
trait := Compiler evaluate: defString notifying: aController logged: true.
^(trait isKindOf: TraitBehavior)
ifTrue: [
self changed: #classList.
self classListIndex: (self classListIndexOf: trait baseTrait name).
self clearUserEditFlag; editClass.
true]
ifFalse: [ false ]
!
----- Method: Browser>>doItReceiver (in category 'accessing') -----
doItReceiver
"This class's classPool has been jimmied to be the classPool of the class
being browsed. A doIt in the code pane will let the user see the value of
the class variables."
^ self selectedClass ifNil: [FakeClassPool new]!
----- Method: Browser>>dragFromClassList: (in category 'drag and drop') -----
dragFromClassList: index
"Drag a class from the browser"
| name envt |
(name := self classList at: index) ifNil: [^ nil].
(envt := self selectedEnvironment) ifNil: [^ nil].
^ envt at: name ifAbsent:[nil]!
----- Method: Browser>>dragFromMessageList: (in category 'drag and drop') -----
dragFromMessageList: index
"Drag a method from the browser"
^self selectedClassOrMetaClass compiledMethodAt: (self messageList at: index) ifAbsent:[nil]!
----- Method: Browser>>dropOnMessageCategories:at: (in category 'drag and drop') -----
dropOnMessageCategories: method at: index
| dstClass category |
(method isKindOf: CompiledMethod)
ifFalse:[^self inform: 'Can only drop methods'].
dstClass := self selectedClassOrMetaClass.
(dstClass == method methodClass) ifTrue:[
category := self messageCategoryList at: index.
dstClass organization classify: method selector under: category.
^true].
^self inform: 'Cannot move methods between unrelated classes'
!
----- Method: Browser>>dropOnSystemCategories:at: (in category 'drag and drop') -----
dropOnSystemCategories: aClass at: index
| category |
(aClass isBehavior) ifFalse:[^self inform: 'Can only drop classes'].
category := self systemCategoryList at: index.
self selectedEnvironment organization classify: aClass instanceSide name under: category.
^true!
----- Method: Browser>>editClass (in category 'class functions') -----
editClass
"Retrieve the description of the class definition."
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
self messageCategoryListIndex: 0.
self editSelection: #editClass.
self changed: #contents.
self changed: #classCommentText.
!
----- Method: Browser>>editComment (in category 'class functions') -----
editComment
"Retrieve the description of the class comment."
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
self messageCategoryListIndex: 0.
metaClassIndicated := false.
self editSelection: #editComment.
self changed: #classSelectionChanged.
self changed: #messageCategoryList.
self changed: #messageList.
self decorateButtons.
self contentsChanged
!
----- Method: Browser>>editMessageCategories (in category 'message category functions') -----
editMessageCategories
"Indicate to the receiver and its dependents that the message categories of
the selected class have been changed."
self okToChange ifFalse: [^ self].
classListIndex ~= 0
ifTrue:
[self messageCategoryListIndex: 0.
self editSelection: #editMessageCategories.
self changed: #editMessageCategories.
self contentsChanged]!
----- Method: Browser>>editSelection (in category 'accessing') -----
editSelection
^editSelection!
----- Method: Browser>>editSelection: (in category 'accessing') -----
editSelection: aSelection
"Set the editSelection as requested."
editSelection := aSelection.
self changed: #editSelection.!
----- Method: Browser>>editSystemCategories (in category 'system category functions') -----
editSystemCategories
"Retrieve the description of the class categories of the system organizer."
self okToChange ifFalse: [^ self].
self systemCategoryListIndex: 0.
self editSelection: #editSystemCategories.
self changed: #editSystemCategories.
self contentsChanged!
----- Method: Browser>>explainSpecial: (in category 'class functions') -----
explainSpecial: string
"Answer a string explaining the code pane selection if it is displaying
one of the special edit functions."
| classes whole lits reply |
(editSelection == #editClass or: [editSelection == #newClass])
ifTrue:
["Selector parts in class definition"
string last == $: ifFalse: [^nil].
lits := Array with:
#subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:.
(whole := lits detect: [:each | (each keywords
detect: [:frag | frag = string] ifNone: []) ~~ nil]
ifNone: []) ~~ nil
ifTrue: [reply := '"' , string , ' is one part of the message selector ' , whole , '.']
ifFalse: [^nil].
classes := self systemNavigation allClassesImplementing: whole.
classes := 'these classes ' , classes printString.
^reply , ' It is defined in ' , classes , '."
Smalltalk browseAllImplementorsOf: #' , whole].
editSelection == #hierarchy
ifTrue:
["Instance variables in subclasses"
classes := self selectedClassOrMetaClass allSubclasses.
classes := classes detect: [:each | (each instVarNames
detect: [:name | name = string] ifNone: []) ~~ nil]
ifNone: [^nil].
classes := classes printString.
^'"is an instance variable in class ' , classes , '."
' , classes , ' browseAllAccessesTo: ''' , string , '''.'].
editSelection == #editSystemCategories ifTrue: [^nil].
editSelection == #editMessageCategories ifTrue: [^nil].
^nil!
----- Method: Browser>>fileOutClass (in category 'class functions') -----
fileOutClass
"Print a description of the selected class onto a file whose name is the
category name followed by .st."
Cursor write showWhile:
[classListIndex ~= 0 ifTrue: [self selectedClass fileOut]]!
----- Method: Browser>>fileOutMessageCategories (in category 'message category functions') -----
fileOutMessageCategories
"Print a description of the selected message category of the selected class
onto an external file."
Cursor write showWhile:
[messageCategoryListIndex ~= 0
ifTrue:
[self selectedClassOrMetaClass fileOutCategory: self selectedMessageCategoryName]]!
----- Method: Browser>>fileOutSystemCategory (in category 'system category functions') -----
fileOutSystemCategory
"Print a description of each class in the selected category onto a file
whose name is the category name followed by .st."
systemCategoryListIndex ~= 0
ifTrue: [systemOrganizer fileOutCategory: self selectedSystemCategoryName]!
----- Method: Browser>>findClass (in category 'system category functions') -----
findClass
"Search for a class by name."
| pattern foundClassOrTrait |
self okToChange ifFalse: [^ self classNotFound].
pattern := UIManager default request: 'Class name or fragment?'.
pattern isEmpty ifTrue: [^ self classNotFound].
foundClassOrTrait := Utilities classFromPattern: pattern withCaption: ''.
foundClassOrTrait ifNil: [^ self classNotFound].
self selectCategoryForClass: foundClassOrTrait.
self selectClass: foundClassOrTrait.
!
----- Method: Browser>>findMethod (in category 'class functions') -----
findMethod
"Pop up a list of the current class's methods, and select the one chosen by the user"
| aClass selectors reply cat messageCatIndex messageIndex |
self classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
aClass := self selectedClassOrMetaClass.
selectors := aClass selectors asSortedArray.
selectors isEmpty ifTrue: [self inform: aClass name, ' has no methods.'. ^ self].
reply := UIManager default
chooseFrom: (Array with: 'Enter Wildcard'), selectors
values: (Array with: 'Enter Wildcard'), selectors
lines: #(1).
reply == nil ifTrue: [^ self].
reply = 'EnterWildcard'
ifTrue: [
reply := UIManager default request: 'Enter partial method name:'.
(reply isNil or: [reply isEmpty])
ifTrue: [^self].
(reply includes: $*)
ifFalse: [reply := '*', reply, '*'].
selectors := selectors select: [:each | reply match: each].
selectors isEmpty ifTrue: [self inform: aClass name, ' has no matching methods.'. ^ self].
reply := selectors size = 1
ifTrue: [selectors first]
ifFalse: [
UIManager default
chooseFrom: selectors
values selectors].
reply == nil ifTrue: [^ self]].
cat := aClass whichCategoryIncludesSelector: reply.
messageCatIndex := self messageCategoryList indexOf: cat.
self messageCategoryListIndex: messageCatIndex.
messageIndex := (self messageList indexOf: reply).
self messageListIndex: messageIndex!
----- Method: Browser>>findMethodWithWildcard (in category 'class functions') -----
findMethodWithWildcard
"Pop up a list of the current class's methods, and select the one chosen by the user"
| aClass selectors reply cat messageCatIndex messageIndex |
self classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
aClass := self selectedClassOrMetaClass.
selectors := aClass selectors asSortedArray.
selectors isEmpty ifTrue: [self inform: aClass name, ' has no methods.'. ^ self].
reply := UIManager default request: 'Enter partial method name:'.
(reply isNil or: [reply isEmpty])
ifTrue: [^self].
(reply includes: $*)
ifFalse: [reply := '*', reply, '*'].
selectors := selectors select: [:each | reply match: each].
selectors isEmpty ifTrue: [self inform: aClass name, ' has no matching methods.'. ^ self].
reply := selectors size = 1
ifTrue: [selectors first]
ifFalse: [
UIManager default
chooseFrom: selectors
values: selectors].
reply == nil ifTrue: [^ self].
cat := aClass whichCategoryIncludesSelector: reply.
messageCatIndex := self messageCategoryList indexOf: cat.
self messageCategoryListIndex: messageCatIndex.
messageIndex := (self messageList indexOf: reply).
self messageListIndex: messageIndex!
----- Method: Browser>>flattenHierarchyTree:on:indent: (in category 'class list') -----
flattenHierarchyTree: classHierarchy on: col indent: indent
| class childs plusIndent |
plusIndent := String space.
classHierarchy do: [:assoc |
class := assoc key.
col add: indent , class name.
childs := assoc value.
self
flattenHierarchyTree: childs
on: col
indent: indent , plusIndent].
^ col!
----- Method: Browser>>hierarchicalClassList (in category 'class list') -----
hierarchicalClassList
"classNames are an arbitrary collection of classNames of the system.
Reorder those class names so that they are sorted and indended by inheritance"
| classes |
classes := self defaultClassList collect: [:sym | Smalltalk classNamed: sym].
^ self
flattenHierarchyTree: (self createHierarchyTreeOf: classes)
on: OrderedCollection new
indent: ''.!
----- Method: Browser>>hierarchy (in category 'class functions') -----
hierarchy
"Display the inheritance hierarchy of the receiver's selected class."
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
self messageCategoryListIndex: 0.
self editSelection: #hierarchy.
self changed: #editComment.
self contentsChanged.
^ self!
----- Method: Browser>>highlightMessageList:with: (in category 'message category functions') -----
highlightMessageList: list with: morphList
"Changed by emm to add emphasis in case of breakpoint"
morphList do:[:each |
| classOrNil methodOrNil |
classOrNil := self selectedClassOrMetaClass.
methodOrNil := classOrNil isNil
ifTrue:[nil]
ifFalse:[classOrNil methodDictionary at: each contents ifAbsent:[]].
(methodOrNil notNil and:[methodOrNil hasBreakpoint])
ifTrue:[each contents: ((each contents ,' [break]') asText allBold)]]!
----- Method: Browser>>indexIsOne (in category 'system category list') -----
indexIsOne
"When used as a singleton list, index is always one"
^ 1!
----- Method: Browser>>indexIsOne: (in category 'system category list') -----
indexIsOne: value
"When used as a singleton list, can't change it"
^ self!
----- Method: Browser>>indicateClassMessages (in category 'metaclass') -----
indicateClassMessages
"Indicate that the message selection should come from the metaclass
messages."
self metaClassIndicated: true!
----- Method: Browser>>indicateInstanceMessages (in category 'metaclass') -----
indicateInstanceMessages
"Indicate that the message selection should come from the class (instance)
messages."
self metaClassIndicated: false!
----- Method: Browser>>inspectInstances (in category 'message functions') -----
inspectInstances
"Inspect all instances of the selected class. 1/26/96 sw"
| myClass |
((myClass := self selectedClassOrMetaClass) isNil or: [myClass isTrait])
ifFalse: [myClass theNonMetaClass inspectAllInstances]
!
----- Method: Browser>>inspectSubInstances (in category 'message functions') -----
inspectSubInstances
"Inspect all instances of the selected class and all its subclasses 1/26/96 sw"
| aClass |
((aClass := self selectedClassOrMetaClass) isNil or: [aClass isTrait])
ifFalse: [
aClass := aClass theNonMetaClass.
aClass inspectSubInstances].
!
----- Method: Browser>>instanceMessagesIndicated (in category 'metaclass') -----
instanceMessagesIndicated
"Answer whether the messages to be presented should come from the
class."
^metaClassIndicated not and: [self classCommentIndicated not]!
----- Method: Browser>>labelString (in category 'initialize-release') -----
labelString
^self selectedClass ifNil: [ self defaultBrowserTitle ]
ifNotNil: [ self defaultBrowserTitle, ': ', self selectedClass printString ].
!
----- Method: Browser>>makeNewSubclass (in category 'class functions') -----
makeNewSubclass
self selectedClassOrMetaClass ifNil: [^ self].
self okToChange ifFalse: [^ self].
self editSelection: #newClass.
self contentsChanged!
----- Method: Browser>>messageCatListSingleton (in category 'message category list') -----
messageCatListSingleton
| name |
name := self selectedMessageCategoryName.
^ name ifNil: [Array new]
ifNotNil: [Array with: name]!
----- Method: Browser>>messageCategoryList (in category 'message category list') -----
messageCategoryList
"Answer the selected category of messages."
classListIndex = 0
ifTrue: [^ Array new]
ifFalse: [^ (Array with: ClassOrganizer allCategory), self classOrMetaClassOrganizer categories]!
----- Method: Browser>>messageCategoryListIndex (in category 'message category list') -----
messageCategoryListIndex
"Answer the index of the selected message category."
^messageCategoryListIndex!
----- Method: Browser>>messageCategoryListIndex: (in category 'message category list') -----
messageCategoryListIndex: anInteger
"Set the selected message category to be the one indexed by anInteger."
messageCategoryListIndex := anInteger.
messageListIndex := 0.
self changed: #messageCategorySelectionChanged.
self changed: #messageCategoryListIndex. "update my selection"
self changed: #messageList.
self editSelection: (anInteger > 0
ifTrue: [#newMessage]
ifFalse: [self classListIndex > 0
ifTrue: [#editClass]
ifFalse: [#newClass]]).
contents := nil.
self contentsChanged.!
----- Method: Browser>>messageCategoryListSelection (in category 'message category list') -----
messageCategoryListSelection
"Return the selected category name or nil."
^ ((self messageCategoryList size = 0
or: [self messageCategoryListIndex = 0])
or: [self messageCategoryList size < self messageCategoryListIndex])
ifTrue: [nil]
ifFalse: [self messageCategoryList at: (self messageCategoryListIndex max: 1)]!
----- Method: Browser>>messageCategoryMenu: (in category 'message category functions') -----
messageCategoryMenu: aMenu
ServiceGui browser: self messageCategoryMenu: aMenu.
ServiceGui onlyServices ifTrue: [^aMenu].
^ aMenu labels:
'browse
printOut
fileOut
reorganize
alphabetize
remove empty categories
categorize all uncategorized
new category...
rename...
remove'
lines: #(3 8)
selections:
#(buildMessageCategoryBrowser printOutMessageCategories fileOutMessageCategories
editMessageCategories alphabetizeMessageCategories removeEmptyCategories
categorizeAllUncategorizedMethods addCategory renameCategory removeMessageCategory)
!
----- Method: Browser>>messageList (in category 'message list') -----
messageList
"Answer an Array of the message selectors of the currently selected message category, provided that the messageCategoryListIndex is in proper range. Otherwise, answer an empty Array If messageCategoryListIndex is found to be larger than the number of categories (it happens!!), it is reset to zero."
| sel |
(sel := self messageCategoryListSelection) ifNil:
[
^ self classOrMetaClassOrganizer
ifNil: [Array new]
ifNotNil: [self classOrMetaClassOrganizer allMethodSelectors]
"^ Array new"
].
^ sel = ClassOrganizer allCategory
ifTrue:
[self classOrMetaClassOrganizer
ifNil: [Array new]
ifNotNil: [self classOrMetaClassOrganizer allMethodSelectors]]
ifFalse:
[(self classOrMetaClassOrganizer listAtCategoryNumber: messageCategoryListIndex - 1)
ifNil: [messageCategoryListIndex := 0. Array new]]!
----- Method: Browser>>messageListIndex (in category 'message list') -----
messageListIndex
"Answer the index of the selected message selector into the currently
selected message category."
^messageListIndex!
----- Method: Browser>>messageListIndex: (in category 'message list') -----
messageListIndex: anInteger
"Set the selected message selector to be the one indexed by anInteger."
messageListIndex := anInteger.
self editSelection: (anInteger > 0
ifTrue: [#editMessage]
ifFalse: [self messageCategoryListIndex > 0
ifTrue: [#newMessage]
ifFalse: [self classListIndex > 0
ifTrue: [#editClass]
ifFalse: [#newClass]]]).
contents := nil.
self changed: #messageListIndex. "update my selection"
self contentsChanged.
self decorateButtons.!
----- Method: Browser>>messageListMenu:shifted: (in category 'message functions') -----
messageListMenu: aMenu shifted: shifted
"Answer the message-list menu"
ServiceGui browser: self messageListMenu: aMenu.
ServiceGui onlyServices ifTrue: [^ aMenu].
shifted
ifTrue: [^ self shiftedMessageListMenu: aMenu].
aMenu addList: #(
('what to show...' offerWhatToShowMenu)
('toggle break on entry' toggleBreakOnEntry)
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' classHierarchy)
('browse method (O)' openSingleMessageBrowser)
('browse protocol (p)' browseFullProtocol)
-
('fileOut' fileOutMessage)
('printOut' printOutMessage)
-
('senders of... (n)' browseSendersOfMessages)
('implementors of... (m)' browseMessages)
('inheritance (i)' methodHierarchy)
('tile scriptor' openSyntaxView)
('versions (v)' browseVersions)
-
('inst var refs...' browseInstVarRefs)
('inst var defs...' browseInstVarDefs)
('class var refs...' browseClassVarRefs)
('class variables' browseClassVariables)
('class refs (N)' browseClassRefs)
-
('remove method (x)' removeMessage)
-
('more...' shiftedYellowButtonActivity)).
^ aMenu!
----- Method: Browser>>messageListSingleton (in category 'message list') -----
messageListSingleton
| name |
name := self selectedMessageName.
^ name ifNil: [Array new]
ifNotNil: [Array with: name]!
----- Method: Browser>>metaClassIndicated (in category 'metaclass') -----
metaClassIndicated
"Answer the boolean flag that indicates which of the method dictionaries,
class or metaclass."
^ metaClassIndicated!
----- Method: Browser>>metaClassIndicated: (in category 'metaclass') -----
metaClassIndicated: trueOrFalse
"Indicate whether browsing instance or class messages."
metaClassIndicated := trueOrFalse.
self setClassOrganizer.
systemCategoryListIndex > 0 ifTrue:
[self editSelection: (classListIndex = 0
ifTrue: [metaClassIndicated
ifTrue: [#none]
ifFalse: [#newClass]]
ifFalse: [#editClass])].
messageCategoryListIndex := 0.
messageListIndex := 0.
contents := nil.
self changed: #classSelectionChanged.
self changed: #messageCategoryList.
self changed: #messageList.
self changed: #contents.
self changed: #annotation.
self decorateButtons
!
----- Method: Browser>>methodCategoryChanged (in category 'initialize-release') -----
methodCategoryChanged
self changed: #messageCategoryList.
self changed: #messageList.
self changed: #annotation.
self messageListIndex: 0!
----- Method: Browser>>newClass (in category 'traits') -----
newClass
(self selectedClassOrMetaClass notNil and:
[self selectedClassOrMetaClass isTrait]) ifTrue: [self classListIndex: 0].
self editClass.
editSelection := #newClass.
self contentsChanged!
----- Method: Browser>>newTrait (in category 'traits') -----
newTrait
self classListIndex: 0.
self editClass.
editSelection := #newTrait.
self contentsChanged!
----- Method: Browser>>noCommentNagString (in category 'class comment pane') -----
noCommentNagString
^ Text string: 'THIS CLASS HAS NO COMMENT!!' translated attribute: TextColor red.
!
----- Method: Browser>>noteSelectionIndex:for: (in category 'accessing') -----
noteSelectionIndex: anInteger for: aSymbol
aSymbol == #systemCategoryList
ifTrue:
[systemCategoryListIndex := anInteger].
aSymbol == #classList
ifTrue:
[classListIndex := anInteger].
aSymbol == #messageCategoryList
ifTrue:
[messageCategoryListIndex := anInteger].
aSymbol == #messageList
ifTrue:
[messageListIndex := anInteger].!
----- Method: Browser>>openEditString: (in category 'initialize-release') -----
openEditString: aString
"Create a pluggable version of all the views for a Browser, including views and controllers."
"Example:
Browser fullOnClass: Browser.
"
| builder max |
builder := ToolBuilder default.
max := self wantsOptionalButtons ifTrue:[0.42] ifFalse:[0.5].
^self buildWindowWith: builder specs: {
(0 at 0 corner: 0.25 at max) -> [self buildSystemCategoryListWith: builder].
(0.25 at 0 corner: 0.5@(max-0.08)) -> [self buildClassListWith: builder].
(0.25@(max-0.08) corner: 0.5 at max) -> [self buildSwitchesWith: builder].
(0.5 at 0 corner: 0.75 at max) -> [self buildMessageCategoryListWith: builder].
(0.75 at 0 corner: 1 at max) -> [self buildMessageListWith: builder].
"(0 at max corner: 1 at 0.5) -> [self buildOptionalButtonsWith: builder]."
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}!
----- Method: Browser>>openMessageCatEditString: (in category 'initialize-release') -----
openMessageCatEditString: aString
"Create a pluggable version of the views for a Browser that just shows one message category."
"Example:
Preferences browseThemes.
"
| builder max |
builder := ToolBuilder default.
max := self wantsOptionalButtons ifTrue:[0.32] ifFalse:[0.4].
^self buildWindowWith: builder specs: {
(0 at 0 corner: 1.0 at 0.08) -> [self buildMessageListCatSingletonWith: builder].
(0.0 at 0.08 corner: 1.0 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.!
----- Method: Browser>>openOnClassWithEditString: (in category 'initialize-release') -----
openOnClassWithEditString: aString
"Create a pluggable version of all the views for a Browser, including views and controllers."
"Example:
Browser newOnClass: Browser.
"
| builder max |
builder := ToolBuilder default.
max := self wantsOptionalButtons ifTrue:[0.32] ifFalse:[0.4].
^self buildWindowWith: builder specs: {
(0.0 at 0.0 corner: 0.5 at 0.08) -> [self buildClassListSingletonWith: builder].
(0.5 at 0.0 corner: 1.0 at 0.08) -> [self buildSwitchesWith: builder].
(0.0 at 0.08 corner: 0.5 at max) -> [self buildMessageCategoryListWith: builder].
(0.5 at 0.08 corner: 1 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}
!
----- Method: Browser>>openSystemCatEditString: (in category 'initialize-release') -----
openSystemCatEditString: aString
"Create a pluggable version of all the views for a Browser, including views and controllers. The top list view is of the currently selected system class category--a single item list."
"Example:
Browser new browseAllClasses.
"
| builder max |
builder := ToolBuilder default.
max := self wantsOptionalButtons ifTrue:[0.32] ifFalse:[0.4].
^self buildWindowWith: builder specs: {
(0 at 0 corner: 1.0 at 0.08) -> [self buildSystemCatListSingletonWith: builder].
(0.0 at 0.08 corner: 0.333@(max-0.1)) -> [self buildClassListWith: builder].
(0.0@(max-0.1) corner: 0.333 at max) -> [self buildSwitchesWith: builder].
(0.333 at 0.08 corner: 0.666 at max) -> [self buildMessageCategoryListWith: builder].
(0.666 at 0.08 corner: 1 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}!
----- Method: Browser>>optionalAnnotationHeight (in category 'initialize-release') -----
optionalAnnotationHeight
^ 10!
----- Method: Browser>>optionalButtonHeight (in category 'initialize-release') -----
optionalButtonHeight
^ 10!
----- Method: Browser>>plusButtonHit (in category 'class functions') -----
plusButtonHit
"Cycle among definition, comment, and hierachy"
editSelection == #editComment
ifTrue: [self hierarchy. ^ self].
editSelection == #hierarchy
ifTrue: [self editSelection: #editClass.
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
self changed: #editComment.
self contentsChanged.
^ self].
self editComment!
----- Method: Browser>>potentialClassNames (in category 'system category functions') -----
potentialClassNames
"Answer the names of all the classes that could be viewed in this browser. This hook is provided so that HierarchyBrowsers can indicate their restricted subset. For generic Browsers, the entire list of classes known to Smalltalk is provided, though of course that really only is accurate in the case of full system browsers."
^ Smalltalk classNames!
----- Method: Browser>>printOutClass (in category 'class functions') -----
printOutClass
"Print a description of the selected class onto a file whose name is the
category name followed by .html."
Cursor write showWhile:
[classListIndex ~= 0 ifTrue: [self selectedClass fileOutAsHtml: true]]!
----- Method: Browser>>printOutMessageCategories (in category 'message category functions') -----
printOutMessageCategories
"Print a description of the selected message category of the selected class
onto an external file in Html format."
Cursor write showWhile:
[messageCategoryListIndex ~= 0
ifTrue:
[self selectedClassOrMetaClass fileOutCategory: self selectedMessageCategoryName
asHtml: true]]!
----- Method: Browser>>printOutSystemCategory (in category 'system category functions') -----
printOutSystemCategory
"Print a description of each class in the selected category as Html."
Cursor write showWhile:
[systemCategoryListIndex ~= 0
ifTrue: [systemOrganizer fileOutCategory: self selectedSystemCategoryName
asHtml: true ]]
!
----- Method: Browser>>rawMessageCategoryList (in category 'message category list') -----
rawMessageCategoryList
^ classListIndex = 0
ifTrue: [Array new]
ifFalse: [self classOrMetaClassOrganizer categories]!
----- Method: Browser>>recategorizeMethodSelector: (in category 'message category list') -----
recategorizeMethodSelector: sel
"Categorize method named sel by looking in parent classes for a
method category.
Answer true if recategorized."
| thisCat |
self selectedClassOrMetaClass allSuperclasses
do: [:ea |
thisCat := ea organization categoryOfElement: sel.
(thisCat ~= ClassOrganizer default
and: [thisCat notNil])
ifTrue: [self classOrMetaClassOrganizer classify: sel under: thisCat.
self changed: #messageCategoryList.
^ true]].
^ false!
----- Method: Browser>>recent (in category 'class list') -----
recent
"Let the user select from a list of recently visited classes. 11/96 stp.
12/96 di: use class name, not classes themselves.
: dont fall into debugger in empty case"
| className class recentList |
recentList := RecentClasses select: [:n | Smalltalk includesKey: n].
recentList size == 0 ifTrue: [^ Beeper beep].
className := UIManager default chooseFrom: recentList values: recentList.
className == nil ifTrue: [^ self].
class := Smalltalk at: className.
self selectCategoryForClass: class.
self classListIndex: (self classListIndexOf: class name)!
----- Method: Browser>>reformulateList (in category 'message list') -----
reformulateList
"If the receiver has a way of reformulating its message list, here is a chance for it to do so"
super reformulateList.
self messageListIndex: 0!
----- Method: Browser>>removeClass (in category 'class functions') -----
removeClass
"If the user confirms the wish to delete the class, do so"
super removeClass ifTrue:
[self classListIndex: 0]!
----- Method: Browser>>removeEmptyCategories (in category 'message category functions') -----
removeEmptyCategories
self okToChange ifFalse: [^ self].
self selectedClassOrMetaClass organization removeEmptyCategories.
self changed: #messageCategoryList
!
----- Method: Browser>>removeMessage (in category 'message functions') -----
removeMessage
"If a message is selected, create a Confirmer so the user can verify that
the currently selected message should be removed from the system. If
so,
remove it. If the Preference 'confirmMethodRemoves' is set to false, the
confirmer is bypassed."
| messageName confirmation |
messageListIndex = 0
ifTrue: [^ self].
self okToChange
ifFalse: [^ self].
messageName := self selectedMessageName.
confirmation := self systemNavigation confirmRemovalOf: messageName on: self selectedClassOrMetaClass.
confirmation == 3
ifTrue: [^ self].
(self selectedClassOrMetaClass includesLocalSelector: messageName)
ifTrue: [self selectedClassOrMetaClass removeSelector: messageName]
ifFalse: [self removeNonLocalSelector: messageName].
self messageListIndex: 0.
self changed: #messageList.
self setClassOrganizer.
"In case organization not cached"
confirmation == 2
ifTrue: [self systemNavigation browseAllCallsOn: messageName]!
----- Method: Browser>>removeMessageCategory (in category 'message category functions') -----
removeMessageCategory
"If a message category is selected, create a Confirmer so the user can
verify that the currently selected message category should be removed
from the system. If so, remove it."
| messageCategoryName |
messageCategoryListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
messageCategoryName := self selectedMessageCategoryName.
(self messageList size = 0
or: [self confirm: 'Are you sure you want to
remove this method category
and all its methods?'])
ifTrue:
[self selectedClassOrMetaClass removeCategory: messageCategoryName.
self messageCategoryListIndex: 0.
self changed: #classSelectionChanged].
self changed: #messageCategoryList.
!
----- Method: Browser>>removeMessageFromBrowser (in category 'message functions') -----
removeMessageFromBrowser
"Our list speaks the truth and can't have arbitrary things removed"
^ self changed: #flash!
----- Method: Browser>>removeNonLocalSelector: (in category 'traits') -----
removeNonLocalSelector: aSymbol
| traits isAlias |
traits := self selectedClassOrMetaClass traitsProvidingSelector: aSymbol.
isAlias := self selectedClassOrMetaClass isLocalAliasSelector: aSymbol.
isAlias
ifTrue: [
self assert: traits size = 1.
self selectedClassOrMetaClass removeAlias: aSymbol of: traits first]
ifFalse: [
traits do: [:each |
self selectedClassOrMetaClass addExclusionOf: aSymbol to: each ]]
!
----- Method: Browser>>removeSystemCategory (in category 'system category functions') -----
removeSystemCategory
"If a class category is selected, create a Confirmer so the user can
verify that the currently selected class category and all of its classes
should be removed from the system. If so, remove it."
systemCategoryListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
(self classList size = 0
or: [self confirm: 'Are you sure you want to
remove this system category
and all its classes?'])
ifTrue:
[systemOrganizer removeSystemCategory: self selectedSystemCategoryName.
self systemCategoryListIndex: 0.
self changed: #systemCategoryList]!
----- Method: Browser>>renameCategory (in category 'message category functions') -----
renameCategory
"Prompt for a new category name and add it before the
current selection, or at the end if no current selection"
| oldIndex oldName newName |
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
(oldIndex := messageCategoryListIndex) = 0 ifTrue: [^ self].
oldName := self selectedMessageCategoryName.
newName := self
request: 'Please type new category name'
initialAnswer: oldName.
newName isEmpty
ifTrue: [^ self]
ifFalse: [newName := newName asSymbol].
newName = oldName ifTrue: [^ self].
self classOrMetaClassOrganizer
renameCategory: oldName
toBe: newName.
self classListIndex: classListIndex.
self messageCategoryListIndex: oldIndex.
self changed: #messageCategoryList.
!
----- Method: Browser>>renameClass (in category 'class functions') -----
renameClass
| oldName newName obs |
classListIndex = 0
ifTrue: [^ self].
self okToChange
ifFalse: [^ self].
oldName := self selectedClass name.
newName := self request: 'Please type new class name' initialAnswer: oldName.
newName = ''
ifTrue: [^ self].
"Cancel returns ''"
newName := newName asSymbol.
newName = oldName
ifTrue: [^ self].
(Smalltalk includesKey: newName)
ifTrue: [^ self error: newName , ' already exists'].
self selectedClass rename: newName.
self changed: #classList.
self classListIndex: (self classListIndexOf: newName).
obs := self systemNavigation
allCallsOn: (Smalltalk associationAt: newName).
obs isEmpty
ifFalse: [self systemNavigation
browseMessageList: obs
name: 'Obsolete References to ' , oldName
autoSelect: oldName]!
----- Method: Browser>>renameSystemCategory (in category 'system category functions') -----
renameSystemCategory
"Prompt for a new category name and add it before the
current selection, or at the end if no current selection"
| oldIndex oldName newName |
(oldIndex := systemCategoryListIndex) = 0
ifTrue: [^ self]. "no selection"
self okToChange ifFalse: [^ self].
oldName := self selectedSystemCategoryName.
newName := self
request: 'Please type new category name'
initialAnswer: oldName.
newName isEmpty
ifTrue: [^ self]
ifFalse: [newName := newName asSymbol].
oldName = newName ifTrue: [^ self].
systemOrganizer
renameCategory: oldName
toBe: newName.
self systemCategoryListIndex: oldIndex.
self changed: #systemCategoryList.!
----- Method: Browser>>request:initialAnswer: (in category 'accessing') -----
request: prompt initialAnswer: initialAnswer
^ UIManager default
request: prompt
initialAnswer: initialAnswer
!
----- Method: Browser>>selectCategoryForClass: (in category 'system category list') -----
selectCategoryForClass: theClass
self systemCategoryListIndex: (self systemCategoryList indexOf: theClass category)
!
----- Method: Browser>>selectClass: (in category 'class list') -----
selectClass: classNotMeta
self classListIndex: (self classListIndexOf: classNotMeta name)!
----- Method: Browser>>selectMessageCategoryNamed: (in category 'message category list') -----
selectMessageCategoryNamed: aSymbol
"Given aSymbol, select the category with that name. Do nothing if
aSymbol doesn't exist."
self messageCategoryListIndex: (self messageCategoryList indexOf: aSymbol ifAbsent: [ 1])!
----- Method: Browser>>selectOriginalCategoryForCurrentMethod (in category 'message category list') -----
selectOriginalCategoryForCurrentMethod
"private - Select the message category for the current method.
Note: This should only be called when somebody tries to save
a method that they are modifying while ALL is selected.
Returns: true on success, false on failure."
| aSymbol selectorName |
aSymbol := self categoryOfCurrentMethod.
selectorName := self selectedMessageName.
(aSymbol notNil and: [aSymbol ~= ClassOrganizer allCategory])
ifTrue:
[messageCategoryListIndex := (self messageCategoryList indexOf: aSymbol).
messageListIndex := (self messageList indexOf: selectorName).
self changed: #messageCategorySelectionChanged.
self changed: #messageCategoryListIndex. "update my selection"
self changed: #messageList.
self changed: #messageListIndex.
^ true].
^ false!
----- Method: Browser>>selectedClass (in category 'class list') -----
selectedClass
"Answer the class that is currently selected. Answer nil if no selection
exists."
| name envt |
(name := self selectedClassName) ifNil: [^ nil].
(envt := self selectedEnvironment) ifNil: [^ nil].
^ envt at: name!
----- Method: Browser>>selectedClassName (in category 'class list') -----
selectedClassName
| className |
className := self classList
at: classListIndex
ifAbsent: [^ nil].
self class listClassesHierarchically ifTrue: [
className := className withoutLeadingBlanks asSymbol].
^ className.!
----- Method: Browser>>selectedClassOrMetaClass (in category 'metaclass') -----
selectedClassOrMetaClass
"Answer the selected class/trait or metaclass/classTrait."
| cls |
^self metaClassIndicated
ifTrue: [(cls := self selectedClass) ifNil: [nil] ifNotNil: [cls classSide]]
ifFalse: [self selectedClass]!
----- Method: Browser>>selectedClassOrMetaClassName (in category 'metaclass') -----
selectedClassOrMetaClassName
"Answer the selected class name or metaclass name."
^self selectedClassOrMetaClass name!
----- Method: Browser>>selectedEnvironment (in category 'system category list') -----
selectedEnvironment
"Answer the name of the selected system category or nil."
systemCategoryListIndex = 0 ifTrue: [^nil].
^ Smalltalk!
----- Method: Browser>>selectedMessage (in category 'message list') -----
selectedMessage
"Answer a copy of the source code for the selected message."
| class selector method |
contents == nil ifFalse: [^ contents copy].
self showingDecompile ifTrue:
[^ self decompiledSourceIntoContents].
class := self selectedClassOrMetaClass.
selector := self selectedMessageName.
method := class compiledMethodAt: selector ifAbsent: [^ '']. "method deleted while in another project"
currentCompiledMethod := method.
^ contents := (self showingDocumentation
ifFalse: [ self sourceStringPrettifiedAndDiffed ]
ifTrue: [ self commentContents ])
copy asText makeSelectorBoldIn: class!
----- Method: Browser>>selectedMessageCategoryName (in category 'message category list') -----
selectedMessageCategoryName
"Answer the name of the selected message category, if any. Answer nil
otherwise."
messageCategoryListIndex = 0 ifTrue: [^nil].
^self messageCategoryList at: messageCategoryListIndex!
----- Method: Browser>>selectedMessageName (in category 'message list') -----
selectedMessageName
"Answer the message selector of the currently selected message, if any.
Answer nil otherwise."
| aList |
messageListIndex = 0 ifTrue: [^ nil].
^ (aList := self messageList) size >= messageListIndex
ifTrue:
[aList at: messageListIndex]
ifFalse:
[nil]!
----- Method: Browser>>selectedMessageName: (in category 'message list') -----
selectedMessageName: aSelector
"Make the given selector be the selected message name"
| anIndex |
anIndex := self messageList indexOf: aSelector.
anIndex > 0 ifTrue:
[self messageListIndex: anIndex]!
----- Method: Browser>>selectedSystemCategoryName (in category 'system category list') -----
selectedSystemCategoryName
"Answer the name of the selected system category or nil."
systemCategoryListIndex = 0 ifTrue: [^nil].
^self systemCategoryList at: systemCategoryListIndex!
----- Method: Browser>>setClass:selector: (in category 'initialize-release') -----
setClass: aBehavior selector: aSymbol
"Set the state of a new, uninitialized Browser."
| isMeta aClass messageCatIndex |
aBehavior ifNil: [^ self].
(aBehavior isKindOf: Metaclass)
ifTrue: [
isMeta := true.
aClass := aBehavior soleInstance]
ifFalse: [
isMeta := false.
aClass := aBehavior].
self selectCategoryForClass: aClass.
self classListIndex: (self classListIndexOf: aClass name).
self metaClassIndicated: isMeta.
aSymbol ifNil: [^ self].
messageCatIndex := aBehavior organization numberOfCategoryOfElement: aSymbol.
self messageCategoryListIndex: (messageCatIndex > 0
ifTrue: [messageCatIndex + 1]
ifFalse: [0]).
messageCatIndex = 0 ifTrue: [^ self].
self messageListIndex: (
(aBehavior organization listAtCategoryNumber: messageCatIndex)
indexOf: aSymbol).!
----- Method: Browser>>setClassOrganizer (in category 'metaclass') -----
setClassOrganizer
"Install whatever organization is appropriate"
| theClass |
classOrganizer := nil.
metaClassOrganizer := nil.
classListIndex = 0 ifTrue: [^ self].
theClass := self selectedClass ifNil: [ ^self ].
classOrganizer := theClass organization.
metaClassOrganizer := theClass classSide organization.!
----- Method: Browser>>setOriginalCategoryIndexForCurrentMethod (in category 'message category list') -----
setOriginalCategoryIndexForCurrentMethod
"private - Set the message category index for the currently selected method.
Note: This should only be called when somebody tries to save
a method that they are modifying while ALL is selected."
messageCategoryListIndex := self messageCategoryList indexOf: self categoryOfCurrentMethod
!
----- Method: Browser>>setSelector: (in category 'initialize-release') -----
setSelector: aSymbol
"Make the receiver point at the given selector, in the currently chosen class"
| aClass messageCatIndex |
aSymbol ifNil: [^ self].
(aClass := self selectedClassOrMetaClass) ifNil: [^ self].
messageCatIndex := aClass organization numberOfCategoryOfElement: aSymbol.
self messageCategoryListIndex: messageCatIndex + 1.
messageCatIndex = 0 ifTrue: [^ self].
self messageListIndex:
((aClass organization listAtCategoryNumber: messageCatIndex)
indexOf: aSymbol)!
----- Method: Browser>>shiftedClassListMenu: (in category 'class functions') -----
shiftedClassListMenu: aMenu
"Set up the menu to apply to the receiver's class list when the shift key is down"
^ aMenu addList: #(
-
('unsent methods' browseUnusedMethods 'browse all methods defined by this class that have no senders')
('unreferenced inst vars' showUnreferencedInstVars 'show a list of all instance variables that are not referenced in methods')
('unreferenced class vars' showUnreferencedClassVars 'show a list of all class variables that are not referenced in methods')
('subclass template' makeNewSubclass 'put a template into the code pane for defining of a subclass of this class')
-
('sample instance' makeSampleInstance 'give me a sample instance of this class, if possible')
('inspect instances' inspectInstances 'open an inspector on all the extant instances of this class')
('inspect subinstances' inspectSubInstances 'open an inspector on all the extant instances of this class and of all of its subclasses')
-
('add all meths to current chgs' addAllMethodsToCurrentChangeSet
'place all the methods defined by this class into the current change set')
('create inst var accessors' createInstVarAccessors 'compile instance-variable access methods for any instance variables that do not yet have them')
-
('more...' offerUnshiftedClassListMenu 'return to the standard class-list menu'))!
----- Method: Browser>>shiftedMessageListMenu: (in category 'message functions') -----
shiftedMessageListMenu: aMenu
"Fill aMenu with the items appropriate when the shift key is held down"
Smalltalk isMorphic ifTrue: [aMenu addStayUpItem].
aMenu addList: #(
('method pane' makeIsolatedCodePane)
('tile scriptor' openSyntaxView)
('toggle diffing (D)' toggleDiffing)
('implementors of sent messages' browseAllMessages)
-
('local senders of...' browseLocalSendersOfMessages)
('local implementors of...' browseLocalImplementors)
-
('spawn sub-protocol' spawnProtocol)
('spawn full protocol' spawnFullProtocol)
-
('sample instance' makeSampleInstance)
('inspect instances' inspectInstances)
('inspect subinstances' inspectSubInstances)).
self addExtraShiftedItemsTo: aMenu.
aMenu addList: #(
-
('change category...' changeCategory)).
self canShowMultipleMessageCategories ifTrue: [aMenu addList:
#(('show category (C)' showHomeCategory))].
aMenu addList: #(
-
('change sets with this method' findMethodInChangeSets)
('revert to previous version' revertToPreviousVersion)
('remove from current change set' removeFromCurrentChanges)
('revert & remove from changes' revertAndForget)
('add to current change set' adoptMessageInCurrentChangeset)
('copy up or copy down...' copyUpOrCopyDown)
-
('more...' unshiftedYellowButtonActivity)).
^ aMenu
!
----- Method: Browser>>showBytecodes (in category 'code pane') -----
showBytecodes
"Show or hide the bytecodes of the selected method -- an older protocol now mostly not relevant."
self toggleShowingByteCodes!
----- Method: Browser>>showHomeCategory (in category 'message category functions') -----
showHomeCategory
"Show the home category of the selected method. This is only really useful if one is in a tool that supports the showing of categories. Thus, it's good in browsers and hierarchy browsers but not in message-list browsers"
| aSelector |
self okToChange ifTrue:
[(aSelector := self selectedMessageName) ifNotNil:
[self selectOriginalCategoryForCurrentMethod.
self selectedMessageName: aSelector]]!
----- Method: Browser>>spawn: (in category 'accessing') -----
spawn: aString
"Create and schedule a fresh browser and place aString in its code pane. This method is called when the user issues the #spawn command (cmd-o) in any code pane. Whatever text was in the original code pane comes in to this method as the aString argument; the changes in the original code pane have already been cancelled by the time this method is called, so aString is the only copy of what the user had in his code pane."
self selectedClassOrMetaClass ifNotNil: [^ super spawn: aString].
systemCategoryListIndex ~= 0
ifTrue:
["This choice is slightly useless but is the historical implementation"
^ self buildSystemCategoryBrowserEditString: aString].
^ super spawn: aString
"This bail-out at least saves the text being spawned, which would otherwise be lost"!
----- Method: Browser>>suggestCategoryToSpawnedBrowser: (in category 'accessing') -----
suggestCategoryToSpawnedBrowser: aBrowser
"aBrowser is a message-category browser being spawned from the receiver. Tell it what it needs to know to get its category info properly set up."
(self isMemberOf: Browser) "yecch, but I didn't invent the browser hierarchy"
ifTrue:
[aBrowser messageCategoryListIndex: (self messageCategoryList indexOf: self categoryOfCurrentMethod ifAbsent: [2])]
ifFalse:
[aBrowser setOriginalCategoryIndexForCurrentMethod]!
----- Method: Browser>>systemCatSingletonKey:from: (in category 'initialize-release') -----
systemCatSingletonKey: aChar from: aView
^ self messageListKey: aChar from: aView!
----- Method: Browser>>systemCatSingletonMenu: (in category 'system category functions') -----
systemCatSingletonMenu: aMenu
^ aMenu labels:
'browse all
browse
printOut
fileOut
update
rename...
remove'
lines: #(2 4)
selections:
#(browseAllClasses buildSystemCategoryBrowser
printOutSystemCategory fileOutSystemCategory updateSystemCategories
renameSystemCategory removeSystemCategory)
!
----- Method: Browser>>systemCategoryList (in category 'system category list') -----
systemCategoryList
"Answer the class categories modelled by the receiver."
^systemOrganizer categories!
----- Method: Browser>>systemCategoryListIndex (in category 'system category list') -----
systemCategoryListIndex
"Answer the index of the selected class category."
^systemCategoryListIndex!
----- Method: Browser>>systemCategoryListIndex: (in category 'system category list') -----
systemCategoryListIndex: anInteger
"Set the selected system category index to be anInteger. Update all other
selections to be deselected."
systemCategoryListIndex := anInteger.
classListIndex := 0.
messageCategoryListIndex := 0.
messageListIndex := 0.
self editSelection: ( anInteger = 0 ifTrue: [#none] ifFalse: [#newClass]).
metaClassIndicated := false.
self setClassOrganizer.
contents := nil.
self changed: #systemCategorySelectionChanged.
self changed: #systemCategoryListIndex. "update my selection"
self changed: #classList.
self changed: #messageCategoryList.
self changed: #messageList.
self changed: #relabel.
self contentsChanged!
----- Method: Browser>>systemCategoryMenu: (in category 'system category functions') -----
systemCategoryMenu: aMenu
ServiceGui browser: self classCategoryMenu: aMenu.
ServiceGui onlyServices ifTrue: [^aMenu].
^ aMenu labels:
'find class... (f)
recent classes... (r)
browse all
browse
printOut
fileOut
reorganize
alphabetize
update
add item...
rename...
remove'
lines: #(2 4 6 8)
selections:
#(findClass recent browseAllClasses buildSystemCategoryBrowser
printOutSystemCategory fileOutSystemCategory
editSystemCategories alphabetizeSystemCategories updateSystemCategories
addSystemCategory renameSystemCategory removeSystemCategory )!
----- Method: Browser>>systemCategorySingleton (in category 'system category list') -----
systemCategorySingleton
| cat |
cat := self selectedSystemCategoryName.
^ cat ifNil: [Array new]
ifNotNil: [Array with: cat]!
----- Method: Browser>>systemOrganizer: (in category 'initialize-release') -----
systemOrganizer: aSystemOrganizer
"Initialize the receiver as a perspective on the system organizer,
aSystemOrganizer. Typically there is only one--the system variable
SystemOrganization."
contents := nil.
systemOrganizer := aSystemOrganizer.
systemCategoryListIndex := 0.
classListIndex := 0.
messageCategoryListIndex := 0.
messageListIndex := 0.
metaClassIndicated := false.
self setClassOrganizer.
self editSelection: #none.!
----- Method: Browser>>toggleBreakOnEntry (in category 'breakpoints') -----
toggleBreakOnEntry
"Install or uninstall a halt-on-entry breakpoint"
| selectedMethod |
self selectedClassOrMetaClass isNil ifTrue:[^self].
selectedMethod := self selectedClassOrMetaClass >> self selectedMessageName.
selectedMethod hasBreakpoint
ifTrue:
[BreakpointManager unInstall: selectedMethod]
ifFalse:
[BreakpointManager
installInClass: self selectedClassOrMetaClass
selector: self selectedMessageName].
self changed: #messageList
!
----- Method: Browser>>toggleClassListIndex: (in category 'class list') -----
toggleClassListIndex: anInteger
"If anInteger is the current class index, deselect it. Else make it the
current class selection."
self classListIndex:
(classListIndex = anInteger
ifTrue: [0]
ifFalse: [anInteger])!
----- Method: Browser>>toggleMessageCategoryListIndex: (in category 'message category list') -----
toggleMessageCategoryListIndex: anInteger
"If the currently selected message category index is anInteger, deselect
the category. Otherwise select the category whose index is anInteger."
self messageCategoryListIndex:
(messageCategoryListIndex = anInteger
ifTrue: [0]
ifFalse: [anInteger])!
----- Method: Browser>>toggleMessageListIndex: (in category 'message list') -----
toggleMessageListIndex: anInteger
"If the currently selected message index is anInteger, deselect the message
selector. Otherwise select the message selector whose index is anInteger."
self messageListIndex:
(messageListIndex = anInteger
ifTrue: [0]
ifFalse: [anInteger])!
----- Method: Browser>>toggleSystemCategoryListIndex: (in category 'system category list') -----
toggleSystemCategoryListIndex: anInteger
"If anInteger is the current system category index, deselect it. Else make
it the current system category selection."
self systemCategoryListIndex:
(systemCategoryListIndex = anInteger
ifTrue: [0]
ifFalse: [anInteger])!
----- Method: Browser>>updateSystemCategories (in category 'system category functions') -----
updateSystemCategories
"The class categories were changed in another browser. The receiver must
reorganize its lists based on these changes."
self okToChange ifFalse: [^ self].
self changed: #systemCategoryList!
----- Method: Browser>>veryDeepInner: (in category 'copying') -----
veryDeepInner: deepCopier
"Copy all of my instance variables. Some need to be not copied at all, but shared. See DeepCopier class comment."
super veryDeepInner: deepCopier.
"systemOrganizer := systemOrganizer. clone has the old value. we share it"
"classOrganizer := classOrganizer clone has the old value. we share it"
"metaClassOrganizer := metaClassOrganizer clone has the old value. we share it"
systemCategoryListIndex := systemCategoryListIndex veryDeepCopyWith: deepCopier.
classListIndex := classListIndex veryDeepCopyWith: deepCopier.
messageCategoryListIndex := messageCategoryListIndex veryDeepCopyWith: deepCopier.
messageListIndex := messageListIndex veryDeepCopyWith: deepCopier.
editSelection := editSelection veryDeepCopyWith: deepCopier.
metaClassIndicated := metaClassIndicated veryDeepCopyWith: deepCopier.
!
----- Method: Browser>>wantsMessageCategoriesDrop: (in category 'drag and drop') -----
wantsMessageCategoriesDrop: anObject
"Only accept drops of compiled methods on system categories"
^anObject isKindOf: CompiledMethod!
----- Method: Browser>>wantsSystemCategoriesDrop: (in category 'drag and drop') -----
wantsSystemCategoriesDrop: anObject
"Only accept drops of behaviors on system categories"
^anObject isBehavior!
Browser subclass: #FileContentsBrowser
instanceVariableNames: 'packages infoString'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-File Contents Browser'!
!FileContentsBrowser commentStamp: '<historical>' prior: 0!
I am a class browser view on a fileout (either a source file (.st) or change set (.cs)). I do not actually load the code into to the system, nor do I alter the classes in the image. Use me to vet code in a comfortable way before loading it into your image.
>From a FileList, I can be invoked by selecting a source file and selecting the "browse code" menu item from the yellow button menu.
I use PseudoClass, PseudoClassOrganizers, and PseudoMetaclass to model the class structure of the source file.!
----- Method: FileContentsBrowser class>>browseCompressedCodeStream: (in category 'instance creation') -----
browseCompressedCodeStream: aStandardFileStream
"Browse the selected file in fileIn format."
| zipped unzipped |
[zipped := GZipReadStream on: aStandardFileStream.
unzipped := MultiByteBinaryOrTextStream with: zipped contents asString]
ensure: [aStandardFileStream close].
unzipped reset.
self browseStream: unzipped named: aStandardFileStream name!
----- Method: FileContentsBrowser class>>browseFile: (in category 'instance creation') -----
browseFile: aFilename
"Open a file contents browser on a file of the given name"
aFilename ifNil: [^ Beeper beep].
self browseFiles: (Array with: aFilename)!
----- Method: FileContentsBrowser class>>browseFiles: (in category 'instance creation') -----
browseFiles: fileList
| package organizer packageDict browser |
Cursor wait showWhile: [
packageDict := Dictionary new.
organizer := SystemOrganizer defaultList: Array new.
fileList do: [:fileName |
package := FilePackage fromFileNamed: fileName.
packageDict
at: package packageName
put: package.
organizer
classifyAll: package classes keys
under: package packageName].
(browser := self systemOrganizer: organizer)
packages: packageDict].
self
openBrowserView: browser createViews
label: 'File Contents Browser'.
!
----- Method: FileContentsBrowser class>>browseStream: (in category 'instance creation') -----
browseStream: aStream
aStream setConverterForCode.
self browseStream: aStream named: aStream name!
----- Method: FileContentsBrowser class>>browseStream:named: (in category 'instance creation') -----
browseStream: aStream named: aString
| package organizer packageDict browser |
Cursor wait showWhile: [
packageDict := Dictionary new.
browser := self new.
organizer := SystemOrganizer defaultList: Array new.
package := (FilePackage new fullName: aString; fileInFrom: aStream).
packageDict
at: package packageName
put: package.
organizer
classifyAll: package classes keys
under: package packageName.
(browser := self systemOrganizer: organizer)
packages: packageDict].
self
openBrowserView: browser createViews
label: 'File Contents Browser'.
!
----- Method: FileContentsBrowser class>>fileReaderServicesForDirectory: (in category 'file list services') -----
fileReaderServicesForDirectory: aDirectory
^{ self serviceBrowseCodeFiles }!
----- Method: FileContentsBrowser class>>fileReaderServicesForFile:suffix: (in category 'file list services') -----
fileReaderServicesForFile: fullName suffix: suffix
((FileStream isSourceFileSuffix: suffix) or: [ suffix = '*' ])
ifTrue: [ ^Array with: self serviceBrowseCode].
^(fullName endsWith: 'cs.gz')
ifTrue: [ Array with: self serviceBrowseCompressedCode ]
ifFalse: [#()]
!
----- Method: FileContentsBrowser class>>initialize (in category 'class initialization') -----
initialize
FileList registerFileReader: self!
----- Method: FileContentsBrowser class>>selectAndBrowseFile: (in category 'file list services') -----
selectAndBrowseFile: aFileList
"When no file are selected you can ask to browse several of them"
| selectionPattern files |
selectionPattern := UIManager default request:'What files?' initialAnswer: '*.cs;*.st'.
files := (aFileList directory fileNamesMatching: selectionPattern)
collect: [:each | aFileList directory fullNameFor: each].
self browseFiles: files.
!
----- Method: FileContentsBrowser class>>serviceBrowseCode (in category 'file list services') -----
serviceBrowseCode
"Answer the service of opening a file-contents browser"
^ (SimpleServiceEntry
provider: self
label: 'code-file browser'
selector: #browseStream:
description: 'open a "file-contents browser" on this file, allowing you to view and selectively load its code'
buttonLabel: 'code')
argumentGetter: [ :fileList | fileList readOnlyStream ]!
----- Method: FileContentsBrowser class>>serviceBrowseCodeFiles (in category 'file list services') -----
serviceBrowseCodeFiles
^ (SimpleServiceEntry
provider: self
label: 'browse code files'
selector: #selectAndBrowseFile:)
argumentGetter: [ :fileList | fileList ];
yourself!
----- Method: FileContentsBrowser class>>serviceBrowseCompressedCode (in category 'file list services') -----
serviceBrowseCompressedCode
"Answer a service for opening a changelist browser on a file"
^ (SimpleServiceEntry
provider: self
label: 'code-file browser'
selector: #browseCompressedCodeStream:
description: 'open a "file-contents browser" on this file, allowing you to view and selectively load its code'
buttonLabel: 'code')
argumentGetter: [ :fileList | fileList readOnlyStream ]!
----- Method: FileContentsBrowser class>>services (in category 'file list services') -----
services
"Answer potential file services associated with this class"
^ {self serviceBrowseCode}.!
----- Method: FileContentsBrowser class>>unload (in category 'class initialization') -----
unload
FileList unregisterFileReader: self !
----- Method: FileContentsBrowser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'File Contents Browser' brightColor: #tan pastelColor: #paleTan helpMessage: 'Lets you view the contents of a file as code, in a browser-like tool.'!
----- Method: FileContentsBrowser>>aboutToStyle: (in category 'edit pane') -----
aboutToStyle: aStyler
"This is a notification that aStyler is about to re-style its text.
Set the classOrMetaClass in aStyler, so that identifiers
will be resolved correctly.
Answer true to allow styling to proceed, or false to veto the styling"
self isModeStyleable ifFalse: [^false].
aStyler classOrMetaClass: self selectedClassOrMetaClass.
^true!
----- Method: FileContentsBrowser>>browseMethodFull (in category 'class list') -----
browseMethodFull
| myClass |
(myClass := self selectedClassOrMetaClass) ifNotNil:
[ToolSet browse: myClass realClass selector: self selectedMessageName]!
----- Method: FileContentsBrowser>>browseSenders (in category 'other') -----
browseSenders
"Create and schedule a message set browser on all senders of the
currently selected message selector. Do nothing if no message is selected."
messageListIndex ~= 0
ifTrue: [self systemNavigation browseAllCallsOn: self selectedMessageName]!
----- Method: FileContentsBrowser>>browseVersions (in category 'other') -----
browseVersions
"Create and schedule a message set browser on all versions of the
currently selected message selector."
| class selector |
(selector := self selectedMessageName) ifNotNil:
[class := self selectedClassOrMetaClass.
(class exists and: [class realClass includesSelector: selector]) ifTrue:
[VersionsBrowser
browseVersionsOf: (class realClass compiledMethodAt: selector)
class: class realClass theNonMetaClass
meta: class realClass isMeta
category: self selectedMessageCategoryName
selector: selector]]!
----- Method: FileContentsBrowser>>buildInfoViewWith: (in category 'toolbuilder') -----
buildInfoViewWith: builder
| textSpec |
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #infoViewContents.
^textSpec!
----- Method: FileContentsBrowser>>buildSystemCatListSingletonWith: (in category 'toolbuilder') -----
buildSystemCatListSingletonWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #systemCategorySingleton;
getIndex: #indexIsOne;
setIndex: #indexIsOne:;
menu: #packageListMenu:;
keyPress: #packageListKey:from:.
^listSpec!
----- Method: FileContentsBrowser>>buildSystemCategoryListWith: (in category 'toolbuilder') -----
buildSystemCategoryListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #systemCategoryList;
getIndex: #systemCategoryListIndex;
setIndex: #systemCategoryListIndex:;
menu: #packageListMenu:;
keyPress: #packageListKey:from:.
^listSpec!
----- Method: FileContentsBrowser>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"Create a pluggable version of all the views for a Browser, including views and controllers."
| windowSpec |
self packages size = 1 ifTrue:[
self systemCategoryListIndex: 1.
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 1.0 at 0.06) -> [self buildSystemCatListSingletonWith: builder].
(0 at 0.06 corner: 0.34 at 0.24) -> [self buildClassListWith: builder].
(0 at 0.24 corner: 0.34 at 0.34) -> [self buildSwitchesWith: builder].
(0.34 at 0.06 corner: 0.67 at 0.34) -> [self buildMessageCategoryListWith: builder].
(0.67 at 0.06 corner: 1.0 at 0.34) -> [self buildMessageListWith: builder].
(0.0 at 0.34 corner: 1.0 at 0.9) -> [self buildCodePaneWith: builder].
(0.0 at 0.9 corner: 1.0 at 1.0) -> [self buildInfoViewWith: builder].
}
] ifFalse:[
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 0.25 at 0.4) -> [self buildSystemCategoryListWith: builder].
(0.25 at 0 corner: 0.5 at 0.3) -> [self buildClassListWith: builder].
(0.25 at 0.3 corner: 0.5 at 0.4) -> [self buildSwitchesWith: builder].
(0.5 at 0 corner: 0.75 at 0.4) -> [self buildMessageCategoryListWith: builder].
(0.75 at 0 corner: 1.0 at 0.4) -> [self buildMessageListWith: builder].
(0.0 at 0.4 corner: 1.0 at 0.9) -> [self buildCodePaneWith: builder].
(0.0 at 0.9 corner: 1.0 at 1.0) -> [self buildInfoViewWith: builder].
}
].
^ windowSpec
!
----- Method: FileContentsBrowser>>changeMessageCategories: (in category 'other') -----
changeMessageCategories: aString
"The characters in aString represent an edited version of the the message
categories for the selected class. Update this information in the system
and inform any dependents that the categories have been changed. This
message is invoked because the user had issued the categories command
and edited the message categories. Then the user issued the accept
command."
self classOrMetaClassOrganizer changeFromString: aString.
self unlock.
self editClass.
self classListIndex: classListIndex.
^ true!
----- Method: FileContentsBrowser>>classList (in category 'class list') -----
classList
"Answer an array of the class names of the selected category. Answer an
empty array if no selection exists."
(systemCategoryListIndex = 0 or:[self selectedPackage isNil])
ifTrue: [^Array new]
ifFalse: [^self selectedPackage classes keys asSortedCollection].!
----- Method: FileContentsBrowser>>classListKey:from: (in category 'keys') -----
classListKey: aChar from: view
aChar == $b ifTrue: [^ self browseMethodFull].
aChar == $N ifTrue: [^ self browseClassRefs].
self packageListKey: aChar from: view!
----- Method: FileContentsBrowser>>classListMenu: (in category 'menus') -----
classListMenu: aMenu
^ aMenu
labels:
'definition
comment
browse full (b)
class refs (N)
fileIn
fileOut
rename...
remove
remove existing'
lines: #(2 4 6 8)
selections: #(editClass editComment browseMethodFull browseClassRefs fileInClass fileOutClass renameClass removeClass removeUnmodifiedCategories)
!
----- Method: FileContentsBrowser>>classListMenu:shifted: (in category 'menus') -----
classListMenu: aMenu shifted: ignored
"Answer the class list menu, ignoring the state of the shift key in this case"
^ self classListMenu: aMenu!
----- Method: FileContentsBrowser>>contents (in category 'accessing') -----
contents
self updateInfoView.
(editSelection == #newClass and:[self selectedPackage notNil])
ifTrue: [^self selectedPackage packageInfo].
editSelection == #editClass
ifTrue:[^self modifiedClassDefinition].
^super contents!
----- Method: FileContentsBrowser>>contents:notifying: (in category 'accessing') -----
contents: input notifying: aController
"The retrieved information has changed and its source must now be
updated. The information can be a variety of things, depending on the
list selections (such as templates for class or message definition, methods)
or the user menu commands (such as definition, comment, hierarchy).
Answer the result of updating the source."
| aString aText theClass |
aString := input asString.
aText := input asText.
editSelection == #editComment
ifTrue: [theClass := self selectedClass.
theClass ifNil: [self inform: 'You must select a class
before giving it a comment.'.
^ false].
theClass comment: aText. ^ true].
editSelection == #editMessageCategories
ifTrue: [^ self changeMessageCategories: aString].
self inform:'You cannot change the current selection'.
^false
!
----- Method: FileContentsBrowser>>contentsSymbolQuints (in category 'menus') -----
contentsSymbolQuints
"Answer a list of quintuplets representing information on the alternative views available in the code pane. For the file-contents browser, the choices are restricted to source and the two diffing options"
^ self sourceAndDiffsQuintsOnly!
----- Method: FileContentsBrowser>>createViews (in category 'creation') -----
createViews
"Create a pluggable version of all the views for a Browser, including views and controllers."
contentsSymbol := self defaultDiffsSymbol. "#showDiffs or #prettyDiffs"
^self buildWith: ToolBuilder default!
----- Method: FileContentsBrowser>>defaultBrowserTitle (in category 'initialize-release') -----
defaultBrowserTitle
^ 'File Contents Browser'!
----- Method: FileContentsBrowser>>didCodeChangeElsewhere (in category 'other') -----
didCodeChangeElsewhere
"Determine whether the code for the currently selected method and class has been changed somewhere else."
| aClass |
(aClass := self selectedClassOrMetaClass) ifNil: [^ false].
(aClass isKindOf: PseudoClass) ifTrue: [^ false]. "class not installed"
^super didCodeChangeElsewhere!
----- Method: FileContentsBrowser>>extraInfo (in category 'infoView') -----
extraInfo
^ (self
methodDiffFor: (self selectedClassOrMetaClass sourceCodeAt: self selectedMessageName)
class: self selectedClass
selector: self selectedMessageName
meta: self metaClassIndicated) unembellished
ifTrue: [' - identical']
ifFalse: [' - modified']!
----- Method: FileContentsBrowser>>fileInClass (in category 'fileIn/fileOut') -----
fileInClass
Cursor read showWhile:[
self selectedClass fileIn.
].!
----- Method: FileContentsBrowser>>fileInMessage (in category 'fileIn/fileOut') -----
fileInMessage
self selectedMessageName ifNil: [^self].
Cursor read showWhile: [
self selectedClassOrMetaClass fileInMethod: self selectedMessageName.
].!
----- Method: FileContentsBrowser>>fileInMessageCategories (in category 'fileIn/fileOut') -----
fileInMessageCategories
Cursor read showWhile:[
self selectedClassOrMetaClass fileInCategory: self selectedMessageCategoryName.
].!
----- Method: FileContentsBrowser>>fileInPackage (in category 'fileIn/fileOut') -----
fileInPackage
Cursor read showWhile:[
self selectedPackage fileIn.
].!
----- Method: FileContentsBrowser>>fileIntoNewChangeSet (in category 'fileIn/fileOut') -----
fileIntoNewChangeSet
| p ff |
(p := self selectedPackage) ifNil: [^ Beeper beep].
ff := FileStream readOnlyFileNamed: p fullPackageName.
ChangeSet newChangesFromStream: ff named: p packageName!
----- Method: FileContentsBrowser>>fileOutClass (in category 'fileIn/fileOut') -----
fileOutClass
Cursor write showWhile:[
self selectedClass fileOut.
].!
----- Method: FileContentsBrowser>>fileOutMessage (in category 'fileIn/fileOut') -----
fileOutMessage
self selectedMessageName ifNil: [^self].
Cursor write showWhile: [
self selectedClassOrMetaClass fileOutMethod: self selectedMessageName].!
----- Method: FileContentsBrowser>>fileOutMessageCategories (in category 'fileIn/fileOut') -----
fileOutMessageCategories
Cursor write showWhile:[
self selectedClassOrMetaClass fileOutCategory: self selectedMessageCategoryName.
].!
----- Method: FileContentsBrowser>>fileOutPackage (in category 'fileIn/fileOut') -----
fileOutPackage
Cursor write showWhile:[
self selectedPackage fileOut.
].!
----- Method: FileContentsBrowser>>findClass (in category 'class list') -----
findClass
| pattern foundClass classNames index foundPackage |
self okToChange ifFalse: [^ self classNotFound].
pattern := (UIManager default request: 'Class Name?') asLowercase.
pattern isEmpty ifTrue: [^ self].
classNames := Set new.
self packages do:[:p| classNames addAll: p classes keys].
classNames := classNames asArray select:
[:n | (n asLowercase indexOfSubCollection: pattern startingAt: 1) > 0].
classNames isEmpty ifTrue: [^ self].
index := classNames size == 1
ifTrue: [1]
ifFalse: [(UIManager default chooseFrom: classNames lines: #())].
index = 0 ifTrue: [^ self].
foundPackage := nil.
foundClass := nil.
self packages do:[:p|
(p classes includesKey: (classNames at: index)) ifTrue:[
foundClass := p classes at: (classNames at: index).
foundPackage := p]].
foundClass isNil ifTrue:[^self].
self systemCategoryListIndex: (self systemCategoryList indexOf: foundPackage packageName asSymbol).
self classListIndex: (self classList indexOf: foundClass name). !
----- Method: FileContentsBrowser>>infoString (in category 'infoView') -----
infoString
^infoString isNil
ifTrue:[infoString := StringHolder new]
ifFalse:[infoString]!
----- Method: FileContentsBrowser>>infoViewContents (in category 'infoView') -----
infoViewContents
"Answer the string to show in the info view"
| theClass stamp exists |
editSelection == #newClass ifTrue: [^ self packageInfo: self selectedPackage].
self selectedClass isNil ifTrue: [^ ''].
theClass := Smalltalk at: self selectedClass name asSymbol ifAbsent: [].
editSelection == #editClass ifTrue:
[^ theClass notNil
ifTrue: ['Class exists already in the system' translated]
ifFalse: ['New class' translated]].
editSelection == #editMessage ifFalse: [^ ''].
(theClass notNil and: [self metaClassIndicated])
ifTrue: [theClass := theClass class].
stamp := self selectedClassOrMetaClass stampAt: self selectedMessageName.
exists := theClass notNil and: [theClass includesSelector: self selectedMessageName].
^ stamp = 'methodWasRemoved'
ifTrue:
[exists
ifTrue:
['Existing method removed by this change-set' translated]
ifFalse:
['Removal request for a method that is not present in this image' translated]]
ifFalse:
[stamp, ' · ',
(exists
ifTrue: ['Method already exists' translated , self extraInfo]
ifFalse: ['New method' translated])]!
----- Method: FileContentsBrowser>>labelString (in category 'other') -----
labelString
"Answer the string for the window title"
^ 'File Contents Browser ', (self selectedSystemCategoryName ifNil: [''])!
----- Method: FileContentsBrowser>>messageCategoryMenu: (in category 'menus') -----
messageCategoryMenu: aMenu
^ aMenu
labels:
'fileIn
fileOut
reorganize
add item...
rename...
remove
remove existing'
lines: #(2 3 6)
selections: #(fileInMessageCategories fileOutMessageCategories editMessageCategories addCategory renameCategory removeMessageCategory removeUnmodifiedMethods)!
----- Method: FileContentsBrowser>>messageListKey:from: (in category 'keys') -----
messageListKey: aChar from: view
aChar == $b ifTrue: [^ self browseMethodFull].
super messageListKey: aChar from: view!
----- Method: FileContentsBrowser>>messageListMenu:shifted: (in category 'menus') -----
messageListMenu: aMenu shifted: aBool
^ aMenu
labels:
'fileIn
fileOut
senders (n)
implementors (m)
method inheritance (h)
versions (v)
remove'
lines: #(2 6)
selections: #(fileInMessage fileOutMessage
browseSenders browseImplementors methodHierarchy browseVersions
removeMessage).
!
----- Method: FileContentsBrowser>>methodDiffFor:class:selector:meta: (in category 'diffs') -----
methodDiffFor: aString class: aPseudoClass selector: selector meta: meta
"Answer the diff between the current copy of the given class/selector/meta for the string provided"
| theClass source |
theClass := Smalltalk
at: aPseudoClass name
ifAbsent: [^ aString copy].
meta
ifTrue: [theClass := theClass class].
(theClass includesSelector: selector)
ifFalse: [^ aString copy].
source := theClass sourceCodeAt: selector.
^ Cursor wait
showWhile: [TextDiffBuilder buildDisplayPatchFrom: source to: aString inClass: theClass prettyDiffs: self showingPrettyDiffs]!
----- Method: FileContentsBrowser>>methodHierarchy (in category 'other') -----
methodHierarchy
(self selectedClassOrMetaClass isNil or:
[self selectedClassOrMetaClass hasDefinition])
ifFalse: [super methodHierarchy]!
----- Method: FileContentsBrowser>>modifiedClassDefinition (in category 'diffs') -----
modifiedClassDefinition
| pClass rClass old new diff |
pClass := self selectedClassOrMetaClass.
pClass hasDefinition ifFalse:[^pClass definition].
rClass := Smalltalk at: self selectedClass name asSymbol ifAbsent:[nil].
rClass isNil ifTrue:[^pClass definition].
self metaClassIndicated ifTrue:[ rClass := rClass class].
old := rClass definition.
new := pClass definition.
Cursor wait showWhile:[
diff := ClassDiffBuilder buildDisplayPatchFrom: old to: new
].
^diff!
----- Method: FileContentsBrowser>>packageInfo: (in category 'infoView') -----
packageInfo: p
| nClasses newClasses oldClasses |
p isNil ifTrue:[^''].
nClasses := newClasses := oldClasses := 0.
p classes do:[:cls|
nClasses := nClasses + 1.
(Smalltalk includesKey: (cls name asSymbol))
ifTrue:[oldClasses := oldClasses + 1]
ifFalse:[newClasses := newClasses + 1]].
^nClasses printString,' classes (', newClasses printString, ' new / ', oldClasses printString, ' modified)'!
----- Method: FileContentsBrowser>>packageListKey:from: (in category 'keys') -----
packageListKey: aChar from: view
aChar == $f ifTrue: [^ self findClass].
self arrowKey: aChar from: view!
----- Method: FileContentsBrowser>>packageListMenu: (in category 'menus') -----
packageListMenu: aMenu
^ aMenu
labels:
'find class... (f)
fileIn
file into new changeset
fileOut
remove
remove existing'
lines: #(1 4 5)
selections: #(findClass fileInPackage fileIntoNewChangeSet fileOutPackage removePackage removeUnmodifiedClasses)!
----- Method: FileContentsBrowser>>packages (in category 'accessing') -----
packages
^packages!
----- Method: FileContentsBrowser>>packages: (in category 'accessing') -----
packages: aDictionary
packages := aDictionary.!
----- Method: FileContentsBrowser>>removeClass (in category 'removing') -----
removeClass
| class |
classListIndex = 0 ifTrue: [^ self].
class := self selectedClass.
(self confirm:'Are you certain that you
want to delete the class ', class name, '?') ifFalse:[^self].
self selectedPackage removeClass: class.
self classListIndex: 0.
self changed: #classList.!
----- Method: FileContentsBrowser>>removeMessage (in category 'removing') -----
removeMessage
| messageName |
messageListIndex = 0
ifTrue: [^ self].
self okToChange
ifFalse: [^ self].
messageName := self selectedMessageName.
(self selectedClass confirmRemovalOf: messageName)
ifFalse: [^ false].
self selectedClassOrMetaClass removeMethod: self selectedMessageName.
self messageListIndex: 0.
self setClassOrganizer.
"In case organization not cached"
self changed: #messageList!
----- Method: FileContentsBrowser>>removeMessageCategory (in category 'removing') -----
removeMessageCategory
"If a message category is selected, create a Confirmer so the user can
verify that the currently selected message category should be removed
from the system. If so, remove it."
| messageCategoryName |
messageCategoryListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
messageCategoryName := self selectedMessageCategoryName.
(self messageList size = 0
or: [self confirm: 'Are you sure you want to
remove this method category
and all its methods?']) ifFalse: [^ self].
self selectedClassOrMetaClass removeCategory: messageCategoryName.
self messageCategoryListIndex: 0.
self changed: #messageCategoryList.!
----- Method: FileContentsBrowser>>removePackage (in category 'removing') -----
removePackage
systemCategoryListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
(self confirm: 'Are you sure you want to
remove this package
and all its classes?') ifFalse:[^self].
(systemOrganizer listAtCategoryNamed: self selectedSystemCategoryName) do:[:el|
systemOrganizer removeElement: el].
self packages removeKey: self selectedPackage packageName.
systemOrganizer removeCategory: self selectedSystemCategoryName.
self systemCategoryListIndex: 0.
self changed: #systemCategoryList!
----- Method: FileContentsBrowser>>removeUnmodifiedCategories (in category 'removing') -----
removeUnmodifiedCategories
| theClass |
self okToChange ifFalse: [^self].
theClass := self selectedClass.
theClass isNil ifTrue: [^self].
Cursor wait showWhile:
[theClass removeUnmodifiedMethods: theClass selectors.
theClass metaClass removeUnmodifiedMethods: theClass metaClass selectors].
self messageCategoryListIndex: 0.
self changed: #messageCategoryList.!
----- Method: FileContentsBrowser>>removeUnmodifiedClasses (in category 'removing') -----
removeUnmodifiedClasses
| packageList |
self okToChange ifFalse:[^self].
packageList := self selectedPackage isNil
ifTrue:[self packages]
ifFalse:[Array with: self selectedPackage].
packageList do:[:package|
package classes copy do:[:theClass|
Cursor wait showWhile:[
theClass removeAllUnmodified.
].
theClass hasChanges ifFalse:[
package removeClass: theClass.
].
]].
self classListIndex: 0.
self changed: #classList.!
----- Method: FileContentsBrowser>>removeUnmodifiedMethods (in category 'removing') -----
removeUnmodifiedMethods
| theClass cat |
self okToChange ifFalse:[^self].
theClass := self selectedClassOrMetaClass.
theClass isNil ifTrue:[^self].
cat := self selectedMessageCategoryName.
cat isNil ifTrue:[^self].
Cursor wait showWhile:[
theClass removeUnmodifiedMethods: (theClass organization listAtCategoryNamed: cat).
].
self messageListIndex: 0.
self changed: #messageList.!
----- Method: FileContentsBrowser>>renameClass (in category 'class list') -----
renameClass
| oldName newName |
classListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
oldName := self selectedClass name.
newName := (self request: 'Please type new class name'
initialAnswer: oldName) asSymbol.
(newName isEmpty or:[newName = oldName]) ifTrue: [^ self].
(self selectedPackage classes includesKey: newName)
ifTrue: [^ self error: newName , ' already exists in the package'].
systemOrganizer classify: newName under: self selectedSystemCategoryName.
systemOrganizer removeElement: oldName.
self selectedPackage renameClass: self selectedClass to: newName.
self changed: #classList.
self classListIndex: ((systemOrganizer listAtCategoryNamed: self selectedSystemCategoryName) indexOf: newName).
!
----- Method: FileContentsBrowser>>selectedBytecodes (in category 'edit pane') -----
selectedBytecodes
"Compile the source code for the selected message selector and extract and return
the bytecode listing."
| class selector |
class := self selectedClassOrMetaClass.
selector := self selectedMessageName.
contents := class sourceCodeAt: selector.
contents := Compiler new
parse: contents
in: class
notifying: nil.
contents := contents generate.
^ contents symbolic asText!
----- Method: FileContentsBrowser>>selectedClass (in category 'class list') -----
selectedClass
"Answer the class that is currently selected. Answer nil if no selection
exists."
self selectedClassName == nil ifTrue: [^nil].
^self selectedPackage classAt: self selectedClassName!
----- Method: FileContentsBrowser>>selectedClassOrMetaClass (in category 'metaclass') -----
selectedClassOrMetaClass
"Answer the selected class or metaclass."
| cls |
self metaClassIndicated
ifTrue: [^ (cls := self selectedClass) ifNotNil: [cls metaClass]]
ifFalse: [^ self selectedClass]!
----- Method: FileContentsBrowser>>selectedMessage (in category 'edit pane') -----
selectedMessage
"Answer a copy of the source code for the selected message selector."
| class selector |
class := self selectedClassOrMetaClass.
selector := self selectedMessageName.
contents := class sourceCodeAt: selector.
Preferences browseWithPrettyPrint
ifTrue:
[contents := class prettyPrinterClass
format: contents
in: class
notifying: nil
decorated: false].
self showingAnyKindOfDiffs
ifTrue:
[contents := self
methodDiffFor: contents
class: self selectedClass
selector: self selectedMessageName
meta: self metaClassIndicated].
^contents asText makeSelectorBoldIn: class!
----- Method: FileContentsBrowser>>selectedPackage (in category 'accessing') -----
selectedPackage
| cat |
cat := self selectedSystemCategoryName.
cat isNil ifTrue:[^nil].
^self packages at: cat asString ifAbsent:[nil]!
----- Method: FileContentsBrowser>>setClassOrganizer (in category 'metaclass') -----
setClassOrganizer
"Install whatever organization is appropriate"
| theClass |
classOrganizer := nil.
metaClassOrganizer := nil.
classListIndex = 0 ifTrue: [^ self].
classOrganizer := (theClass := self selectedClass) organization.
metaClassOrganizer := theClass metaClass organization.
!
----- Method: FileContentsBrowser>>updateInfoView (in category 'infoView') -----
updateInfoView
Smalltalk isMorphic
ifTrue: [self changed: #infoViewContents]
ifFalse: [
self infoString contents: self infoViewContents.
self infoString changed].!
----- Method: FileContentsBrowser>>wantsAnnotationPane (in category 'toolbuilder') -----
wantsAnnotationPane
"Never. FCB has its info pane already which serves the same purpose."
^false
!
Browser subclass: #HierarchyBrowser
instanceVariableNames: 'classList centralClass'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
HierarchyBrowser subclass: #ClassListBrowser
instanceVariableNames: 'defaultTitle'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
!ClassListBrowser commentStamp: '<historical>' prior: 0!
A ClassListBrowser displays the code for an arbitrary list of classes.
ClassListBrowser example1. "all classes that have the string 'Pluggable' in their names"
ClassListBrowser example2. "all classes whose names start with the letter S"
ClassListBrowser example3. "all variable classes"
ClassListBrowser example4. "all classes with more than 100 methods"
ClassListBrowser example5. "all classes that lack class comments"
ClassListBrowser example6. "all classes that have class instance variables"
ClassListBrowser new initForClassesNamed: #(Browser Boolean) title: 'Browser and Boolean!!'.
!
----- Method: ClassListBrowser class>>browseClassesSatisfying:title: (in category 'instance creation') -----
browseClassesSatisfying: classBlock title: aTitle
"Put up a ClassListBrowser showing all classes that satisfy the classBlock."
self new
initForClassesNamed:
(self systemNavigation allClasses select:
[:c | (classBlock value: c) == true]
thenCollect:
[:c | c name])
title:
aTitle!
----- Method: ClassListBrowser class>>example1 (in category 'examples') -----
example1
"Put up a ClassListBrowser that shows all classes that have the string 'Pluggable' in their names"
self browseClassesSatisfying: [:cl | cl name includesSubString: 'Pluggable'] title: 'Pluggables'
"ClassListBrowser example1"
!
----- Method: ClassListBrowser class>>example2 (in category 'examples') -----
example2
"Put up a ClassListBrowser that shows all classes whose names start with
the letter S"
self new
initForClassesNamed: (self systemNavigation allClasses
collect: [:c | c name]
thenSelect: [:aName | aName first == $S])
title: 'All classes starting with S'
"ClassListBrowser example2"!
----- Method: ClassListBrowser class>>example3 (in category 'examples') -----
example3
"Put up a ClassListBrowser that shows all Variable classes"
self browseClassesSatisfying: [:c | c isVariable] title: 'All Variable classes'
"ClassListBrowser example3"
!
----- Method: ClassListBrowser class>>example4 (in category 'examples') -----
example4
"Put up a ClassListBrowser that shows all classes implementing more than 100 methods"
self browseClassesSatisfying:
[:c | (c selectors size + c class selectors size) > 100] title: 'Classes with more than 100 methods'
"ClassListBrowser example4"
!
----- Method: ClassListBrowser class>>example5 (in category 'examples') -----
example5
"Put up a ClassListBrowser that shows all classes that lack class comments"
self
browseClassesSatisfying:
[:c | c organization classComment isEmptyOrNil]
title: 'Classes lacking class comments'
"ClassListBrowser example5"
!
----- Method: ClassListBrowser class>>example6 (in category 'examples') -----
example6
"Put up a ClassListBrowser that shows all classes that have class instance variables"
self
browseClassesSatisfying:
[:c | c class instVarNames size > 0]
title:
'Classes that define class-side instance variables'
"ClassListBrowser example6"!
----- Method: ClassListBrowser>>defaultTitle: (in category 'title') -----
defaultTitle: aTitle
"Set the browser's default title"
defaultTitle := aTitle!
----- Method: ClassListBrowser>>initForClassesNamed:title: (in category 'initialization') -----
initForClassesNamed: nameList title: aTitle
"Initialize the receiver for the class-name-list and title provided"
self systemOrganizer: SystemOrganization.
metaClassIndicated := false.
defaultTitle := aTitle.
classList := nameList copy.
self class openBrowserView: (self openSystemCatEditString: nil)
label: aTitle
"ClassListBrowser new initForClassesNamed: #(Browser CategoryViewer) title: 'Frogs'"!
----- Method: ClassListBrowser>>labelString (in category 'title') -----
labelString
"Answer the label strilng to use on the browser"
^ defaultTitle ifNil: [super labelString]!
----- Method: HierarchyBrowser class>>newFor: (in category 'instance creation') -----
newFor: aClass
"Open a new HierarchyBrowser on the given class"
| newBrowser |
newBrowser := HierarchyBrowser new initHierarchyForClass: aClass.
Browser openBrowserView: (newBrowser openSystemCatEditString: nil)
label: newBrowser labelString
"HierarchyBrowser newFor: Boolean"!
----- Method: HierarchyBrowser class>>newFor:labeled: (in category 'instance creation') -----
newFor: aClass labeled: aLabel
"Open a new HierarchyBrowser on the given class, using aLabel as the window title."
| newBrowser |
newBrowser := HierarchyBrowser new initHierarchyForClass: aClass.
Browser openBrowserView: (newBrowser openSystemCatEditString: nil)
label: aLabel
"HierarchyBrowser newFor: Boolean labeled: 'Testing'"!
----- Method: HierarchyBrowser>>assureSelectionsShow (in category 'class list') -----
assureSelectionsShow
"This is a workaround for the fact that a hierarchy browser, when launched, often does not show the selected class"
| saveCatIndex saveMsgIndex |
saveCatIndex := messageCategoryListIndex.
saveMsgIndex := messageListIndex.
self classListIndex: classListIndex.
self messageCategoryListIndex: saveCatIndex.
self messageListIndex: saveMsgIndex!
----- Method: HierarchyBrowser>>buildClassBrowserEditString: (in category 'menu messages') -----
buildClassBrowserEditString: aString
"Create and schedule a new class browser for the current selection, if one
exists, with initial textual contents set to aString."
self spawnHierarchy!
----- Method: HierarchyBrowser>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
^builder build: (self openSystemCatEditString: '')!
----- Method: HierarchyBrowser>>changed: (in category 'initialization') -----
changed: sym
sym == #classList ifTrue: [self updateAfterClassChange].
super changed: sym!
----- Method: HierarchyBrowser>>classList (in category 'class list') -----
classList
classList := classList select: [:each | Smalltalk includesKey: each withBlanksTrimmed asSymbol].
^ classList!
----- Method: HierarchyBrowser>>classListIndex: (in category 'initialization') -----
classListIndex: newIndex
"Cause system organization to reflect appropriate category"
| newClassName ind |
newIndex ~= 0 ifTrue:
[newClassName := (classList at: newIndex) copyWithout: $ .
systemCategoryListIndex :=
systemOrganizer numberOfCategoryOfElement: newClassName].
ind := super classListIndex: newIndex.
self changed: #systemCategorySingleton.
^ ind!
----- Method: HierarchyBrowser>>defaultBrowserTitle (in category 'initialization') -----
defaultBrowserTitle
^ 'Hierarchy Browser'!
----- Method: HierarchyBrowser>>initAlphabeticListing (in category 'initialization') -----
initAlphabeticListing
| tab stab index |
self systemOrganizer: SystemOrganization.
metaClassIndicated := false.
classList := Smalltalk classNames.!
----- Method: HierarchyBrowser>>initHierarchyForClass: (in category 'initialization') -----
initHierarchyForClass: aClassOrMetaClass
| tab stab index nonMetaClass |
centralClass := aClassOrMetaClass.
nonMetaClass := aClassOrMetaClass theNonMetaClass.
self systemOrganizer: SystemOrganization.
metaClassIndicated := aClassOrMetaClass isMeta.
classList := OrderedCollection new.
tab := ''.
nonMetaClass allSuperclasses reverseDo:
[:aClass |
classList add: tab , aClass name.
tab := tab , ' '].
index := classList size + 1.
nonMetaClass allSubclassesWithLevelDo:
[:aClass :level |
stab := ''. 1 to: level do: [:i | stab := stab , ' '].
classList add: tab , stab , aClass name]
startingLevel: 0.
self classListIndex: index!
----- Method: HierarchyBrowser>>openEditString: (in category 'initialization') -----
openEditString: aString
"Create a pluggable version of all the views for a HierarchyBrowser, including views and controllers. The top list view is of the currently selected system class category--a single item list."
^ self openSystemCatEditString: aString!
----- Method: HierarchyBrowser>>potentialClassNames (in category 'initialization') -----
potentialClassNames
"Answer the names of all the classes that could be viewed in this browser"
^ self classList collect:
[:aName | aName copyWithout: $ ]!
----- Method: HierarchyBrowser>>removeSystemCategory (in category 'menu messages') -----
removeSystemCategory
"If a class category is selected, create a Confirmer so the user can
verify that the currently selected class category and all of its classes
should be removed from the system. If so, remove it."
self inform: 'Use a normal Browser, in which you can see
the entire category you are trying to remove.'!
----- Method: HierarchyBrowser>>selectClass: (in category 'initialization') -----
selectClass: classNotMeta
| name |
name := classNotMeta name.
self classListIndex: (self classList findFirst:
[:each | (each endsWith: name)
and: [each size = name size
or: [(each at: each size - name size) isSeparator]]])!
----- Method: HierarchyBrowser>>selectedClassName (in category 'initialization') -----
selectedClassName
"Answer the name of the class currently selected. di
bug fix for the case where name cannot be found -- return nil rather than halt"
| aName |
aName := self classList at: classListIndex ifAbsent: [^ nil].
^ (aName copyWithout: Character space) asSymbol!
----- Method: HierarchyBrowser>>systemCatSingletonKey:from: (in category 'menu messages') -----
systemCatSingletonKey: aChar from: aView
^ self systemCatListKey: aChar from: aView!
----- Method: HierarchyBrowser>>systemCatSingletonMenu: (in category 'menu messages') -----
systemCatSingletonMenu: aMenu
^ aMenu labels:
'find class... (f)
browse
printOut
fileOut
update
rename...
remove'
lines: #(1 4)
selections:
#(findClass buildSystemCategoryBrowser
printOutSystemCategory fileOutSystemCategory updateSystemCategories
renameSystemCategory removeSystemCategory )
!
----- Method: HierarchyBrowser>>systemCategorySingleton (in category 'initialization') -----
systemCategorySingleton
| cls |
cls := self selectedClass.
^ cls ifNil: [Array new]
ifNotNil: [Array with: cls category]!
----- Method: HierarchyBrowser>>updateAfterClassChange (in category 'initialization') -----
updateAfterClassChange
"It is possible that some the classes comprising the hierarchy have changed, so reinitialize the entire browser."
(centralClass notNil and: [centralClass isObsolete not])
ifTrue: [self initHierarchyForClass: centralClass]!
Browser subclass: #MessageSet
instanceVariableNames: 'messageList autoSelectString growable'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
!MessageSet commentStamp: '<historical>' prior: 0!
I represent a query path of the retrieval result of making a query about methods in the system. The result is a set of methods, denoted by a message selector and the class in which the method was found. As a StringHolder, the string I represent is the source code of the currently selected method. I am typically viewed in a Message Set Browser consisting of a MessageListView and a BrowserCodeView.!
MessageSet subclass: #ChangedMessageSet
instanceVariableNames: 'changeSet'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
!ChangedMessageSet commentStamp: '<historical>' prior: 0!
A ChangedMessageSet is a message set associated with a change-set; it bears an entry for every method added or changed in the change set, as well as for every class-comment of which the change-set bears a note.!
----- Method: ChangedMessageSet class>>openFor: (in category 'as yet unclassified') -----
openFor: aChangeSet
"Open up a ChangedMessageSet browser on the given change set; this is a conventional message-list browser whose message-list consists of all the methods in aChangeSet. After any method submission, the message list is refigured, making it plausibly dynamic"
| messageSet |
messageSet := aChangeSet changedMessageListAugmented select: [ :each | each isValid].
self
openMessageList: messageSet
name: 'Methods in Change Set ', aChangeSet name
autoSelect: nil
changeSet: aChangeSet!
----- Method: ChangedMessageSet class>>openMessageList:name:autoSelect:changeSet: (in category 'as yet unclassified') -----
openMessageList: messageList name: labelString autoSelect: autoSelectString changeSet: aChangeSet
| messageSet |
messageSet := self messageList: messageList.
messageSet changeSet: aChangeSet.
messageSet autoSelectString: autoSelectString.
ToolBuilder open: messageSet label: labelString.!
----- Method: ChangedMessageSet>>changeSet: (in category 'initialization') -----
changeSet: aChangeSet
changeSet := aChangeSet!
----- Method: ChangedMessageSet>>contents:notifying: (in category 'acceptance') -----
contents: aString notifying: aController
"Accept the string as new source for the current method, and make certain the annotation pane gets invalidated"
| existingSelector existingClass superResult newSelector |
existingSelector := self selectedMessageName.
existingClass := self selectedClassOrMetaClass.
superResult := super contents: aString notifying: aController.
superResult ifTrue: "succeeded"
[newSelector := existingClass parserClass new parseSelector: aString.
newSelector ~= existingSelector
ifTrue: "Selector changed -- maybe an addition"
[self reformulateList.
self changed: #messageList.
self messageList doWithIndex:
[:aMethodReference :anIndex |
(aMethodReference actualClass == existingClass and:
[aMethodReference methodSymbol == newSelector])
ifTrue:
[self messageListIndex: anIndex]]]].
^ superResult!
----- Method: ChangedMessageSet>>growable (in category 'message list') -----
growable
"Answer whether the receiver can be changed by manual additions & deletions"
^ false!
----- Method: ChangedMessageSet>>reformulateList (in category 'reformulation') -----
reformulateList
"Reformulate the message list of the receiver"
self initializeMessageList: (changeSet changedMessageListAugmented select:
[:each | each isValid])
!
MessageSet subclass: #MessageNames
instanceVariableNames: 'searchString selectorList selectorListIndex searchPane'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
----- Method: MessageNames class>>methodBrowserSearchingFor: (in category 'instance creation') -----
methodBrowserSearchingFor: searchString
"Answer an method-browser window whose search-string is initially as indicated"
| aWindow |
aWindow := self new searchString: searchString.
^ToolBuilder default build: aWindow!
----- Method: MessageNames class>>openMessageNames (in category 'instance creation') -----
openMessageNames
"Open a new instance of the receiver in the active world"
^ToolBuilder open: self new label: 'Message Names'
"MessageNames openMessageNames"
!
----- Method: MessageNames class>>prototypicalToolWindow (in category 'instance creation') -----
prototypicalToolWindow
"Answer an example of myself seen in a tool window, for the benefit of parts-launching tools"
^ self methodBrowserSearchingFor: nil!
----- Method: MessageNames class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Message Names' brightColor: #(0.645 1.0 0.452) pastelColor: #(0.843 0.976 0.843) helpMessage: 'A tool finding, viewing, and editing all methods whose names contiane a given character sequence.'!
----- Method: MessageNames>>buildSearchPaneWith: (in category 'toolbuilder') -----
buildSearchPaneWith: builder
| textSpec |
textSpec := builder pluggableInputFieldSpec new.
textSpec
model: self;
name: #search;
getText: #searchString;
setText: #searchString:notifying:.
^textSpec!
----- Method: MessageNames>>buildSelectorListWith: (in category 'toolbuilder') -----
buildSelectorListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #selectorList;
getIndex: #selectorListIndex;
setIndex: #selectorListIndex:;
menu: #selectorListMenu:;
keyPress: #selectorListKey:from:.
^listSpec
!
----- Method: MessageNames>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"ToolBuilder open: MessageNames new"
| windowSpec max buttonSpec result |
max := self wantsOptionalButtons ifTrue:[0.42] ifFalse:[0.5].
windowSpec := self buildWindowWith: builder specs: {
(0.15 at 0 corner: 0.5 at 0.08) -> [self buildSearchPaneWith: builder].
(0 at 0.08 corner: 0.5 at max) -> [self buildSelectorListWith: builder].
(0.5 at 0.0 corner: 1.0 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.
buttonSpec := builder pluggableActionButtonSpec new.
buttonSpec
model: self;
label: 'Search';
action: [self doSearchFrom: searchPane];
frame: (0.0 at 0 corner: 0.15 at 0.08).
windowSpec children add: buttonSpec.
result := builder build: windowSpec.
searchPane := builder widgetAt: #search.
^result!
----- Method: MessageNames>>computeSelectorListFromSearchString (in category 'search') -----
computeSelectorListFromSearchString
"Compute selector list from search string"
| raw sorted |
searchString := searchString asString copyWithout: $ .
selectorList := Cursor wait
showWhile: [raw := Symbol selectorsContaining: searchString.
sorted := raw as: SortedCollection.
sorted
sortBlock: [:x :y | x asLowercase <= y asLowercase].
sorted asArray].
selectorList size > 19
ifFalse: ["else the following filtering is considered too expensive. This 19
should be a system-maintained Parameter, someday"
selectorList := self systemNavigation allSelectorsWithAnyImplementorsIn: selectorList].
^ selectorList!
----- Method: MessageNames>>copyName (in category 'message list menu') -----
copyName
"Copy the current selector to the clipboard"
| selector |
(selector := self selectorList at: selectorListIndex ifAbsent: [nil]) ifNotNil:
[Clipboard clipboardText: selector asString asText]!
----- Method: MessageNames>>doSearchFrom: (in category 'search') -----
doSearchFrom: aPane
"The user hit the Search button -- treat it as a synonym for the user having hit the Return or Enter (or cmd-s) in the type-in pane"
aPane accept.
aPane selectAll!
----- Method: MessageNames>>messageList (in category 'selector list') -----
messageList
"Answer the receiver's message list, computing it if necessary. The way
to force a recomputation is to set the messageList to nil"
messageList
ifNil: [messageList := selectorListIndex == 0
ifTrue: [#()]
ifFalse: [self systemNavigation
allImplementorsOf: (selectorList at: selectorListIndex)].
self
messageListIndex: (messageList size > 0
ifTrue: [1]
ifFalse: [0])].
^ messageList!
----- Method: MessageNames>>searchString (in category 'search') -----
searchString
"Answer the current searchString, initializing it if need be"
| pane |
searchString isEmptyOrNil ifTrue:
[searchString := 'type here, then hit Search'.
pane := self containingWindow findDeepSubmorphThat:
[:m | m knownName = 'Search'] ifAbsent: ["this happens during window creation" ^ searchString].
pane setText: searchString.
pane setTextMorphToSelectAllOnMouseEnter.
pane selectAll].
^ searchString!
----- Method: MessageNames>>searchString: (in category 'search') -----
searchString: aString
"Set the current searchString"
self searchString: aString notifying: nil!
----- Method: MessageNames>>searchString:notifying: (in category 'search') -----
searchString: aString notifying: aController
"Take what the user typed and find all selectors containing it"
searchString := aString asString copyWithout: $ .
self containingWindow ifNotNil:[:w| w setLabel: 'Message names containing "', searchString asLowercase, '"'].
selectorList := nil.
self changed: #selectorList.
self changed: #messageList.
^ true!
----- Method: MessageNames>>selection (in category 'selection') -----
selection
"Answer the item in the list that is currently selected, or nil if no selection is present"
^ self messageList at: messageListIndex ifAbsent: [nil]!
----- Method: MessageNames>>selectorList (in category 'selector list') -----
selectorList
"Answer the selectorList"
selectorList ifNil:
[self computeSelectorListFromSearchString.
selectorListIndex := selectorList size > 0
ifTrue: [1]
ifFalse: [0].
messageList := nil].
^ selectorList!
----- Method: MessageNames>>selectorListIndex (in category 'selector list') -----
selectorListIndex
"Answer the selectorListIndex"
^ selectorListIndex!
----- Method: MessageNames>>selectorListIndex: (in category 'selector list') -----
selectorListIndex: anInteger
"Set the selectorListIndex as specified, and propagate consequences"
selectorListIndex := anInteger.
selectorListIndex = 0
ifTrue: [^ self].
messageList := nil.
self changed: #selectorListIndex.
self changed: #messageList!
----- Method: MessageNames>>selectorListKey:from: (in category 'initialization') -----
selectorListKey: aChar from: view
"Respond to a Command key in the message-list pane."
aChar == $n ifTrue: [^ self browseSenders].
aChar == $c ifTrue: [^ self copyName].
aChar == $b ifTrue: [^ self browseMethodFull].
!
----- Method: MessageNames>>selectorListMenu: (in category 'selector list') -----
selectorListMenu: aMenu
"Answer the menu associated with the selectorList"
aMenu addList: #(
('senders (n)' browseSenders 'browse senders of the chosen selector')
('copy selector to clipboard' copyName 'copy the chosen selector to the clipboard, for subsequent pasting elsewhere')
-
('show only implemented selectors' showOnlyImplementedSelectors 'remove from the selector-list all symbols that do not represent implemented methods')).
^ aMenu!
----- Method: MessageNames>>selectorListMenuTitle (in category 'selector list') -----
selectorListMenuTitle
"Answer the title to supply for the menu belonging to the selector-list pane"
^ 'Click on any item in the list
to see all implementors of it'!
----- Method: MessageNames>>showOnlyImplementedSelectors (in category 'search') -----
showOnlyImplementedSelectors
"Caution -- can be slow!! Filter my selector list down such that it only
shows selectors that are actually implemented somewhere in the system."
self okToChange
ifTrue: [Cursor wait
showWhile: [selectorList := self systemNavigation allSelectorsWithAnyImplementorsIn: selectorList.
self changed: #selectorList.
self changed: #messageList]]!
----- Method: MessageSet class>>extantMethodsIn: (in category 'utilities') -----
extantMethodsIn: aListOfMethodRefs
"Answer the subset of the incoming list consisting only of those message markers that refer to methods actually in the current image"
self flag: #mref. "may be removed in second round"
^ aListOfMethodRefs select: [:aToken |
self
parse: aToken
toClassAndSelector: [ :aClass :aSelector |
aClass notNil and: [aClass includesSelector: aSelector]
]
]!
----- Method: MessageSet class>>isPseudoSelector: (in category 'utilities') -----
isPseudoSelector: aSelector
"Answer whether the given selector is a special marker"
^ #(Comment Definition Hierarchy) includes: aSelector!
----- Method: MessageSet class>>messageList: (in category 'instance creation') -----
messageList: anArray
"Answer an instance of me with message list anArray."
^self new initializeMessageList: anArray!
----- Method: MessageSet class>>open:name: (in category 'instance creation') -----
open: aMessageSet name: aString
"Create a standard system view for the messageSet, aMessageSet, whose label is aString."
^ToolBuilder open: aMessageSet label: aString!
----- Method: MessageSet class>>openMessageList:name: (in category 'instance creation') -----
openMessageList: anArray name: aString
"Create a standard system view for the message set on the list, anArray.
The label of the view is aString."
self open: (self messageList: anArray) name: aString!
----- Method: MessageSet class>>openMessageList:name:autoSelect: (in category 'instance creation') -----
openMessageList: messageList name: labelString autoSelect: autoSelectString
"Open a system view for a MessageSet on messageList.
1/24/96 sw: the there-are-no msg now supplied by my sender"
| messageSet |
messageSet := self messageList: messageList.
messageSet autoSelectString: autoSelectString.
^ToolBuilder open: messageSet label: labelString!
----- Method: MessageSet class>>parse:toClassAndSelector: (in category 'utilities') -----
parse: methodRef toClassAndSelector: csBlock
"Decode strings of the form <className> [class] <selectorName>."
| tuple cl |
self flag: #mref. "compatibility with pre-MethodReference lists"
methodRef ifNil: [^ csBlock value: nil value: nil].
(methodRef isKindOf: MethodReference) ifTrue: [
^methodRef setClassAndSelectorIn: csBlock
].
methodRef isEmpty ifTrue: [^ csBlock value: nil value: nil].
tuple := methodRef asString findTokens: ' .'.
cl := Smalltalk at: tuple first asSymbol ifAbsent: [^ csBlock value: nil value: nil].
(tuple size = 2 or: [tuple size > 2 and: [(tuple at: 2) ~= 'class']])
ifTrue: [^ csBlock value: cl value: (tuple at: 2) asSymbol]
ifFalse: [^ csBlock value: cl class value: (tuple at: 3) asSymbol]!
----- Method: MessageSet class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Message List' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'A list of messages (e.g. senders, implementors)'!
----- Method: MessageSet>>aboutToStyle: (in category 'contents') -----
aboutToStyle: aStyler
"This is a notification that aStyler is about to re-style its text.
Set the classOrMetaClass in aStyler, so that identifiers
will be resolved correctly.
Answer true to allow styling to proceed, or false to veto the styling"
self isModeStyleable ifFalse: [^false].
aStyler classOrMetaClass: self selectedClassOrMetaClass.
^true!
----- Method: MessageSet>>addExtraShiftedItemsTo: (in category 'message list') -----
addExtraShiftedItemsTo: aMenu
"The shifted selector-list menu is being built. Add items specific to MessageSet"
self growable ifTrue:
[aMenu addList: #(
-
('remove from this browser' removeMessageFromBrowser)
('filter message list...' filterMessageList)
('add to message list...' augmentMessageList))].
aMenu add: 'sort by date' action: #sortByDate!
----- Method: MessageSet>>addItem: (in category 'message list') -----
addItem: classAndMethod
"Append a classAndMethod string to the list. Select the new item."
"Do some checks on the input?"
self okToChange ifFalse: [^ self].
messageList add: classAndMethod.
self changed: #messageList.
self messageListIndex: messageList size.!
----- Method: MessageSet>>adjustWindowTitleAfterFiltering (in category 'private') -----
adjustWindowTitleAfterFiltering
"Set the title of the receiver's window, if any, to reflect the just-completed filtering"
| aWindow existingLabel newLabel |
(aWindow := self containingWindow) ifNil: [^ self].
(existingLabel := aWindow label) isEmptyOrNil ifTrue: [^ self].
(((existingLabel size < 3) or: [existingLabel last ~~ $]]) or: [(existingLabel at: (existingLabel size - 1)) isDigit not]) ifTrue: [^ self].
existingLabel size to: 1 by: -1 do:
[:anIndex | ((existingLabel at: anIndex) == $[) ifTrue:
[newLabel := (existingLabel copyFrom: 1 to: anIndex),
'Filtered: ',
messageList size printString,
']'.
^ aWindow setLabel: newLabel]]
!
----- Method: MessageSet>>augmentMessageList (in category 'filtering') -----
augmentMessageList
"Allow the user to add to the list of messages."
self notYetImplemented
!
----- Method: MessageSet>>autoSelectString (in category 'private') -----
autoSelectString
"Return the string to be highlighted when making new selections"
^ autoSelectString!
----- Method: MessageSet>>autoSelectString: (in category 'private') -----
autoSelectString: aString
"Set the string to be highlighted when making new selections"
autoSelectString := aString!
----- Method: MessageSet>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
| windowSpec max result |
self wantsOptionalButtons ifTrue:[max := 0.3] ifFalse:[max := 0.3].
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 1 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.
result := builder build: windowSpec.
autoSelectString ifNotNil:[self changed: #autoSelect].
^result!
----- Method: MessageSet>>canShowMultipleMessageCategories (in category 'message category functions') -----
canShowMultipleMessageCategories
"Answer whether the receiver is capable of showing multiple message categories"
^ false!
----- Method: MessageSet>>classCommentIndicated (in category 'metaclass') -----
classCommentIndicated
"Answer true iff we're viewing the class comment."
^ editSelection == #editComment or: [ self selectedMessageName == #Comment ]!
----- Method: MessageSet>>contents (in category 'contents') -----
contents
"Answer the contents of the receiver"
^ contents == nil
ifTrue: [currentCompiledMethod := nil. '']
ifFalse: [messageListIndex = 0
ifTrue: [currentCompiledMethod := nil. contents]
ifFalse: [self showingByteCodes
ifTrue: [self selectedBytecodes]
ifFalse: [self selectedMessage]]]!
----- Method: MessageSet>>contents:notifying: (in category 'private') -----
contents: aString notifying: aController
"Compile the code in aString. Notify aController of any syntax errors.
Answer false if the compilation fails. Otherwise, if the compilation
created a new method, deselect the current selection. Then answer true."
| category selector class oldSelector |
self okayToAccept ifFalse: [^ false].
self setClassAndSelectorIn: [:c :os | class := c. oldSelector := os].
class ifNil: [^ false].
(oldSelector ~~ nil and: [oldSelector first isUppercase]) ifTrue:
[oldSelector = #Comment ifTrue:
[class comment: aString stamp: Utilities changeStamp.
self changed: #annotation.
self clearUserEditFlag.
^ false].
oldSelector = #Definition ifTrue:
["self defineClass: aString notifying: aController."
class subclassDefinerClass
evaluate: aString
notifying: aController
logged: true.
self clearUserEditFlag.
^ false].
oldSelector = #Hierarchy ifTrue:
[self inform: 'To change the hierarchy, edit the class definitions'.
^ false]].
"Normal method accept"
category := class organization categoryOfElement: oldSelector.
selector := class compile: aString
classified: category
notifying: aController.
selector == nil ifTrue: [^ false].
self noteAcceptanceOfCodeFor: selector.
selector == oldSelector ifFalse:
[self reformulateListNoting: selector].
contents := aString copy.
self changed: #annotation.
^ true!
----- Method: MessageSet>>deleteFromMessageList: (in category 'message functions') -----
deleteFromMessageList: aMessage
"Delete the given message from the receiver's message list"
messageList := messageList copyWithout: aMessage!
----- Method: MessageSet>>dragPassengerFor:inMorph: (in category 'drag and drop') -----
dragPassengerFor: item inMorph: dragSource
| transferType |
transferType := self dragTransferTypeForMorph: dragSource.
transferType == #messageList
ifTrue: [^self selectedClassOrMetaClass->(item contents findTokens: ' ') second asSymbol].
transferType == #classList
ifTrue: [^self selectedClass].
^nil!
----- Method: MessageSet>>filterFrom: (in category 'filtering') -----
filterFrom: aBlock
"Filter the receiver's list down to only those items that satisfy aBlock, which takes a class an a selector as its arguments."
| newList |
newList := messageList select:
[:anElement |
self class parse: anElement toClassAndSelector: [ :cls :sel |
(self class isPseudoSelector: sel) not and: [ aBlock value: cls value: sel ]]].
self setFilteredList: newList!
----- Method: MessageSet>>filterMessageList (in category 'filtering') -----
filterMessageList
"Allow the user to refine the list of messages."
| aMenu evt |
Smalltalk isMorphic ifFalse: [^ self inform: 'sorry, morphic only at this time.'].
messageList size <= 1 ifTrue: [^ self inform: 'this is not a propitious filtering situation'].
"would like to get the evt coming in but thwarted by the setInvokingView: circumlocution"
evt := self currentWorld activeHand lastEvent.
aMenu := MenuMorph new defaultTarget: self.
aMenu addTitle: 'Filter by only showing...'.
aMenu addStayUpItem.
aMenu addList: #(
('unsent messages' filterToUnsentMessages 'filter to show only messages that have no senders')
-
('messages that send...' filterToSendersOf 'filter to show only messages that send a selector I specify')
('messages that do not send...' filterToNotSendersOf 'filter to show only messages that do not send a selector I specify')
-
('messages whose selector is...' filterToImplementorsOf 'filter to show only messages with a given selector I specify')
('messages whose selector is NOT...' filterToNotImplementorsOf 'filter to show only messages whose selector is NOT a seletor I specify')
-
('messages in current change set' filterToCurrentChangeSet 'filter to show only messages that are in the current change set')
('messages not in current change set' filterToNotCurrentChangeSet 'filter to show only messages that are not in the current change set')
-
('messages in any change set' filterToAnyChangeSet 'filter to show only messages that occur in at least one change set')
('messages not in any change set' filterToNotAnyChangeSet 'filter to show only messages that do not occur in any change set in the system')
-
('messages authored by me' filterToCurrentAuthor 'filter to show only messages whose authoring stamp has my initials')
('messages not authored by me' filterToNotCurrentAuthor 'filter to show only messages whose authoring stamp does not have my initials')
-
('messages logged in .changes file' filterToMessagesInChangesFile 'filter to show only messages whose latest source code is logged in the .changes file')
('messages only in .sources file' filterToMessagesInSourcesFile 'filter to show only messages whose latest source code is logged in the .sources file')
-
('messages with prior versions' filterToMessagesWithPriorVersions 'filter to show only messages that have at least one prior version')
('messages without prior versions' filterToMessagesWithoutPriorVersions 'filter to show only messages that have no prior versions')
-
('uncommented messages' filterToUncommentedMethods 'filter to show only messages that do not have comments at the beginning')
('commented messages' filterToCommentedMethods 'fileter to show only messages that have comments at the beginning')
-
('messages in hardened classes' filterToMessagesWithHardenedClasses 'filter to show only messages of established classes (as opposed to Uniclasses such as Player23)')
-
('messages that...' filterToMessagesThat 'let me type in a block taking a class and a selector, which will specify yea or nay concerning which elements should remain in the list')
).
aMenu popUpEvent: evt hand lastEvent in: evt hand world.!
----- Method: MessageSet>>filterToAnyChangeSet (in category 'filtering') -----
filterToAnyChangeSet
"Filter down only to messages present in ANY change set"
self filterFrom:
[:aClass :aSelector |
ChangesOrganizer doesAnyChangeSetHaveClass: aClass andSelector: aSelector]
!
----- Method: MessageSet>>filterToCommentedMethods (in category 'filtering') -----
filterToCommentedMethods
"Filter the receiver's list down to only those items which have comments"
self filterFrom:
[:aClass :aSelector |
(aClass selectors includes: aSelector) and:
[(aClass firstPrecodeCommentFor: aSelector) isEmptyOrNil not]]!
----- Method: MessageSet>>filterToCurrentAuthor (in category 'filtering') -----
filterToCurrentAuthor
"Filter down only to messages with my initials as most recent author"
| myInitials aMethod aTimeStamp |
(myInitials := Utilities authorInitialsPerSe) ifNil: [^ self inform: 'No author initials set in this image'].
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[aMethod := aClass compiledMethodAt: aSelector ifAbsent: [nil].
aMethod notNil and:
[(aTimeStamp := Utilities timeStampForMethod: aMethod) notNil and:
[aTimeStamp beginsWith: myInitials]]]]!
----- Method: MessageSet>>filterToCurrentChangeSet (in category 'filtering') -----
filterToCurrentChangeSet
"Filter the receiver's list down to only those items in the current change set"
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[(ChangeSet current atSelector: aSelector class: aClass) ~~ #none]]!
----- Method: MessageSet>>filterToImplementorsOf (in category 'filtering') -----
filterToImplementorsOf
"Filter the receiver's list down to only those items with a given selector"
| aFragment inputWithBlanksTrimmed |
aFragment := UIManager default request: 'type selector:' initialAnswer: ''.
aFragment isEmptyOrNil ifTrue: [^ self].
inputWithBlanksTrimmed := aFragment withBlanksTrimmed.
Symbol hasInterned: inputWithBlanksTrimmed ifTrue:
[:aSymbol |
self filterFrom:
[:aClass :aSelector |
aSelector == aSymbol]]!
----- Method: MessageSet>>filterToMessagesInChangesFile (in category 'filtering') -----
filterToMessagesInChangesFile
"Filter down only to messages whose source code risides in the Changes file. This allows one to ignore long-standing methods that live in the .sources file."
| cm |
self filterFrom:
[:aClass :aSelector |
aClass notNil and: [aSelector notNil and:
[(self class isPseudoSelector: aSelector) not and:
[(cm := aClass compiledMethodAt: aSelector ifAbsent: [nil]) notNil and:
[cm fileIndex ~~ 1]]]]]!
----- Method: MessageSet>>filterToMessagesInSourcesFile (in category 'filtering') -----
filterToMessagesInSourcesFile
"Filter down only to messages whose source code resides in the .sources file."
| cm |
self filterFrom: [:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[(self class isPseudoSelector: aSelector) not and:
[(cm := aClass compiledMethodAt: aSelector ifAbsent: [nil]) notNil and:
[cm fileIndex == 1]]]]!
----- Method: MessageSet>>filterToMessagesThat (in category 'filtering') -----
filterToMessagesThat
"Allow the user to type in a block which will be"
| reply |
reply := UIManager default
multiLineRequest: 'Type your block here'
centerAt: Sensor cursorPoint
initialAnswer: '[:aClass :aSelector |
]'
answerHeight: 200.
reply isEmptyOrNil ifTrue: [^ self].
self filterFrom: (Compiler evaluate: reply)
!
----- Method: MessageSet>>filterToMessagesWithHardenedClasses (in category 'filtering') -----
filterToMessagesWithHardenedClasses
"Filter the receiver's list down to only those items representing methods of hardened classes, as opposed to uniclasses"
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[aClass isUniClass not]]!
----- Method: MessageSet>>filterToMessagesWithPriorVersions (in category 'filtering') -----
filterToMessagesWithPriorVersions
"Filter down only to messages which have at least one prior version"
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[(self class isPseudoSelector: aSelector) not and:
[(VersionsBrowser versionCountForSelector: aSelector class: aClass) > 1]]]!
----- Method: MessageSet>>filterToMessagesWithoutPriorVersions (in category 'filtering') -----
filterToMessagesWithoutPriorVersions
"Filter down only to messages which have no prior version stored"
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[(self class isPseudoSelector: aSelector) not and:
[(VersionsBrowser versionCountForSelector: aSelector class: aClass) <= 1]]]!
----- Method: MessageSet>>filterToNotAnyChangeSet (in category 'filtering') -----
filterToNotAnyChangeSet
"Filter down only to messages present in NO change set"
self filterFrom:
[:aClass :aSelector |
(ChangesOrganizer doesAnyChangeSetHaveClass: aClass andSelector: aSelector) not]
!
----- Method: MessageSet>>filterToNotCurrentAuthor (in category 'filtering') -----
filterToNotCurrentAuthor
"Filter down only to messages not stamped with my initials"
| myInitials aMethod aTimeStamp |
(myInitials := Utilities authorInitialsPerSe) ifNil: [^ self inform: 'No author initials set in this image'].
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[aMethod := aClass compiledMethodAt: aSelector ifAbsent: [nil].
aMethod notNil and:
[(aTimeStamp := Utilities timeStampForMethod: aMethod) isNil or:
[(aTimeStamp beginsWith: myInitials) not]]]]!
----- Method: MessageSet>>filterToNotCurrentChangeSet (in category 'filtering') -----
filterToNotCurrentChangeSet
"Filter the receiver's list down to only those items not in the current change set"
self filterFrom:
[:aClass :aSelector |
(aClass notNil and: [aSelector notNil]) and:
[(ChangeSet current atSelector: aSelector class: aClass) == #none]]!
----- Method: MessageSet>>filterToNotImplementorsOf (in category 'filtering') -----
filterToNotImplementorsOf
"Filter the receiver's list down to only those items whose selector is NOT one solicited from the user."
| aFragment inputWithBlanksTrimmed |
aFragment := UIManager default request: 'type selector: ' initialAnswer: ''.
aFragment isEmptyOrNil ifTrue: [^ self].
inputWithBlanksTrimmed := aFragment withBlanksTrimmed.
Symbol hasInterned: inputWithBlanksTrimmed ifTrue:
[:aSymbol |
self filterFrom:
[:aClass :aSelector |
aSelector ~~ aSymbol]]!
----- Method: MessageSet>>filterToNotSendersOf (in category 'filtering') -----
filterToNotSendersOf
"Filter the receiver's list down to only those items which do not send a given selector"
| aFragment inputWithBlanksTrimmed aMethod |
aFragment := UIManager default request: 'type selector:' initialAnswer: ''.
aFragment isEmptyOrNil ifTrue: [^ self].
inputWithBlanksTrimmed := aFragment withBlanksTrimmed.
Symbol hasInterned: inputWithBlanksTrimmed ifTrue:
[:aSymbol |
self filterFrom:
[:aClass :aSelector |
(aMethod := aClass compiledMethodAt: aSelector) isNil or:
[(aMethod hasLiteralThorough: aSymbol) not]]]!
----- Method: MessageSet>>filterToSendersOf (in category 'filtering') -----
filterToSendersOf
"Filter the receiver's list down to only those items which send a given selector"
| aFragment inputWithBlanksTrimmed aMethod |
aFragment := UIManager default request: 'type selector:' initialAnswer: ''.
aFragment isEmptyOrNil ifTrue: [^ self].
inputWithBlanksTrimmed := aFragment withBlanksTrimmed.
Symbol hasInterned: inputWithBlanksTrimmed ifTrue:
[:aSymbol |
self filterFrom:
[:aClass :aSelector |
(aMethod := aClass compiledMethodAt: aSelector) notNil and:
[aMethod hasLiteralThorough: aSymbol]]]
!
----- Method: MessageSet>>filterToUncommentedMethods (in category 'filtering') -----
filterToUncommentedMethods
"Filter the receiver's list down to only those items which lack comments"
self filterFrom:
[:aClass :aSelector |
(aClass selectors includes: aSelector) and:
[(aClass firstPrecodeCommentFor: aSelector) isEmptyOrNil]]!
----- Method: MessageSet>>filterToUnsentMessages (in category 'filtering') -----
filterToUnsentMessages
"Filter the receiver's list down to only those items which have no
senders"
self
filterFrom: [:aClass :aSelector | (self systemNavigation allCallsOn: aSelector) isEmpty]!
----- Method: MessageSet>>growable (in category 'message list') -----
growable
"Answer whether the receiver is capable of growing/shrinking dynamically"
^ growable ~~ false!
----- Method: MessageSet>>growable: (in category 'message list') -----
growable: aBoolean
"Give or take away the growable trait; when a message set is growable, methods submitted within it will be added to its message list"
growable := aBoolean!
----- Method: MessageSet>>initializeMessageList: (in category 'private') -----
initializeMessageList: anArray
"Initialize my messageList from the given list of MethodReference or string objects. NB: special handling for uniclasses."
| s |
messageList := OrderedCollection new.
anArray do: [ :each |
MessageSet
parse: each
toClassAndSelector: [ :class :sel |
class ifNotNil:
[class isUniClass
ifTrue:
[s := class typicalInstanceName, ' ', sel]
ifFalse:
[s := class name , ' ' , sel , ' {' ,
((class organization categoryOfElement: sel) ifNil: ['']) , '}'].
messageList add: (
MethodReference new
setClass: class
methodSymbol: sel
stringVersion: s)]]].
messageListIndex := messageList isEmpty ifTrue: [0] ifFalse: [1].
contents := ''!
----- Method: MessageSet>>messageList (in category 'message list') -----
messageList
"Answer the current list of messages."
^messageList!
----- Method: MessageSet>>messageListIndex: (in category 'message list') -----
messageListIndex: anInteger
"Set the index of the selected item to be anInteger."
messageListIndex := anInteger.
contents :=
messageListIndex ~= 0
ifTrue: [self selectedMessage]
ifFalse: [''].
self changed: #messageListIndex. "update my selection"
self editSelection: #editMessage.
self contentsChanged.
(messageListIndex ~= 0 and: [autoSelectString notNil])
ifTrue: [self changed: #autoSelect].
self decorateButtons
!
----- Method: MessageSet>>metaClassIndicated (in category 'class list') -----
metaClassIndicated
"Answer the boolean flag that indicates whether
this is a class method."
^ self selectedClassOrMetaClass isMeta!
----- Method: MessageSet>>methodCategoryChanged (in category 'message functions') -----
methodCategoryChanged
self changed: #annotation!
----- Method: MessageSet>>optionalButtonHeight (in category 'message list') -----
optionalButtonHeight
^ 15!
----- Method: MessageSet>>reformulateList (in category 'message functions') -----
reformulateList
"The receiver's messageList has been changed; rebuild it"
super reformulateList.
self initializeMessageList: messageList.
self changed: #messageList.
self changed: #messageListIndex.
self contentsChanged
!
----- Method: MessageSet>>removeMessage (in category 'message functions') -----
removeMessage
"Remove the selected message from the system. 1/15/96 sw"
| messageName confirmation |
messageListIndex = 0
ifTrue: [^ self].
self okToChange
ifFalse: [^ self].
messageName := self selectedMessageName.
confirmation := self systemNavigation confirmRemovalOf: messageName on: self selectedClassOrMetaClass.
confirmation == 3
ifTrue: [^ self].
self selectedClassOrMetaClass removeSelector: messageName.
self deleteFromMessageList: self selection.
self reformulateList.
confirmation == 2
ifTrue: [self systemNavigation browseAllCallsOn: messageName]!
----- Method: MessageSet>>removeMessageFromBrowser (in category 'message functions') -----
removeMessageFromBrowser
"Remove the selected message from the browser."
messageListIndex = 0 ifTrue: [^ self].
self deleteFromMessageList: self selection.
self reformulateList.
self adjustWindowTitleAfterFiltering
!
----- Method: MessageSet>>selectedClass (in category 'class list') -----
selectedClass
"Return the base class for the current selection. 1/17/96 sw fixed up so that it doesn't fall into a debugger in a msg browser that has no message selected"
| aClass |
^ (aClass := self selectedClassOrMetaClass) == nil
ifTrue:
[nil]
ifFalse:
[aClass theNonMetaClass]!
----- Method: MessageSet>>selectedClassName (in category 'class list') -----
selectedClassName
"Answer the name of class of the currently selected message. Answer nil if no selection
exists."
| cls |
(cls := self selectedClass) ifNil: [^ nil].
^ cls name!
----- Method: MessageSet>>selectedClassOrMetaClass (in category 'class list') -----
selectedClassOrMetaClass
"Answer the currently selected class (or metaclass)."
messageListIndex = 0 ifTrue: [^nil].
self setClassAndSelectorIn: [:c :s | ^c]!
----- Method: MessageSet>>selectedMessage (in category 'contents') -----
selectedMessage
"Answer the source method for the currently selected message."
| source |
self setClassAndSelectorIn: [:class :selector |
class ifNil: [^ 'Class vanished'].
selector first isUppercase ifTrue:
[selector == #Comment ifTrue:
[currentCompiledMethod := class organization commentRemoteStr.
^ class comment].
selector == #Definition ifTrue:
[^ class definitionST80].
selector == #Hierarchy ifTrue: [^ class printHierarchy]].
source := class sourceMethodAt: selector ifAbsent:
[currentCompiledMethod := nil.
^ 'Missing'].
self showingDecompile ifTrue: [^ self decompiledSourceIntoContents].
currentCompiledMethod := class compiledMethodAt: selector ifAbsent: [nil].
self showingDocumentation ifTrue: [^ self commentContents].
source := self sourceStringPrettifiedAndDiffed.
^ source asText makeSelectorBoldIn: class]!
----- Method: MessageSet>>selectedMessageCategoryName (in category 'class list') -----
selectedMessageCategoryName
"Answer the name of the selected message category or nil."
messageListIndex = 0 ifTrue: [^ nil].
^ self selectedClassOrMetaClass organization categoryOfElement: self selectedMessageName!
----- Method: MessageSet>>selectedMessageName (in category 'message list') -----
selectedMessageName
"Answer the name of the currently selected message."
"wod 6/16/1998: answer nil if none are selected."
messageListIndex = 0 ifTrue: [^ nil].
^ self setClassAndSelectorIn: [:class :selector | ^ selector]!
----- Method: MessageSet>>selection (in category 'private') -----
selection
"Answer the item in the list that is currently selected, or nil if no selection is present"
^ messageList at: messageListIndex ifAbsent: [nil]!
----- Method: MessageSet>>setClassAndSelectorIn: (in category 'private') -----
setClassAndSelectorIn: csBlock
| sel |
"Decode strings of the form <className> [class] <selectorName>."
self flag: #mref. "compatibility with pre-MethodReference lists"
sel := self selection.
^(sel isKindOf: MethodReference) ifTrue: [
sel setClassAndSelectorIn: csBlock
] ifFalse: [
MessageSet parse: sel toClassAndSelector: csBlock
]!
----- Method: MessageSet>>setContentsToForceRefetch (in category 'contents') -----
setContentsToForceRefetch
"Set the receiver's contents such that on the next update the contents will be formulated afresh. This is a critical and obscure difference between Browsers on the one hand and MessageSets on the other, and has over the years been the source of much confusion and much difficulty. By centralizing the different handling here, we don't need so many idiosyncratic overrides in MessageSet any more"
contents := ''!
----- Method: MessageSet>>setFilteredList: (in category 'private') -----
setFilteredList: newList
"Establish newList as the new list if appropriate, and adjust the window title accordingly; if the new list is of the same size as the old, warn and do nothing"
newList size == 0
ifTrue:
[^ self inform: 'Nothing would be left in the list if you did that'].
newList size == messageList size
ifTrue:
[^ self inform: 'That leaves the list unchanged'].
self initializeMessageList: newList.
self adjustWindowTitleAfterFiltering!
----- Method: MessageSet>>sortByDate (in category 'message list') -----
sortByDate
"Sort the message-list by date of time-stamp"
| assocs aCompiledMethod aDate inOrder |
assocs := messageList collect:
[:aRef |
aDate := aRef methodSymbol == #Comment
ifTrue:
[aRef actualClass organization dateCommentLastSubmitted]
ifFalse:
[aCompiledMethod := aRef actualClass compiledMethodAt: aRef methodSymbol ifAbsent: [nil].
aCompiledMethod ifNotNil: [aCompiledMethod dateMethodLastSubmitted]].
aRef -> (aDate ifNil: [Date fromString: '01/01/1996'])]. "The dawn of Squeak history"
inOrder := assocs asSortedCollection:
[:a :b | a value < b value].
messageList := inOrder asArray collect: [:assoc | assoc key].
self changed: #messageList!
MessageSet subclass: #ProtocolBrowser
instanceVariableNames: 'selectedClass selectedSelector'
classVariableNames: 'TextMenu'
poolDictionaries: ''
category: 'Tools-Browser'!
!ProtocolBrowser commentStamp: '<historical>' prior: 0!
An instance of ProtocolBrowser shows the methods a class understands--inherited or implemented at this level--as a "flattened" list.!
ProtocolBrowser subclass: #Lexicon
instanceVariableNames: 'currentVocabulary categoryList categoryListIndex targetClass limitClass currentQuery currentQueryParameter selectorsVisited'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Protocols'!
!Lexicon commentStamp: '<historical>' prior: 0!
An instance of Lexicon shows the a list of all the method categories known to an object or any of its superclasses, as a "flattened" list, and, within any selected category, shows all methods understood by the class's instances which are associated with that category, again as a "flattened" list. A variant with a search pane rather than a category list is also implemented.
categoryList the list of categories
categoryListIndex index of currently-selected category
targetObject optional -- an instance being viewed
targetClass the class being viewed
lastSearchString the last string searched for
lastSendersSearchSelector the last senders search selector
limitClass optional -- the limit class to search for
selectorsVisited list of selectors visited
selectorsActive not presently in use, subsumed by selectorsVisited
currentVocabulary the vocabulary currently installed
currentQuery what the query category relates to:
#senders #selectorName #currentChangeSet!
Lexicon subclass: #InstanceBrowser
instanceVariableNames: 'objectViewed'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Protocols'!
----- Method: InstanceBrowser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Instance Browser' brightColor: #(0.806 1.0 1.0) pastelColor: #(0.925 1.000 1.0) helpMessage: 'A tool for browsing the full protocol of an instance.'!
----- Method: InstanceBrowser>>inspectViewee (in category 'menu commands') -----
inspectViewee
"Open an Inspector on the object I view"
objectViewed inspect!
----- Method: InstanceBrowser>>offerMenu (in category 'menu commands') -----
offerMenu
"Offer a menu to the user, in response to the hitting of the menu button on the tool pane"
| aMenu |
aMenu := MenuMorph new defaultTarget: self.
aMenu title: 'Messages of ', objectViewed nameForViewer.
aMenu addStayUpItem.
aMenu addList: #(
('vocabulary...' chooseVocabulary)
('what to show...' offerWhatToShowMenu)
-
('inst var refs (here)' setLocalInstVarRefs)
('inst var defs (here)' setLocalInstVarDefs)
('class var refs (here)' setLocalClassVarRefs)
-
('navigate to a sender...' navigateToASender)
('recent...' navigateToRecentMethod)
('show methods in current change set'
showMethodsInCurrentChangeSet)
('show methods with initials...'
showMethodsWithInitials)
-
"('toggle search pane' toggleSearch)"
-
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' classHierarchy)
('browse method (O)' openSingleMessageBrowser)
('browse protocol (p)' browseFullProtocol)
-
('fileOut' fileOutMessage)
('printOut' printOutMessage)
-
('senders of... (n)' browseSendersOfMessages)
('implementors of... (m)' browseMessages)
('versions (v)' browseVersions)
('inheritance (i)' methodHierarchy)
-
('inst var refs' browseInstVarRefs)
('inst var defs' browseInstVarDefs)
('class var refs' browseClassVarRefs)
-
('viewer on me' viewViewee)
('inspector on me' inspectViewee)
-
('more...' shiftedYellowButtonActivity)).
aMenu popUpInWorld: ActiveWorld!
----- Method: InstanceBrowser>>openOnObject:inWorld:showingSelector: (in category 'initialization') -----
openOnObject: anObject inWorld: ignored showingSelector: aSelector
"Create and open a SystemWindow to house the receiver, showing the categories pane."
^self openOnObject: anObject showingSelector: aSelector!
----- Method: InstanceBrowser>>openOnObject:showingSelector: (in category 'initialization') -----
openOnObject: anObject showingSelector: aSelector
"Create and open a SystemWindow to house the receiver, showing the categories pane."
objectViewed := anObject.
self openOnClass: anObject class showingSelector: aSelector!
----- Method: InstanceBrowser>>startingWindowTitle (in category 'window title') -----
startingWindowTitle
"Answer the initial window title to apply"
^ 'Vocabulary of ', objectViewed nameForViewer!
----- Method: InstanceBrowser>>targetObject (in category 'target-object access') -----
targetObject
"Answer the object to which this tool is bound"
^ objectViewed!
----- Method: InstanceBrowser>>viewViewee (in category 'menu commands') -----
viewViewee
"Open a viewer on the object I view"
objectViewed beViewed!
----- Method: Lexicon class>>activeCategoryName (in category 'visible category names') -----
activeCategoryName
"Answer the name to be used for the active-methods category"
true ifTrue: [^ #'-- current working set --'].
'-- current working set --' asSymbol "Placed here so a message-strings-containing-it query will find this method"
!
----- Method: Lexicon class>>allCategoryName (in category 'visible category names') -----
allCategoryName
"Answer the name to be used for the all category"
true ifTrue: [^ #'-- all --'].
'-- all --' asSymbol "Placed here so a message-strings-containing-it query will find this method"
!
----- Method: Lexicon class>>queryCategoryName (in category 'visible category names') -----
queryCategoryName
"Answer the name to be used for the query-results category"
true ifTrue: [^ #'-- query results --'].
^ '-- query results --' asSymbol "Placed here so a message-strings-containing-it query will find this method"!
----- Method: Lexicon class>>sendersCategoryName (in category 'visible category names') -----
sendersCategoryName
"Answer the name to be used for the senders-results category"
true ifTrue: [^ #'-- "senders" results --'].
^ '-- "senders" results --'. "so methods-strings-containing will find this"!
----- Method: Lexicon class>>viewedCategoryName (in category 'visible category names') -----
viewedCategoryName
"Answer the name to be used for the previously-viewed-methods category"
true ifTrue: [^ #'-- active --'].
^ '-- active --' asSymbol "For benefit of method-strings-containing-it search"
!
----- Method: Lexicon class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Lexicon' brightColor: #(0.878 1.000 0.878) pastelColor: #(0.925 1.000 0.925) helpMessage: 'A tool for browsing the full protocol of a class.'!
----- Method: Lexicon>>addModelItemsToWindowMenu: (in category 'window title') -----
addModelItemsToWindowMenu: aMenu
"Add model-related item to the window menu"
super addModelItemsToWindowMenu: aMenu.
aMenu add: 'choose vocabulary...' target: self action: #chooseVocabulary!
----- Method: Lexicon>>addSpecialButtonsTo:with: (in category 'toolbuilder') -----
addSpecialButtonsTo: buttonPanelSpec with: builder
| homeCatBtnSpec menuBtnSpec mostGenericBtnSpec |
homeCatBtnSpec := builder pluggableButtonSpec new
model: self;
action: #showHomeCategory;
label: (ScriptingSystem formAtKey: #Cat) asMorph;
help: 'show this method''s home category';
yourself.
menuBtnSpec := builder pluggableButtonSpec new
model: self;
action: #offerMenu;
label: (ScriptingSystem formAtKey: #TinyMenu) asMorph;
help: 'click here to get a menu with further options';
yourself.
mostGenericBtnSpec :=builder pluggableButtonSpec new
model: self;
action: #chooseLimitClass;
label: #limitClassString;
help: 'Governs which classes'' methods should be shown. If this is the same as the viewed class, then only methods implemented in that class will be shown. If it is ProtoObject, then methods of all classes in the vocabulary will be shown.'.
buttonPanelSpec children
add: homeCatBtnSpec;
addFirst: mostGenericBtnSpec;
addFirst: menuBtnSpec.!
----- Method: Lexicon>>adjustWindowTitle (in category 'window title') -----
adjustWindowTitle
"Set the title of the receiver's window, if any, to reflect the current choices"
| aWindow aLabel catName |
(catName := self selectedCategoryName) ifNil: [^ self].
(aWindow := self containingWindow) ifNil: [^ self].
aLabel := nil.
#( (viewedCategoryName 'Messages already viewed - ')
(allCategoryName 'All messages - ')) do:
[:aPair | catName = (self categoryWithNameSpecifiedBy: aPair first) ifTrue: [aLabel := aPair second]].
aLabel ifNil:
[aLabel := catName = self class queryCategoryName
ifTrue:
[self queryCharacterization, ' - ']
ifFalse:
['Vocabulary of ']].
aWindow setLabel: aLabel, (self targetObject ifNil: [targetClass]) nameForViewer!
----- Method: Lexicon>>annotation (in category 'basic operation') -----
annotation
"Provide a line of annotation material for a middle pane."
| aCategoryName |
self selectedMessageName ifNotNil: [^ super annotation].
(aCategoryName := self selectedCategoryName) ifNil:
[^ self hasSearchPane
ifTrue:
['type a message name or fragment in the top pane and hit RETURN or ENTER']
ifFalse:
['' "currentVocabulary documentation"]].
(aCategoryName = self class queryCategoryName) ifTrue:
[^ self queryCharacterization].
#(
(allCategoryName 'Shows all methods, whatever other category they belong to')
(viewedCategoryName 'Methods visited recently. Use "-" button to remove a method from this category.')
(queryCategoryName 'Query results'))
do:
[:pair | (self categoryWithNameSpecifiedBy: pair first) = aCategoryName ifTrue: [^ pair second]].
^ currentVocabulary categoryCommentFor: aCategoryName!
----- Method: Lexicon>>browseClassVarRefs (in category 'new-window queries') -----
browseClassVarRefs
"Let the search pertain to the target class regardless of selection"
self systemNavigation browseClassVarRefs: targetClass theNonMetaClass !
----- Method: Lexicon>>browseInstVarDefs (in category 'new-window queries') -----
browseInstVarDefs
"Let the search pertain to the target class regardless of selection"
self systemNavigation browseInstVarDefs: targetClass!
----- Method: Lexicon>>browseInstVarRefs (in category 'new-window queries') -----
browseInstVarRefs
"Let the search pertain to the target class regardless of selection"
self systemNavigation browseInstVarRefs: targetClass!
----- Method: Lexicon>>buildCategoryListWith: (in category 'toolbuilder') -----
buildCategoryListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #categoryList;
getIndex: #categoryListIndex;
setIndex: #categoryListIndex:;
menu: #categoryListMenu:shifted:;
keyPress: #categoryListKey:from:.
^listSpec!
----- Method: Lexicon>>buildCodePaneWith: (in category 'toolbuilder') -----
buildCodePaneWith: builder
| spec standardButtonPanel codePane customPanelSpec |
spec := super buildCodePaneWith: builder.
standardButtonPanel := spec children
detect: [:ea | ea isKindOf: PluggablePanelSpec]
ifNone: [^ spec]. "do nothing if optionalButtons not enabled"
customPanelSpec := self buildCustomButtonsWith: builder.
customPanelSpec frame: (0 at 0.12 corner: 1 at 0.24).
spec children add: customPanelSpec after: standardButtonPanel.
"resize code pane so that new panel fits in"
codePane := spec children detect: [:ea | ea isKindOf: PluggableCodePaneSpec].
codePane frame: (codePane frame withTop: 0.24).
^ spec.!
----- Method: Lexicon>>buildCustomButtonsWith: (in category 'toolbuilder') -----
buildCustomButtonsWith: builder
"This method if very similar to StringHolder>>buildOptionalButtonsWith:.
Refactor and pass in button specs?"
| panelSpec buttonSpec |
panelSpec := builder pluggablePanelSpec new.
panelSpec children: OrderedCollection new.
self customButtonSpecs do: [:spec |
buttonSpec := builder pluggableActionButtonSpec new.
buttonSpec model: self.
buttonSpec label: spec first.
buttonSpec action: spec second.
spec size > 2 ifTrue: [buttonSpec help: spec third].
panelSpec children add: buttonSpec.
].
panelSpec layout: #horizontal. "buttons"
self addSpecialButtonsTo: panelSpec with: builder.
^panelSpec!
----- Method: Lexicon>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"Create the ui for the browser"
| windowSpec max |
max := self wantsOptionalButtons ifTrue:[0.32] ifFalse:[0.4].
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 0.5 at max) -> [self buildCategoryListWith: builder].
(0.5 at 0 corner: 1 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.
^builder build: windowSpec!
----- Method: Lexicon>>canShowMultipleMessageCategories (in category 'message category functions') -----
canShowMultipleMessageCategories
"Answer whether the receiver is capable of showing multiple message categories"
^ true!
----- Method: Lexicon>>categoriesPane (in category 'category list') -----
categoriesPane
"If there is a pane defined by #categoryList in my containing window, answer it, else answer nil"
^ self listPaneWithSelector: #categoryList!
----- Method: Lexicon>>categoryDefiningSelector: (in category 'category list') -----
categoryDefiningSelector: aSelector
"Answer a category in which aSelector occurs"
| categoryNames |
categoryNames := categoryList copyWithoutAll: #('-- all --').
^ currentVocabulary categoryWithNameIn: categoryNames thatIncludesSelector: aSelector forInstance: self targetObject ofClass: targetClass!
----- Method: Lexicon>>categoryList (in category 'category list') -----
categoryList
"Answer the category list for the protcol, creating it if necessary, and prepending the -- all -- category, and appending the other special categories for search results, etc."
| specialCategoryNames |
categoryList ifNil:
[specialCategoryNames := #(queryCategoryName viewedCategoryName "searchCategoryName sendersCategoryName changedCategoryName activeCategoryName") collect:
[:sym | self class perform: sym].
categoryList :=
(currentVocabulary categoryListForInstance: self targetObject ofClass: targetClass limitClass: limitClass),
specialCategoryNames,
(Array with: self class allCategoryName)].
^ categoryList!
----- Method: Lexicon>>categoryListIndex (in category 'category list') -----
categoryListIndex
"Answer the index of the currently-selected item in in the category list"
^ categoryListIndex ifNil: [categoryListIndex := 1]!
----- Method: Lexicon>>categoryListIndex: (in category 'category list') -----
categoryListIndex: anIndex
"Set the category list index as indicated"
| categoryName aList found existingSelector |
existingSelector := self selectedMessageName.
categoryListIndex := anIndex.
anIndex > 0
ifTrue:
[categoryName := categoryList at: anIndex]
ifFalse:
[contents := nil].
self changed: #categoryListIndex.
found := false.
#( (viewedCategoryName selectorsVisited)
(queryCategoryName selectorsRetrieved)) do:
[:pair |
categoryName = (self class perform: pair first)
ifTrue:
[aList := self perform: pair second.
found := true]].
found ifFalse:
[aList := currentVocabulary allMethodsInCategory: categoryName forInstance: self targetObject ofClass: targetClass].
categoryName = self class queryCategoryName ifFalse: [autoSelectString := nil].
self initListFrom: aList highlighting: targetClass.
messageListIndex := 0.
self changed: #messageList.
contents := nil.
self contentsChanged.
self selectWithinCurrentCategoryIfPossible: existingSelector.
self adjustWindowTitle!
----- Method: Lexicon>>categoryListKey:from: (in category 'category list') -----
categoryListKey: aChar from: aView
"The user hit a command-key while in the category-list. Do something"
(aChar == $f and: [self hasSearchPane not]) ifTrue:
[^ self obtainNewSearchString].!
----- Method: Lexicon>>categoryListMenu:shifted: (in category 'category list') -----
categoryListMenu: aMenu shifted: aBoolean
"Answer the menu for the category list"
^ aMenu labels: 'find...(f)' lines: #() selections: #(obtainNewSearchString)!
----- Method: Lexicon>>categoryListMenuTitle (in category 'category list') -----
categoryListMenuTitle
"Answer the menu title for the category list menu"
^ 'categories'!
----- Method: Lexicon>>categoryOfSelector: (in category 'selection') -----
categoryOfSelector: aSelector
"Answer the name of the defining category for aSelector, or nil if none"
| classDefiningSelector |
classDefiningSelector := targetClass whichClassIncludesSelector: aSelector.
classDefiningSelector
ifNil: [^ nil].
"can happen for example if one issues this from a change-sorter for a
message that is recorded as having been removed"
^ classDefiningSelector whichCategoryIncludesSelector: aSelector!
----- Method: Lexicon>>categoryWithNameSpecifiedBy: (in category 'category list') -----
categoryWithNameSpecifiedBy: aSelector
"Answer the category name obtained by sending aSelector to my class. This provides a way to avoid hard-coding the wording of conventions such as '-- all --'"
^ self class perform: aSelector!
----- Method: Lexicon>>chooseCategory: (in category 'category list') -----
chooseCategory: aCategory
"Choose the category of the given name, if there is one"
self categoryListIndex: (categoryList indexOf: aCategory ifAbsent: [^ Beeper beep])!
----- Method: Lexicon>>chooseLimitClass (in category 'limit class') -----
chooseLimitClass
"Put up a menu allowing the user to choose the most generic class to show"
| aMenu |
aMenu := MenuMorph new defaultTarget: self.
targetClass withAllSuperclasses do:
[:aClass |
aClass == ProtoObject
ifTrue:
[aMenu addLine].
aMenu add: aClass name selector: #setLimitClass: argument: aClass.
aClass == limitClass ifTrue:
[aMenu lastItem color: Color red].
aClass == targetClass ifTrue: [aMenu addLine]].
aMenu addTitle: 'Show only methods
implemented at or above...'. "heh heh -- somebody please find nice wording here!!"
aMenu popUpInWorld: self currentWorld!
----- Method: Lexicon>>chooseVocabulary (in category 'vocabulary') -----
chooseVocabulary
"Put up a dialog affording the user a chance to choose a different vocabulary to be installed in the receiver"
| aMenu |
aMenu := MenuMorph new defaultTarget: self.
aMenu addTitle: 'Choose a vocabulary
blue = current
red = imperfect' translated.
aMenu addStayUpItem.
Vocabulary allStandardVocabularies do:
[:aVocabulary |
(targetClass implementsVocabulary: aVocabulary)
ifTrue:
[aMenu add: aVocabulary vocabularyName selector: #switchToVocabulary: argument: aVocabulary.
(targetClass fullyImplementsVocabulary: aVocabulary) ifFalse:
[aMenu lastItem color: Color red].
aVocabulary == currentVocabulary ifTrue:
[aMenu lastItem color: Color blue].
aMenu balloonTextForLastItem: aVocabulary documentation]].
aMenu popUpInWorld: self currentWorld!
----- Method: Lexicon>>contents (in category 'contents') -----
contents
"We have a class, allow new messages to be defined"
editSelection == #newMessage ifTrue: [^ targetClass sourceCodeTemplate].
^ super contents!
----- Method: Lexicon>>currentQueryParameter (in category 'within-tool queries') -----
currentQueryParameter
"Answer the current query parameter"
^ currentQueryParameter ifNil: [currentQueryParameter := 'contents']!
----- Method: Lexicon>>customButtonSpecs (in category 'control buttons') -----
customButtonSpecs
"Answer a triplet defining buttons, in the format:
button label
selector to send
help message"
| aa |
aa := contentsSymbol == #tiles ifTrue: [{ "Consult Ted Kaehler regarding this bit"
{'tiles'. #tilesMenu. 'tiles for assignment and constants'. true}.
{'vars'. #varTilesMenu. 'tiles for instance variables and a new temporary'. true}
}] ifFalse: [#()]. "true in 4th place means act on mouseDown"
^ aa, #(
('follow' seeAlso 'view a method I implement that is called by this method')
('find' obtainNewSearchString 'find methods by name search')
('sent...' setSendersSearch 'view the methods I implement that send a given message')
('<' navigateToPreviousMethod 'view the previous active method')
('>' navigateToNextMethod 'view the next active method')
('-' removeFromSelectorsVisited 'remove this method from my active list'))!
----- Method: Lexicon>>displaySelector: (in category 'basic operation') -----
displaySelector: aSelector
"Set aSelector to be the one whose source shows in the browser. If there is a category list, make it highlight a suitable category"
| detectedItem messageIndex |
self chooseCategory: (self categoryDefiningSelector: aSelector).
detectedItem := messageList detect:
[:anItem | (anItem asString upTo: $ ) asSymbol == aSelector] ifNone: [^ Beeper beep].
messageIndex := messageList indexOf: detectedItem.
self messageListIndex: messageIndex!
----- Method: Lexicon>>doItReceiver (in category 'model glue') -----
doItReceiver
"This class's classPool has been jimmied to be the classPool of the class being browsed. A doIt in the code pane will let the user see the value of the class variables. Here, if the receiver is affiliated with a specific instance, we give give that primacy"
^ self targetObject ifNil: [self selectedClass ifNil: [FakeClassPool new]]!
----- Method: Lexicon>>hasSearchPane (in category 'search') -----
hasSearchPane
"Answer whether receiver has a search pane"
^ self searchPane notNil!
----- Method: Lexicon>>initListFrom:highlighting: (in category 'initialization') -----
initListFrom: selectorCollection highlighting: aClass
"Make up the messageList with items from aClass in boldface. Provide a final filtering in that only selectors whose implementations fall within my limitClass will be shown."
| defClass item |
messageList := OrderedCollection new.
selectorCollection do:
[:selector | defClass := aClass whichClassIncludesSelector: selector.
(defClass notNil and: [defClass includesBehavior: self limitClass]) ifTrue:
[item := selector, ' (' , defClass name , ')'.
item := item asText.
defClass == aClass ifTrue: [item allBold].
"(self isThereAnOverrideOf: selector) ifTrue: [item addAttribute: TextEmphasis struckOut]."
"The above has a germ of a good idea but could be very slow"
messageList add: item]]!
----- Method: Lexicon>>initialLimitClass (in category 'limit class') -----
initialLimitClass
"Choose a plausible initial vlaue for the limit class, and answer it"
| oneTooFar |
limitClass := targetClass.
(#('ProtoObject' 'Object' 'Behavior' 'ClassDescription' 'Class' 'ProtoObject class' 'Object class') includes: targetClass name asString) ifTrue: [^ targetClass].
oneTooFar := (targetClass isKindOf: Metaclass)
ifTrue:
["use the fifth back from the superclass chain for Metaclasses, which is the immediate subclass of ProtoObject class. Print <ProtoObject class allSuperclasses> to count them yourself."
targetClass allSuperclasses at: (targetClass allSuperclasses size - 5)]
ifFalse:
[targetClass allSuperclasses at: targetClass allSuperclasses size].
[limitClass superclass ~~ oneTooFar]
whileTrue: [limitClass := limitClass superclass].
^ limitClass!
----- Method: Lexicon>>lastSearchString (in category 'search') -----
lastSearchString
"Answer the last search string, initializing it to an empty string if it has not been initialized yet"
^ currentQueryParameter ifNil: [currentQueryParameter := 'contents']!
----- Method: Lexicon>>lastSearchString: (in category 'search') -----
lastSearchString: aString
"Make a note of the last string searched for in the receiver"
currentQueryParameter := aString asString.
currentQuery := #selectorName.
autoSelectString := aString.
self setMethodListFromSearchString.
^ true!
----- Method: Lexicon>>lastSendersSearchSelector (in category 'search') -----
lastSendersSearchSelector
"Answer the last senders search selector, initializing it to a default value if it does not already have a value"
^ currentQueryParameter ifNil: [currentQueryParameter := #flag:]!
----- Method: Lexicon>>limitClass (in category 'limit class') -----
limitClass
"Answer the most generic class to show in the browser. By default, we go all the way up to ProtoObject"
^ limitClass ifNil: [self initialLimitClass]!
----- Method: Lexicon>>limitClass: (in category 'limit class') -----
limitClass: aClass
"Set the most generic class to show as indicated"
limitClass := aClass!
----- Method: Lexicon>>limitClassString (in category 'limit class') -----
limitClassString
"Answer a string representing the current choice of most-generic-class-to-show"
| most |
(most := self limitClass) == ProtoObject
ifTrue: [^ 'All'].
most == targetClass
ifTrue: [^ most name].
^ 'Only through ', most name!
----- Method: Lexicon>>maybeReselectClass:selector: (in category 'transition') -----
maybeReselectClass: aClass selector: aSelector
"The protocol or limitClass may have changed, so that there is a different categoryList. Formerly, the given class and selector were selected; if it is possible to do so, reselect them now"
aClass ifNil: [^ self].
(currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass)
ifTrue:
[self selectSelectorItsNaturalCategory: aSelector]!
----- Method: Lexicon>>messageListIndex: (in category 'basic operation') -----
messageListIndex: anIndex
"Set the message list index as indicated, and update the history list if appropriate"
| newSelector current |
current := self selectedMessageName.
super messageListIndex: anIndex.
anIndex = 0 ifTrue: [
self editSelection: #newMessage.
self contentsChanged].
(newSelector := self selectedMessageName) ifNotNil:
[self updateSelectorsVisitedfrom: current to: newSelector]!
----- Method: Lexicon>>messageListKey:from: (in category 'message list menu') -----
messageListKey: aChar from: view
"Respond to a Command key"
aChar == $f ifTrue: [^ self obtainNewSearchString].
^ super messageListKey: aChar from: view!
----- Method: Lexicon>>methodListFromSearchString: (in category 'search') -----
methodListFromSearchString: fragment
"Answer a method list of methods whose selectors match the given fragment"
| aList searchFor |
currentQueryParameter := fragment.
currentQuery := #selectorName.
autoSelectString := fragment.
searchFor := fragment asString asLowercase withBlanksTrimmed.
aList := targetClass allSelectors select:
[:aSelector | currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass].
searchFor size > 0 ifTrue:
[aList := aList select:
[:aSelector | aSelector includesSubstring: searchFor caseSensitive: false]].
^ aList asSortedArray
!
----- Method: Lexicon>>methodsWithInitials (in category 'within-tool queries') -----
methodsWithInitials
"Answer the list of method selectors within the scope of this tool whose time stamps begin with the initials designated by my currentQueryParameter"
^ self methodsWithInitials: currentQueryParameter!
----- Method: Lexicon>>methodsWithInitials: (in category 'within-tool queries') -----
methodsWithInitials: initials
"Return a list of selectors representing methods whose timestamps have the given initials and which are in the protocol of this object and within the range dictated by my limitClass."
| classToUse |
classToUse := self targetObject ifNotNil: [self targetObject class] ifNil: [targetClass]. "In support of lightweight uniclasses"
^ targetClass allSelectors select:
[:aSelector | (currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: classToUse limitClass: limitClass) and:
[Utilities doesMethod: aSelector forClass: classToUse bearInitials: initials]].
!
----- Method: Lexicon>>navigateToASender (in category 'senders') -----
navigateToASender
"Present the user with a list of senders of the currently-selected
message, and navigate to the chosen one"
| selectorSet chosen aSelector |
aSelector := self selectedMessageName.
selectorSet := Set new.
(self systemNavigation allCallsOn: aSelector)
do: [:anItem | selectorSet add: anItem methodSymbol].
selectorSet := selectorSet
select: [:sel | currentVocabulary
includesSelector: sel
forInstance: self targetObject
ofClass: targetClass
limitClass: limitClass].
selectorSet size == 0
ifTrue: [^ Beeper beep].
self okToChange
ifFalse: [^ self].
chosen := UIManager default chooseFrom: selectorSet asSortedArray values: selectorSet asSortedArray.
chosen isEmptyOrNil
ifFalse: [self displaySelector: chosen]!
----- Method: Lexicon>>navigateToNextMethod (in category 'history') -----
navigateToNextMethod
"Navigate to the 'next' method in the current viewing sequence"
| anIndex aSelector |
self selectorsVisited size == 0 ifTrue: [^ self].
anIndex := (aSelector := self selectedMessageName) notNil ifTrue: [selectorsVisited indexOf: aSelector ifAbsent: [selectorsVisited size]] ifFalse: [1].
self selectedCategoryName == self class viewedCategoryName
ifTrue:
[self selectWithinCurrentCategory: (selectorsVisited atWrap: (anIndex + 1))]
ifFalse:
[self displaySelector: (selectorsVisited atWrap: (anIndex + 1))]!
----- Method: Lexicon>>navigateToPreviousMethod (in category 'history') -----
navigateToPreviousMethod
"Navigate to the 'previous' method in the current viewing sequence"
| anIndex aSelector |
self selectorsVisited size == 0 ifTrue: [^ self].
anIndex := (aSelector := self selectedMessageName) notNil
ifTrue: [selectorsVisited indexOf: aSelector ifAbsent: [selectorsVisited size]]
ifFalse: [selectorsVisited size].
self selectedCategoryName == self class viewedCategoryName
ifTrue:
[self selectWithinCurrentCategory: (selectorsVisited atWrap: (anIndex - 1))]
ifFalse:
[self displaySelector: (selectorsVisited atWrap: (anIndex - 1))]!
----- Method: Lexicon>>navigateToRecentMethod (in category 'history') -----
navigateToRecentMethod
"Put up a menu of recent selectors visited and navigate to the one chosen"
| visited aSelector |
(visited := self selectorsVisited) size > 1 ifTrue:
[visited := visited copyFrom: 1 to: (visited size min: 20).
aSelector := UIManager default chooseFrom: visited values: visited
title: 'Recent methods visited in this browser'.
aSelector isEmptyOrNil ifFalse: [self displaySelector: aSelector]]!
----- Method: Lexicon>>newCategoryPane (in category 'category list') -----
newCategoryPane
"Formulate a category pane for insertion into the receiver's pane list"
| aListMorph |
aListMorph := PluggableListMorph on: self list: #categoryList
selected: #categoryListIndex changeSelected: #categoryListIndex:
menu: #categoryListMenu:shifted:
keystroke: #categoryListKey:from:.
aListMorph setNameTo: 'categoryList'.
aListMorph menuTitleSelector: #categoryListMenuTitle.
^ aListMorph!
----- Method: Lexicon>>noteAcceptanceOfCodeFor: (in category 'transition') -----
noteAcceptanceOfCodeFor: newSelector
"The user has submitted new code for the given selector; take a note of it. NB that the selectors-changed list gets added to here, but is not currently used in the system."
(self selectorsVisited includes: newSelector) ifFalse: [selectorsVisited add: newSelector].!
----- Method: Lexicon>>obtainNewSearchString (in category 'search') -----
obtainNewSearchString
"Put up a box allowing the user to enter a fresh search string"
| fragment |
fragment := UIManager default request: 'type method name or fragment: ' initialAnswer: self currentQueryParameter.
fragment ifNil: [^ self].
(fragment := fragment copyWithout: $ ) size == 0 ifTrue: [^ self].
currentQueryParameter := fragment.
fragment := fragment asLowercase.
currentQuery := #selectorName.
self showQueryResultsCategory.
self messageListIndex: 0!
----- Method: Lexicon>>offerMenu (in category 'menu commands') -----
offerMenu
"Offer a menu to the user, in response to the hitting of the menu button on the tool pane"
| aMenu |
aMenu := MenuMorph new defaultTarget: self.
aMenu addTitle: 'Lexicon'.
aMenu addStayUpItem.
aMenu addList: #(
('vocabulary...' chooseVocabulary)
('what to show...' offerWhatToShowMenu)
-
('inst var refs (here)' setLocalInstVarRefs)
('inst var defs (here)' setLocalInstVarDefs)
('class var refs (here)' setLocalClassVarRefs)
-
('navigate to a sender...' navigateToASender)
('recent...' navigateToRecentMethod)
('show methods in current change set'
showMethodsInCurrentChangeSet)
('show methods with initials...'
showMethodsWithInitials)
-
"('toggle search pane' toggleSearch)"
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' classHierarchy)
('browse method (O)' openSingleMessageBrowser)
('browse protocol (p)' browseFullProtocol)
-
('fileOut' fileOutMessage)
('printOut' printOutMessage)
-
('senders of... (n)' browseSendersOfMessages)
('implementors of... (m)' browseMessages)
('versions (v)' browseVersions)
('inheritance (i)' methodHierarchy)
-
('inst var refs' browseInstVarRefs)
('inst var defs' browseInstVarDefs)
('class var refs' browseClassVarRefs)
-
('more...' shiftedYellowButtonActivity)).
aMenu popUpInWorld: ActiveWorld!
----- Method: Lexicon>>okayToAccept (in category 'model glue') -----
okayToAccept
"Answer whether it is okay to accept the receiver's input"
| ok aClass reply |
(ok := super okayToAccept) ifTrue:
[((aClass := self selectedClassOrMetaClass) ~~ targetClass) ifTrue:
[reply := UIManager default chooseFrom:
{'okay, no problem'.
'cancel - let me reconsider'.
'compile into ', targetClass name, ' instead'.
'compile into a new uniclass'} title:
'Caution!! This would be
accepted into class ', aClass name, '.
Is that okay?' .
reply = 1 ifTrue: [^ true].
reply ~~ 2 ifTrue:
[self notYetImplemented].
^ false]].
^ ok!
----- Method: Lexicon>>openOnClass:inWorld:showingSelector: (in category 'toolbuilder') -----
openOnClass: aTargetClass inWorld: ignored showingSelector: aSelector
^self openOnClass: aTargetClass showingSelector: aSelector!
----- Method: Lexicon>>openOnClass:showingSelector: (in category 'toolbuilder') -----
openOnClass: aTargetClass showingSelector: aSelector
currentVocabulary ifNil: [currentVocabulary := Vocabulary fullVocabulary].
targetClass := aTargetClass.
self initialLimitClass.
self reformulateCategoryList.
ToolBuilder open: self.
self adjustWindowTitle.!
----- Method: Lexicon>>preserveSelectorIfPossibleSurrounding: (in category 'transition') -----
preserveSelectorIfPossibleSurrounding: aBlock
"Make a note of the currently-selected method; perform aBlock and then attempt to reestablish that same method as the selected one in the new circumstances"
| aClass aSelector |
aClass := self selectedClassOrMetaClass.
aSelector := self selectedMessageName.
aBlock value.
self hasSearchPane
ifTrue:
[self setMethodListFromSearchString]
ifFalse:
[self maybeReselectClass: aClass selector: aSelector]!
----- Method: Lexicon>>queryCharacterization (in category 'within-tool queries') -----
queryCharacterization
"Answer a characterization of the most recent query"
currentQuery == #selectorName
ifTrue: [^ 'My methods whose names include "', self lastSearchString, '"'].
currentQuery == #methodsWithInitials
ifTrue: [^ 'My methods stamped with initials ', currentQueryParameter].
currentQuery == #senders
ifTrue: [^ 'My methods that send #', self lastSendersSearchSelector].
currentQuery == #currentChangeSet
ifTrue: [^ 'My methods in the current change set'].
currentQuery == #instVarRefs
ifTrue: [^ 'My methods that refer to instance variable "', currentQueryParameter, '"'].
currentQuery == #instVarDefs
ifTrue: [^ 'My methods that store into instance variable "', currentQueryParameter, '"'].
currentQuery == #classVarRefs
ifTrue: [^ 'My methods that refer to class variable "', currentQueryParameter, '"'].
^ 'Results of queries will show up here'!
----- Method: Lexicon>>reformulateCategoryList (in category 'category list') -----
reformulateCategoryList
"Reformulate the category list"
categoryList := nil.
self categoryListIndex: 0.
self changed: #categoryList.
self contentsChanged!
----- Method: Lexicon>>reformulateList (in category 'transition') -----
reformulateList
"Make the category list afresh, and reselect the current selector if appropriate"
self preserveSelectorIfPossibleSurrounding:
[super reformulateList.
self categoryListIndex: categoryListIndex]!
----- Method: Lexicon>>reformulateListNoting: (in category 'transition') -----
reformulateListNoting: newSelector
"A method has possibly been submitted for the receiver with newSelector as its selector; If the receiver has a way of reformulating its message list, here is a chance for it to do so"
super reformulateListNoting: newSelector.
newSelector ifNotNil:
[self displaySelector: newSelector]!
----- Method: Lexicon>>removeFromSelectorsVisited (in category 'history') -----
removeFromSelectorsVisited
"Remove the currently-selected method from the active set"
| aSelector |
(aSelector := self selectedMessageName) ifNil: [^ self].
self removeFromSelectorsVisited: aSelector.
self chooseCategory: self class viewedCategoryName!
----- Method: Lexicon>>removeFromSelectorsVisited: (in category 'history') -----
removeFromSelectorsVisited: aSelector
"remove aSelector from my history list"
self selectorsVisited remove: aSelector ifAbsent: []!
----- Method: Lexicon>>removeMessage (in category 'menu commands') -----
removeMessage
"Remove the selected message from the system."
messageListIndex = 0 ifTrue: [^ self].
self okToChange ifFalse: [^ self].
super removeMessage.
"my #reformulateList method, called from the super #removeMethod method, will however try to preserve the selection, so we take pains to clobber it by the below..."
messageListIndex := 0.
self changed: #messageList.
self changed: #messageListIndex.
contents := nil.
self contentsChanged!
----- Method: Lexicon>>retainMethodSelectionWhileSwitchingToCategory: (in category 'transition') -----
retainMethodSelectionWhileSwitchingToCategory: aCategoryName
"retain method selection while switching the category-pane selection to show the category of the given name"
| aSelectedName |
aSelectedName := self selectedMessageName.
self categoryListIndex: (categoryList indexOf: aCategoryName ifAbsent: [^ self]).
aSelectedName ifNotNil: [self selectWithinCurrentCategory: aSelectedName]
!
----- Method: Lexicon>>seeAlso (in category 'within-tool queries') -----
seeAlso
"Present a menu offering the selector of the currently selected message, as well as of all messages sent by it. If the chosen selector is showable in the current browser, show it here, minding unsubmitted edits however"
self selectImplementedMessageAndEvaluate:
[:aSelector |
((currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass) "i.e., is this aSelector available in this browser"
and: [self okToChange])
ifTrue:
[self displaySelector: aSelector]
ifFalse:
[Beeper beep. "SysttemNavigation new browseAllImplementorsOf: aSelector"]].
"Initially I tried making this open an external implementors browser in this case, but later decided that the user model for this was unstable"!
----- Method: Lexicon>>seeAlso: (in category 'within-tool queries') -----
seeAlso: aSelector
"If the requested selector is showable in the current browser, show it here, minding unsubmitted edits however"
((currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass) "i.e., is aSelector available in this browser"
and: [self okToChange])
ifTrue:
[self displaySelector: aSelector]
ifFalse:
[Beeper beep]!
----- Method: Lexicon>>selectImplementedMessageAndEvaluate: (in category 'selection') -----
selectImplementedMessageAndEvaluate: aBlock
"Allow the user to choose one selector, chosen from the currently selected message's selector, as well as those of all messages sent by it, and evaluate aBlock on behalf of chosen selector. If there is only one possible choice, simply make it; if there are multiple choices, put up a menu, and evaluate aBlock on behalf of the the chosen selector, doing nothing if the user declines to choose any. In this variant, only selectors "
| selector method messages |
(selector := self selectedMessageName) ifNil: [^ self].
method := (self selectedClassOrMetaClass ifNil: [^ self])
compiledMethodAt: selector
ifAbsent: [].
(method isNil or: [(messages := method messages) size == 0])
ifTrue: [^ aBlock value: selector].
(messages size == 1 and: [messages includes: selector])
ifTrue:
[^ aBlock value: selector]. "If only one item, there is no choice"
messages := messages select: [:aSelector | currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass].
self systemNavigation
showMenuOf: messages
withFirstItem: selector
ifChosenDo: [:sel | aBlock value: sel]!
----- Method: Lexicon>>selectSelectorItsNaturalCategory: (in category 'selection') -----
selectSelectorItsNaturalCategory: aSelector
"Make aSelector be the current selection of the receiver, with the category being its home category."
| cat catIndex detectedItem |
cat := self categoryOfSelector: aSelector.
catIndex := categoryList indexOf: cat ifAbsent:
["The method's own category is not seen in this browser; the method probably occurs in some other category not known directly to the class, but for now, we'll just use the all category"
1].
self categoryListIndex: catIndex.
detectedItem := messageList detect:
[:anItem | (anItem asString upTo: $ ) asSymbol == aSelector] ifNone: [^ self].
self messageListIndex: (messageList indexOf: detectedItem ifAbsent: [^ self])!
----- Method: Lexicon>>selectWithinCurrentCategory: (in category 'selection') -----
selectWithinCurrentCategory: aSelector
"If aSelector is one of the selectors seen in the current category, select it"
| detectedItem |
detectedItem := self messageList detect:
[:anItem | (anItem asString upTo: $ ) asSymbol == aSelector] ifNone: [^ self].
self messageListIndex: (messageList indexOf: detectedItem ifAbsent: [^ self])!
----- Method: Lexicon>>selectWithinCurrentCategoryIfPossible: (in category 'category list') -----
selectWithinCurrentCategoryIfPossible: aSelector
"If the receiver's message list contains aSelector, navigate right to it without changing categories"
| detectedItem messageIndex |
aSelector ifNil: [^ self].
detectedItem := messageList detect:
[:anItem | (anItem asString upTo: $ ) asSymbol == aSelector] ifNone: [^ self].
messageIndex := messageList indexOf: detectedItem.
self messageListIndex: messageIndex
!
----- Method: Lexicon>>selectedCategoryName (in category 'category list') -----
selectedCategoryName
"Answer the selected category name"
^ categoryList ifNotNil:
[categoryList at: categoryListIndex ifAbsent: [nil]]!
----- Method: Lexicon>>selectedClassOrMetaClass (in category 'selection') -----
selectedClassOrMetaClass
"Answer the currently selected class (or metaclass)."
self setClassAndSelectorIn: [:c :s | ^c]!
----- Method: Lexicon>>selectedMessage (in category 'selection') -----
selectedMessage
"Answer the source method for the currently selected message."
(categoryList notNil and: [(categoryListIndex isNil or: [categoryListIndex == 0])])
ifTrue:
[^ '---'].
self setClassAndSelectorIn: [:class :selector |
class ifNil: [^ 'here would go the documentation for the protocol category, if any.'].
self showingDecompile ifTrue: [^ self decompiledSourceIntoContents].
self showingDocumentation ifTrue: [^ self commentContents].
currentCompiledMethod := class compiledMethodAt: selector ifAbsent: [nil].
^ self sourceStringPrettifiedAndDiffed asText makeSelectorBoldIn: class]!
----- Method: Lexicon>>selectorsChanged (in category 'within-tool queries') -----
selectorsChanged
"Return a list of methods in the current change set (or satisfying some
other such criterion) that are in the protocol of this object"
| aList aClass targetedClass |
targetedClass := self targetObject
ifNil: [targetClass]
ifNotNil: [self targetObject class].
aList := OrderedCollection new.
ChangeSet current methodChanges
associationsDo: [:classChgAssoc | classChgAssoc value
associationsDo: [:methodChgAssoc | (methodChgAssoc value == #change
or: [methodChgAssoc value == #add])
ifTrue: [(aClass := targetedClass whichClassIncludesSelector: methodChgAssoc key)
ifNotNil: [aClass name = classChgAssoc key
ifTrue: [aList add: methodChgAssoc key]]]]].
^ aList!
----- Method: Lexicon>>selectorsDefiningInstVar (in category 'within-tool queries') -----
selectorsDefiningInstVar
"Return a list of methods that define a given inst var that are in the protocol of this object"
| aList |
aList := OrderedCollection new.
targetClass withAllSuperclassesDo:
[:aClass |
(aClass whichSelectorsStoreInto: currentQueryParameter asString) do:
[:sel | sel isDoIt ifFalse: [aList add: sel]
]
].
^ aList!
----- Method: Lexicon>>selectorsMatching (in category 'search') -----
selectorsMatching
"Anwer a list of selectors in the receiver that match the current search string"
| fragment aList |
fragment := self lastSearchString asLowercase.
aList := targetClass allSelectors select:
[:aSelector | (aSelector includesSubstring: fragment caseSensitive: false) and:
[currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass]].
^ aList asSortedArray!
----- Method: Lexicon>>selectorsReferringToClassVar (in category 'category list') -----
selectorsReferringToClassVar
"Return a list of methods that refer to given class var that are in the
protocol of this object"
| aList aClass nonMeta poolAssoc |
nonMeta := targetClass theNonMetaClass.
aClass := nonMeta classThatDefinesClassVariable: currentQueryParameter.
aList := OrderedCollection new.
poolAssoc := aClass classPool associationAt: currentQueryParameter asSymbol.
(self systemNavigation allCallsOn: poolAssoc)
do: [:elem | (nonMeta isKindOf: elem actualClass)
ifTrue: [aList add: elem methodSymbol]].
^ aList!
----- Method: Lexicon>>selectorsReferringToInstVar (in category 'within-tool queries') -----
selectorsReferringToInstVar
"Return a list of methods that refer to a given inst var that are in the protocol of this object"
| aList |
aList := OrderedCollection new.
targetClass withAllSuperclassesDo: [:aClass |
(aClass whichSelectorsAccess: currentQueryParameter asString) do: [:sel |
sel isDoIt ifFalse: [aList add: sel]
]
].
^ aList!
----- Method: Lexicon>>selectorsRetrieved (in category 'within-tool queries') -----
selectorsRetrieved
"Anwer a list of selectors in the receiver that have been retrieved for the query category. This protocol is used when reformulating a list after, say, a limitClass change"
currentQuery == #classVarRefs ifTrue: [^ self selectorsReferringToClassVar].
currentQuery == #currentChangeSet ifTrue: [^ self selectorsChanged].
currentQuery == #instVarDefs ifTrue: [^ self selectorsDefiningInstVar].
currentQuery == #instVarRefs ifTrue: [^ self selectorsReferringToInstVar].
currentQuery == #methodsWithInitials ifTrue: [^ self methodsWithInitials].
currentQuery == #selectorName ifTrue: [^ self selectorsMatching].
currentQuery == #senders ifTrue: [^ self selectorsSendingSelectedSelector].
^ #()!
----- Method: Lexicon>>selectorsSendingSelectedSelector (in category 'senders') -----
selectorsSendingSelectedSelector
"Assumes lastSendersSearchSelector is already set"
| selectorSet sel cl |
autoSelectString := (self lastSendersSearchSelector upTo: $:) asString.
selectorSet := Set new.
(self systemNavigation allCallsOn: self lastSendersSearchSelector)
do: [:anItem |
sel := anItem methodSymbol.
cl := anItem actualClass.
((currentVocabulary
includesSelector: sel
forInstance: self targetObject
ofClass: targetClass
limitClass: limitClass)
and: [targetClass includesBehavior: cl])
ifTrue: [selectorSet add: sel]].
^ selectorSet asSortedArray!
----- Method: Lexicon>>selectorsVisited (in category 'history') -----
selectorsVisited
"Answer the list of selectors visited in this tool"
^ selectorsVisited ifNil: [selectorsVisited := OrderedCollection new]!
----- Method: Lexicon>>setClassAndSelectorIn: (in category 'selection') -----
setClassAndSelectorIn: csBlock
"Decode strings of the form <selectorName> (<className> [class])"
self selection ifNil: [^ csBlock value: targetClass value: nil].
^ super setClassAndSelectorIn: csBlock!
----- Method: Lexicon>>setLimitClass: (in category 'limit class') -----
setLimitClass: aClass
"Set aClass as the limit class for this browser"
| currentClass currentSelector |
currentClass := self selectedClassOrMetaClass.
currentSelector := self selectedMessageName.
self limitClass: aClass.
categoryList := nil.
self categoryListIndex: 0.
self changed: #categoryList.
self changed: #methodList.
self changed: #contents.
self changed: #limitClassString.
self adjustWindowTitle.
self hasSearchPane
ifTrue:
[self setMethodListFromSearchString].
self maybeReselectClass: currentClass selector: currentSelector
!
----- Method: Lexicon>>setLocalClassVarRefs (in category 'within-tool queries') -----
setLocalClassVarRefs
"Put up a list of the class variables in the viewed object, and when the user selects one, let the query results category show all the references to that class variable."
| aName |
(aName := targetClass theNonMetaClass chooseClassVarName) ifNil: [^ self].
currentQuery := #classVarRefs.
currentQueryParameter := aName.
self showQueryResultsCategory!
----- Method: Lexicon>>setLocalInstVarDefs (in category 'within-tool queries') -----
setLocalInstVarDefs
"Put up a list of the instance variables in the viewed object, and when the user seletcts one, let the query results category show all the references to that instance variable."
| instVarToProbe |
targetClass chooseInstVarThenDo:
[:aName | instVarToProbe := aName].
instVarToProbe isEmptyOrNil ifTrue: [^ self].
currentQuery := #instVarDefs.
currentQueryParameter := instVarToProbe.
self showQueryResultsCategory!
----- Method: Lexicon>>setLocalInstVarRefs (in category 'within-tool queries') -----
setLocalInstVarRefs
"Put up a list of the instance variables in the viewed object, and when the user seletcts one, let the query results category show all the references to that instance variable."
| instVarToProbe |
targetClass chooseInstVarThenDo:
[:aName | instVarToProbe := aName].
instVarToProbe isEmptyOrNil ifTrue: [^ self].
currentQuery := #instVarRefs.
currentQueryParameter := instVarToProbe.
self showQueryResultsCategory!
----- Method: Lexicon>>setMethodListFromSearchString (in category 'search') -----
setMethodListFromSearchString
"Set the method list of the receiver based on matches from the search string"
| fragment aList |
self okToChange ifFalse: [^ self].
fragment := currentQueryParameter.
fragment := fragment asString asLowercase withBlanksTrimmed.
aList := targetClass allSelectors select:
[:aSelector | currentVocabulary includesSelector: aSelector forInstance: self targetObject ofClass: targetClass limitClass: limitClass].
fragment size > 0 ifTrue:
[aList := aList select:
[:aSelector | aSelector includesSubstring: fragment caseSensitive: false]].
aList size == 0 ifTrue:
[^ Beeper beep].
self initListFrom: aList asSortedArray highlighting: targetClass.
messageListIndex := messageListIndex min: messageList size.
self changed: #messageList
!
----- Method: Lexicon>>setSendersSearch (in category 'senders') -----
setSendersSearch
"Put up a list of messages sent in the current message, find all methods
of the browsee which send the one the user chooses, and show that list
in the message-list pane, with the 'query results' item selected in the
category-list pane"
| selectorSet aSelector aString |
self selectedMessageName
ifNil: [aString := UIManager default request: 'Type selector to search for' initialAnswer: 'flag:'.
aString isEmptyOrNil
ifTrue: [^ self].
Symbol
hasInterned: aString
ifTrue: [:sel | aSelector := sel]]
ifNotNil: [self
selectMessageAndEvaluate: [:sel | aSelector := sel]].
aSelector
ifNil: [^ self].
selectorSet := Set new.
(self systemNavigation allCallsOn: aSelector)
do: [:anItem | selectorSet add: anItem methodSymbol].
selectorSet := selectorSet
select: [:sel | currentVocabulary
includesSelector: sel
forInstance: self targetObject
ofClass: targetClass
limitClass: limitClass].
selectorSet size > 0
ifTrue: [currentQuery := #senders.
currentQueryParameter := aSelector.
self
categoryListIndex: (categoryList indexOf: self class queryCategoryName).
self messageListIndex: 0]!
----- Method: Lexicon>>setToShowSelector: (in category 'selection') -----
setToShowSelector: aSelector
"Set up the receiver so that it will show the given selector"
| catName catIndex detectedItem messageIndex aList |
catName := (aList := currentVocabulary categoriesContaining: aSelector forClass: targetClass) size > 0
ifTrue:
[aList first]
ifFalse:
[self class allCategoryName].
catIndex := categoryList indexOf: catName ifAbsent: [1].
self categoryListIndex: catIndex.
detectedItem := messageList detect:
[:anItem | (anItem upTo: $ ) asString asSymbol == aSelector] ifNone: [^ self].
messageIndex := messageList indexOf: detectedItem.
self messageListIndex: messageIndex
!
----- Method: Lexicon>>showCategoriesPane (in category 'category list') -----
showCategoriesPane
"Show the categories pane instead of the search pane"
| aPane |
(aPane := self searchPane) ifNil: [^ Beeper beep].
self containingWindow replacePane: aPane with: self newCategoryPane.
categoryList := nil.
self changed: #categoryList.
self changed: #messageList!
----- Method: Lexicon>>showCategory (in category 'menu commands') -----
showCategory
"A revectoring blamable on history. Not sent in the image, but grandfathered buttons may still send this."
^ self showHomeCategory!
----- Method: Lexicon>>showHomeCategory (in category 'menu commands') -----
showHomeCategory
"Continue to show the current selector, but show it within the context of its primary category"
| aSelector |
(aSelector := self selectedMessageName) ifNotNil:
[self preserveSelectorIfPossibleSurrounding:
[self setToShowSelector: aSelector]]!
----- Method: Lexicon>>showMainCategory (in category 'menu commands') -----
showMainCategory
"Continue to show the current selector, but show it within the context of its primary category. Preserved for backward compatibility with pre-existing buttons."
^ self showHomeCategory!
----- Method: Lexicon>>showMethodsInCurrentChangeSet (in category 'within-tool queries') -----
showMethodsInCurrentChangeSet
"Set the current query to be for methods in the current change set"
currentQuery := #currentChangeSet.
autoSelectString := nil.
self categoryListIndex: (categoryList indexOf: self class queryCategoryName).!
----- Method: Lexicon>>showMethodsWithInitials (in category 'within-tool queries') -----
showMethodsWithInitials
"Prompt the user for initials to scan for; then show, in the query-results category, all methods with those initials in their time stamps"
| initials |
initials := UIManager default request: 'whose initials? ' initialAnswer: Utilities authorInitials.
initials isEmptyOrNil ifTrue: [^ self].
self showMethodsWithInitials: initials
!
----- Method: Lexicon>>showMethodsWithInitials: (in category 'within-tool queries') -----
showMethodsWithInitials: initials
"Make the current query be for methods stamped with the given initials"
currentQuery := #methodsWithInitials.
currentQueryParameter := initials.
self showQueryResultsCategory.
autoSelectString := nil.
self changed: #messageList.
self adjustWindowTitle
!
----- Method: Lexicon>>showQueryResultsCategory (in category 'within-tool queries') -----
showQueryResultsCategory
"Point the receiver at the query-results category and set the search string accordingly"
autoSelectString := self currentQueryParameter.
self categoryListIndex: (categoryList indexOf: self class queryCategoryName).
self messageListIndex: 0!
----- Method: Lexicon>>showSearchPane (in category 'search') -----
showSearchPane
"Given that the receiver is showing the categories pane, replace that with a search pane. Though there is a residual UI for obtaining this variant, it is obscure and the integrity of the protocol-category-browser when there is no categories pane is not necessarily assured at the moment."
| aPane |
(aPane := self categoriesPane) ifNil: [^ Beeper beep].
self containingWindow replacePane: aPane with: self newSearchPane.
categoryList := nil.
self changed: #categoryList.
self changed: #messageList!
----- Method: Lexicon>>startingWindowTitle (in category 'window title') -----
startingWindowTitle
"Answer the initial window title to apply"
^ 'Vocabulary of ', targetClass nameForViewer!
----- Method: Lexicon>>switchToVocabulary: (in category 'vocabulary') -----
switchToVocabulary: aVocabulary
"Make aVocabulary be the current one in the receiver"
self preserveSelectorIfPossibleSurrounding:
[self useVocabulary: aVocabulary.
self reformulateCategoryList.
self adjustWindowTitle]
!
----- Method: Lexicon>>targetObject (in category 'model glue') -----
targetObject
"Answer the object to which this tool is bound."
^ nil!
----- Method: Lexicon>>toggleSearch (in category 'search') -----
toggleSearch
"Toggle the determination of whether a categories pane or a search pane shows"
self hasSearchPane
ifTrue: [self showCategoriesPane]
ifFalse: [self showSearchPane]!
----- Method: Lexicon>>updateSelectorsVisitedfrom:to: (in category 'history') -----
updateSelectorsVisitedfrom: oldSelector to: newSelector
"Update the list of selectors visited."
newSelector == oldSelector ifTrue: [^ self].
self selectorsVisited remove: newSelector ifAbsent: [].
(selectorsVisited includes: oldSelector)
ifTrue:
[selectorsVisited add: newSelector after: oldSelector]
ifFalse:
[selectorsVisited add: newSelector]
!
----- Method: Lexicon>>useVocabulary: (in category 'vocabulary') -----
useVocabulary: aVocabulary
"Set up the receiver to use the given vocabulary"
currentVocabulary := aVocabulary!
----- Method: Lexicon>>wantsAnnotationPane (in category 'toolbuilder') -----
wantsAnnotationPane
"This kind of browser always wants annotation panes, so answer true"
^ true!
----- Method: ProtocolBrowser class>>openFullProtocolForClass: (in category 'instance creation') -----
openFullProtocolForClass: aClass
"Create and schedule a browser for the entire protocol of the class."
"ProtocolBrowser openFullProtocolForClass: ProtocolBrowser."
| aPBrowser label |
aPBrowser := ProtocolBrowser new on: aClass.
label := 'Entire protocol of: ', aClass name.
self open: aPBrowser name: label!
----- Method: ProtocolBrowser class>>openSubProtocolForClass: (in category 'instance creation') -----
openSubProtocolForClass: aClass
"Create and schedule a browser for the entire protocol of the class."
"ProtocolBrowser openSubProtocolForClass: ProtocolBrowser."
| aPBrowser label |
aPBrowser := ProtocolBrowser new onSubProtocolOf: aClass.
label := 'Sub-protocol of: ', aClass name.
self open: aPBrowser name: label!
----- Method: ProtocolBrowser>>getList (in category 'accessing') -----
getList
"Answer the receiver's message list."
^ messageList!
----- Method: ProtocolBrowser>>growable (in category 'accessing') -----
growable
"Answer whether the receiver is subject to manual additions and deletions"
^ false!
----- Method: ProtocolBrowser>>initListFrom:highlighting: (in category 'private') -----
initListFrom: selectorCollection highlighting: aClass
"Make up the messageList with items from aClass in boldface."
| defClass item |
messageList := OrderedCollection new.
selectorCollection do: [ :selector |
defClass := aClass whichClassIncludesSelector: selector.
item := selector, ' (' , defClass name , ')'.
defClass == aClass ifTrue: [item := item asText allBold].
messageList add: (
MethodReference new
setClass: defClass
methodSymbol: selector
stringVersion: item
)
].
selectedClass := aClass.!
----- Method: ProtocolBrowser>>list (in category 'accessing') -----
list
"Answer the receiver's message list."
^ messageList!
----- Method: ProtocolBrowser>>on: (in category 'private') -----
on: aClass
"Initialize with the entire protocol for the class, aClass."
self initListFrom: aClass allSelectors asSortedCollection
highlighting: aClass!
----- Method: ProtocolBrowser>>onSubProtocolOf: (in category 'private') -----
onSubProtocolOf: aClass
"Initialize with the entire protocol for the class, aClass,
but excluding those inherited from Object."
| selectors |
selectors := Set new.
aClass withAllSuperclasses do:
[:each | (each == Object or: [each == ProtoObject])
ifFalse: [selectors addAll: each selectors]].
self initListFrom: selectors asSortedCollection
highlighting: aClass!
----- Method: ProtocolBrowser>>selectedClassOrMetaClass (in category 'class list') -----
selectedClassOrMetaClass
^selectedClass!
----- Method: ProtocolBrowser>>selector (in category 'accessing') -----
selector
"Answer the receiver's selected selector."
^ selectedSelector!
----- Method: ProtocolBrowser>>selector: (in category 'accessing') -----
selector: aString
"Set the currently selected message selector to be aString."
selectedSelector := aString.
self changed: #selector!
----- Method: ProtocolBrowser>>setClassAndSelectorIn: (in category 'private') -----
setClassAndSelectorIn: csBlock
"Decode strings of the form <selectorName> (<className> [class])"
| i classAndSelString selString sel |
sel := self selection ifNil: [^ csBlock value: nil value: nil].
(sel isKindOf: MethodReference) ifTrue: [
sel setClassAndSelectorIn: csBlock
] ifFalse: [
selString := sel asString.
i := selString indexOf: $(.
"Rearrange to <className> [class] <selectorName> , and use MessageSet"
classAndSelString := (selString copyFrom: i + 1 to: selString size - 1) , ' ' ,
(selString copyFrom: 1 to: i - 1) withoutTrailingBlanks.
MessageSet parse: classAndSelString toClassAndSelector: csBlock.
].
!
----- Method: ProtocolBrowser>>setSelector: (in category 'accessing') -----
setSelector: aString
"Set the currently selected message selector to be aString."
selectedSelector := aString!
MessageSet subclass: #RecentMessageSet
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
!RecentMessageSet commentStamp: 'sw 8/1/2002 17:40' prior: 0!
RecentMessageSet is a message set that shows the most recently-submitted methods, in chronological order.!
----- Method: RecentMessageSet>>addExtraShiftedItemsTo: (in category 'message list') -----
addExtraShiftedItemsTo: aMenu
"The shifted selector-list menu is being built. Overridden here to defeat the presence of the items that add or change order, since RecentMessageSet defines methods & order explicitly based on external criteria"
aMenu add: 'set size of recent history...' action: #setRecentHistorySize!
----- Method: RecentMessageSet>>contents:notifying: (in category 'contents') -----
contents: c notifying: n
| result |
result := super contents: c notifying: n.
result == true ifTrue:
[self reformulateList].
^ result!
----- Method: RecentMessageSet>>growable (in category 'update') -----
growable
"Answer whether the receiver can be changed by manual additions & deletions"
^ false!
----- Method: RecentMessageSet>>maybeSetSelection (in category 'selection') -----
maybeSetSelection
"After a browser's message list is changed, this message is dispatched to the model, to give it a chance to refigure a selection"
self messageListIndex: 1!
----- Method: RecentMessageSet>>messageListMenu:shifted: (in category 'message functions') -----
messageListMenu: aMenu shifted: shifted
"Answer the message-list menu"
shifted ifTrue: [^ self shiftedMessageListMenu: aMenu].
aMenu addList:#(
('what to show...' offerWhatToShowMenu)
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' classHierarchy)
('browse method (O)' openSingleMessageBrowser)
('browse protocol (p)' browseFullProtocol)
-
('fileOut (o)' fileOutMessage)
('printOut' printOutMessage)
('copy selector (c)' copySelector)
-
('senders of... (n)' browseSendersOfMessages)
('implementors of... (m)' browseMessages)
('inheritance (i)' methodHierarchy)
('versions (v)' browseVersions)
-
('inst var refs...' browseInstVarRefs)
('inst var defs...' browseInstVarDefs)
('class var refs...' browseClassVarRefs)
('class variables' browseClassVariables)
('class refs (N)' browseClassRefs)
-
('remove method (x)' removeMessage)
('remove from RecentSubmissions' removeFromRecentSubmissions)
-
('more...' shiftedYellowButtonActivity)).
^ aMenu!
----- Method: RecentMessageSet>>reformulateList (in category 'update') -----
reformulateList
| myList |
"Reformulate the receiver's list. Exclude methods now deleted"
myList := Utilities recentMethodSubmissions reversed select: [ :each | each isValid].
self initializeMessageList: myList.
self messageListIndex: (messageList size min: 1). "0 or 1"
self changed: #messageList.
self changed: #messageListIndex!
----- Method: RecentMessageSet>>removeFromRecentSubmissions (in category 'message functions') -----
removeFromRecentSubmissions
"Remove the currently-selected method from the RecentSubmissions list"
| aClass methodSym |
((aClass := self selectedClassOrMetaClass) notNil and: [(methodSym := self selectedMessageName) notNil])
ifTrue:
[Utilities purgeFromRecentSubmissions: (MethodReference new setStandardClass: aClass methodSymbol: methodSym).
self reformulateList]!
----- Method: RecentMessageSet>>setRecentHistorySize (in category 'message list') -----
setRecentHistorySize
"Let the user specify the recent history size"
| aReply aNumber |
aReply := UIManager default request: 'How many recent methods
should be maintained?' initialAnswer: Utilities numberOfRecentSubmissionsToStore asString.
aReply isEmptyOrNil ifFalse:
[aNumber := aReply asNumber rounded.
(aNumber > 1 and: [aNumber <= 1000])
ifTrue:
[Utilities numberOfRecentSubmissionsToStore: aNumber.
self inform: 'Okay, ', aNumber asString, ' is the new size of the recent method history']
ifFalse:
[self inform: 'Sorry, must be a number between 2 & 1000']]
!
----- Method: RecentMessageSet>>updateListsAndCodeIn: (in category 'update') -----
updateListsAndCodeIn: aWindow
| recentFromUtilities |
"RAA 20 june 2000 - a recent change to how messages were displayed in the list caused them not to match what was stored in Utilities. This caused the recent submissions to be continuously updated. The hack below fixed that problem"
self flag: #mref. "in second pass, use simpler test"
self canDiscardEdits ifFalse: [^ self].
recentFromUtilities := Utilities mostRecentlySubmittedMessage,' '.
(messageList first asStringOrText asString beginsWith: recentFromUtilities)
ifFalse:
[self reformulateList]
ifTrue:
[self updateCodePaneIfNeeded]!
MessageSet subclass: #TimeProfileBrowser
instanceVariableNames: 'selectedClass selectedSelector block tally'
classVariableNames: 'TextMenu'
poolDictionaries: ''
category: 'Tools-Debugger'!
!TimeProfileBrowser commentStamp: '<historical>' prior: 0!
A TimeProfileBrowser is a browser visualizing the runtime profile of an executed Smalltalk block. It is useful for finding performance bottlenecks in code. When optimizing code it can
be hard to know what methods actually constitute the bulk of the execution time. Is it a few
methods that take very long time to execute or is it perhaps a single method that gets executed a thousand times?
The block is first spied on using a MessageTally instance (which has even more funtionality than used by the TimeProfileBrowser) which samples the block during it's execution and collects the amount of time approximately spent in the methods executed. Then the methods are shown in the browser with their relative execution time in percent.
Example:
TimeProfileBrowser onBlock: [20 timesRepeat: [Transcript show: 100 factorial printString]]
!
----- Method: TimeProfileBrowser class>>onBlock: (in category 'instance creation') -----
onBlock: block
"Open a profile browser on the given block, thereby running the block and
collecting the message tally."
"TimeProfileBrowser onBlock: [20 timesRepeat:
[Transcript show: 100 factorial printString]]"
| inst result |
inst := self new.
result := inst runBlock: block.
self open: inst name: 'Time Profile'.
^ result!
----- Method: TimeProfileBrowser class>>spyOn: (in category 'instance creation') -----
spyOn: block
"Open a profile browser on the given block, thereby running the block and
collecting the message tally."
"TimeProfileBrowser spyOn: [20 timesRepeat:
[Transcript show: 100 factorial printString]]"
^self onBlock: block!
----- Method: TimeProfileBrowser class>>spyOnProcess:forMilliseconds: (in category 'instance creation') -----
spyOnProcess: aProcess forMilliseconds: msecDuration
"Run aProcess for msecDuration milliseconds, then open a TimeProfileBrowser on the results."
"| p |
p := [100000 timesRepeat: [3.14159 printString]] fork.
(Delay forMilliseconds: 100) wait.
TimeProfileBrowser spyOnProcess: p forMilliseconds: 1000"
| inst |
inst := self new.
inst runProcess: aProcess forMilliseconds: msecDuration pollingEvery: MessageTally defaultPollPeriod.
self open: inst name: (String streamContents: [ :s | s nextPutAll: 'Time Profile for '; print: msecDuration; nextPutAll: ' msec' ]).
^ inst!
----- Method: TimeProfileBrowser>>initializeMessageList: (in category 'private') -----
initializeMessageList: anArray
messageList := anArray.
messageListIndex := 0.
contents := ''!
----- Method: TimeProfileBrowser>>messageListKey:from: (in category 'private') -----
messageListKey: aChar from: view
"Respond to a Command key. Cmd-D means re-run block."
aChar == $d ifTrue: [^Cursor execute showWhile: [ block value ]].
^super messageListKey: aChar from: view!
----- Method: TimeProfileBrowser>>messageListMenu:shifted: (in category 'private') -----
messageListMenu: aMenu shifted: shifted
"Add a menu to the inherited one."
| menu |
menu := super messageListMenu: aMenu shifted: shifted.
" menu addItem: (0)."
^menu!
----- Method: TimeProfileBrowser>>runBlock: (in category 'private') -----
runBlock: aBlock
^self runBlock: aBlock pollingEvery: MessageTally defaultPollPeriod!
----- Method: TimeProfileBrowser>>runBlock:pollingEvery: (in category 'private') -----
runBlock: aBlock pollingEvery: pollPeriod
| stream list result |
block := MessageSend
receiver: self
selector: #runBlock:pollingEvery:
arguments: {
aBlock.
pollPeriod}. "so we can re-run it"
tally := MessageTally new.
tally
maxClassNameSize: 1000;
maxClassPlusSelectorSize: 1000;
maxTabs: 100.
result := tally spyEvery: pollPeriod on: aBlock.
stream := ReadWriteStream
with: (String streamContents:
[:s |
tally
report: s;
close]).
stream reset.
list := OrderedCollection new.
[stream atEnd] whileFalse: [list add: stream nextLine].
self initializeMessageList: list.
self changed: #messageList.
self changed: #messageListIndex.
^result!
----- Method: TimeProfileBrowser>>runProcess:forMilliseconds:pollingEvery: (in category 'private') -----
runProcess: aProcess forMilliseconds: msecDuration pollingEvery: pollPeriod
| stream list result |
block := MessageSend
receiver: self
selector: #runProcess:forMilliseconds:pollingEvery:
arguments: {
aProcess.
msecDuration.
pollPeriod}. "so we can re-run it"
tally := MessageTally new.
tally
maxClassNameSize: 1000;
maxClassPlusSelectorSize: 1000;
maxTabs: 100.
result := tally
spyEvery: pollPeriod
onProcess: aProcess
forMilliseconds: msecDuration.
stream := ReadWriteStream
with: (String streamContents:
[:s |
tally
report: s;
close]).
stream reset.
list := OrderedCollection new.
[stream atEnd] whileFalse: [list add: stream nextLine].
self initializeMessageList: list.
self changed: #messageList.
self changed: #messageListIndex.
^result!
----- Method: TimeProfileBrowser>>selectedClass (in category 'accessing') -----
selectedClass
"Answer the receiver's 'selectedClass'."
^selectedClass!
----- Method: TimeProfileBrowser>>selectedClass: (in category 'accessing') -----
selectedClass: anObject
"Set the receiver's instance variable 'selectedClass' to be anObject."
selectedClass := anObject!
----- Method: TimeProfileBrowser>>selectedMessage (in category 'message list') -----
selectedMessage
"Answer the source method for the currently selected message."
| source |
self setClassAndSelectorIn:
[:class :selector |
source := class sourceMethodAt: selector ifAbsent: [^'Missing'].
Preferences browseWithPrettyPrint
ifTrue:
[source := class prettyPrinterClass
format: source
in: class
notifying: nil
decorated: false].
self selectedClass: class.
self selectedSelector: selector.
^source asText makeSelectorBoldIn: class].
^''!
----- Method: TimeProfileBrowser>>selectedSelector (in category 'accessing') -----
selectedSelector
"Answer the receiver's 'selectedSelector'."
^selectedSelector!
----- Method: TimeProfileBrowser>>selectedSelector: (in category 'accessing') -----
selectedSelector: anObject
"Set the receiver's instance variable 'selectedSelector' to be anObject."
selectedSelector := anObject!
----- Method: TimeProfileBrowser>>setClassAndSelectorIn: (in category 'private') -----
setClassAndSelectorIn: csBlock
"Decode strings of the form <selectorName> (<className> [class]) "
| string strm class sel parens |
self flag: #mref. "fix for faster references to methods"
[string := self selection asString.
string first == $* ifTrue: [^contents := nil]. "Ignore lines starting with *"
parens := string includes: $(. "Does it have open-paren?"
strm := ReadStream on: string.
parens
ifTrue: [strm skipTo: $(. "easy case"
class := strm upTo: $).
strm next: 2.
sel := strm upToEnd]
ifFalse: [strm position: (string findString: ' class>>').
strm position > 0
ifFalse: [strm position: (string findLast: [ :ch | ch == $ ])]
ifTrue:
[ | subString | "find the next to last space character"
subString := strm contents copyFrom: 1 to: (string findLast: [ :ch | ch == $ ]) - 1.
strm position: (subString findLast: [ :ch | ch == $ ])].
"ifFalse: [strm position: (string findLast: [ :ch | ch == $ ])."
class := strm upTo: $>.
strm next.
sel := strm upToEnd].
^ MessageSet parse: (class, ' ', sel) toClassAndSelector: csBlock]
on: Error do: [:ex | ^ contents := nil]!
----- Method: TimeProfileBrowser>>tally (in category 'accessing') -----
tally
"Answer the receiver's 'tally'."
^tally!
----- Method: TimeProfileBrowser>>tally: (in category 'accessing') -----
tally: anObject
"Set the receiver's instance variable 'tally' to be anObject."
tally := anObject!
Browser subclass: #PackagePaneBrowser
instanceVariableNames: 'package packageListIndex packageList'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
!PackagePaneBrowser commentStamp: '<historical>' prior: 0!
A package browser represents a hierarchical query path through an organization of class and method information. It parses class categories into a two-level hierarchy on the first '-' character, giving "packages" (e.g., Magnitude, Collections, Graphics, etc.), and "categories" (e.g., Magnitude-General and Magnitude-Number).
Instance Variables:
package <Symbol> the "category header," e.g., #Magnitudes or #Collections
packageListIndex <Integer> The index in the package list
packageList <OrderedCollection of String> the list of package names
!
----- Method: PackagePaneBrowser class>>prototypicalToolWindow (in category 'instance creation') -----
prototypicalToolWindow
"Answer an example of myself seen in a tool window, for the benefit of parts-launching tools"
| aWindow |
aWindow := self new openEditString: nil.
^ ToolBuilder build: aWindow!
----- Method: PackagePaneBrowser class>>registerInAppRegistry (in category 'class initialization') -----
registerInAppRegistry
"Register the receiver in the SystemBrowser AppRegistry"
SystemBrowser register: self.!
----- Method: PackagePaneBrowser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Package Browser' brightColor: #(1.0 1.0 0.6) pastelColor: #(0.976 0.976 0.835) helpMessage: 'A system browser with an extra pane at top-left for module.'!
----- Method: PackagePaneBrowser>>buildPackageListWith: (in category 'initialize-release') -----
buildPackageListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #packageList;
getIndex: #packageListIndex;
setIndex: #packageListIndex:;
menu: #packageMenu:;
keyPress: #packageListKey:from:.
^listSpec
!
----- Method: PackagePaneBrowser>>categoryExistsForPackage (in category 'package list') -----
categoryExistsForPackage
^ self hasPackageSelected
and: [(systemOrganizer categories indexOf: self package asSymbol) ~= 0]
!
----- Method: PackagePaneBrowser>>changeCategoryForClass:srcSystemCategory:atListMorph:internal:copy: (in category 'dragNDrop') -----
changeCategoryForClass: class srcSystemCategory: srcSystemCategorySel atListMorph: dstListMorph internal: internal copy: copyFlag
"only move semantic"
| newClassCategory success |
self flag: #stringSymbolProblem.
success := copyFlag not ifFalse: [^ false].
newClassCategory := self dstCategoryDstListMorph: dstListMorph internal: internal.
(success := newClassCategory notNil & (newClassCategory ~= class category))
ifTrue:
[class category: newClassCategory.
self changed: #classList.
internal ifFalse: [self selectClass: class]].
^ success!
----- Method: PackagePaneBrowser>>classList (in category 'class list') -----
classList
"Answer an array of the class names of the selected category. Answer an
empty array if no selection exists."
^ self hasSystemCategorySelected
ifFalse:
[self packageClasses]
ifTrue: [systemOrganizer listAtCategoryNumber:
(systemOrganizer categories indexOf: self selectedSystemCategoryName asSymbol)]!
----- Method: PackagePaneBrowser>>defaultBrowserTitle (in category 'initialize-release') -----
defaultBrowserTitle
^ 'Package Browser'!
----- Method: PackagePaneBrowser>>dstCategoryDstListMorph:internal: (in category 'dragNDrop util') -----
dstCategoryDstListMorph: dstListMorph internal: internal
| dropItem |
^ internal & (dstListMorph getListSelector == #systemCategoryList)
ifTrue: [(dropItem := dstListMorph potentialDropItem) ifNotNil: [(self package , '-' , dropItem) asSymbol]]
ifFalse: [self selectedSystemCategoryName]!
----- Method: PackagePaneBrowser>>hasPackageSelected (in category 'package list') -----
hasPackageSelected
^ packageListIndex ~= 0!
----- Method: PackagePaneBrowser>>hasSystemCategorySelected (in category 'system category list') -----
hasSystemCategorySelected
^ systemCategoryListIndex ~= 0!
----- Method: PackagePaneBrowser>>openEditString: (in category 'initialize-release') -----
openEditString: aString
"Create a pluggable version of all the views for a Browser, including views and controllers."
"Example:
PackagePaneBrowser fullOnClass: Browser.
"
| builder max |
builder := ToolBuilder default.
max := self wantsOptionalButtons ifTrue:[0.42] ifFalse:[0.5].
^self buildWindowWith: builder specs: {
(0 at 0 corner: 0.15 at max) -> [self buildPackageListWith: builder].
(0.15 at 0 corner: 0.35 at max) -> [self buildSystemCategoryListWith: builder].
(0.35 at 0 corner: 0.6@(max-0.1)) -> [self buildClassListWith: builder].
(0.35@(max-0.1) corner: 0.6 at max) -> [self buildSwitchesWith: builder].
(0.6 at 0 corner: 0.75 at max) -> [self buildMessageCategoryListWith: builder].
(0.75 at 0 corner: 1 at max) -> [self buildMessageListWith: builder].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}!
----- Method: PackagePaneBrowser>>package (in category 'package list') -----
package
"Answer the receiver's 'package'."
^ self hasPackageSelected
ifFalse: [nil]
ifTrue: [self packageList at: packageListIndex]
!
----- Method: PackagePaneBrowser>>packageClasses (in category 'class list') -----
packageClasses
^ self categoryExistsForPackage
ifFalse: [Array new]
ifTrue:
[systemOrganizer listAtCategoryNumber:
(systemOrganizer categories indexOf: self package asSymbol)]!
----- Method: PackagePaneBrowser>>packageList (in category 'package list') -----
packageList
"Answer a list of the packages in the current system organization."
| str cats stream |
str := Set new: 100.
stream := WriteStream on: (Array new: 100).
systemOrganizer categories do:
[ :categ |
cats := categ asString copyUpTo: $-.
(str includes: cats) ifFalse:
[str add: cats.
stream nextPut: cats]].
^stream contents!
----- Method: PackagePaneBrowser>>packageListIndex (in category 'package list') -----
packageListIndex
"Answer the index of the current package selection."
^packageListIndex!
----- Method: PackagePaneBrowser>>packageListIndex: (in category 'package list') -----
packageListIndex: anInteger
"Set anInteger to be the index of the current package selection."
packageListIndex := anInteger.
anInteger = 0
ifFalse: [package := self packageList at: packageListIndex].
messageCategoryListIndex := 0.
systemCategoryListIndex := 0.
messageListIndex := 0.
classListIndex := 0.
self setClassOrganizer.
self changed: #packageSelectionChanged.
self changed: #packageListIndex. "update my selection"
self changed: #systemCategoryList. "update the category list"
self systemCategoryListIndex: 0. "update category list selection"
!
----- Method: PackagePaneBrowser>>packageMenu: (in category 'package list') -----
packageMenu: aMenu
"Answer a Menu of operations on class packages to be
displayed when the operate menu button is pressed."
^aMenu
labels: 'find class...\recent classes...\reorganize\update' withCRs
lines: #(2)
selections: #(#findClass #recent #editSystemCategories #updatePackages)!
----- Method: PackagePaneBrowser>>selectCategoryForClass: (in category 'system category list') -----
selectCategoryForClass: theClass
"Set the package and category lists to display the given class."
| cat |
cat := theClass category.
self packageListIndex: (self packageList indexOf: (cat copyUpTo: $-)).
self systemCategoryListIndex: (self systemCategoryList indexOf:
(cat copyFrom: ((cat indexOf: $- ifAbsent: [0]) + 1) to: cat size)).!
----- Method: PackagePaneBrowser>>selectedClass (in category 'class list') -----
selectedClass
"Answer the class that is currently selected. Answer nil if no selection
exists."
| name envt |
(name := self selectedClassName) ifNil: [^ nil].
"(envt := self selectedEnvironment) ifNil: [^ nil]."
envt:= Smalltalk.
^ envt at: name!
----- Method: PackagePaneBrowser>>selectedSystemCategoryName (in category 'system category list') -----
selectedSystemCategoryName
"Answer the name of the selected system category or nil."
systemCategoryListIndex = 0
ifTrue: [^nil].
packageListIndex = 0
ifTrue: [^ self systemCategoryList at: systemCategoryListIndex].
^ self package , '-' , (self systemCategoryList at: systemCategoryListIndex)!
----- Method: PackagePaneBrowser>>systemCategoryList (in category 'system category list') -----
systemCategoryList
"Answer the sequenceable collection containing the class categories that
the receiver accesses."
| prefix |
packageListIndex = 0 ifTrue: [^ systemOrganizer categories].
prefix := self package, '-'.
^ Array streamContents:
[:strm |
systemOrganizer categories do:
[ :cat | (cat beginsWith: prefix) ifTrue:
[strm nextPut: (cat copyFrom: prefix size + 1 to: cat size)]]]!
----- Method: PackagePaneBrowser>>systemOrganizer: (in category 'initialize-release') -----
systemOrganizer: aSystemOrganizer
"Initialize the receiver as a perspective on the system organizer,
aSystemOrganizer. Typically there is only one--the system variable
SystemOrganization."
super systemOrganizer: aSystemOrganizer .
packageListIndex := 0!
----- Method: PackagePaneBrowser>>updatePackages (in category 'package list') -----
updatePackages
"Update the contents of the package list."
self editSelection: #none.
self changed: #packageList.
self changed: #package.
self packageListIndex: 0 !
CodeHolder subclass: #ChangeList
instanceVariableNames: 'changeList list listIndex listSelections file lostMethodPointer showsVersions'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!ChangeList commentStamp: '<historical>' prior: 0!
A ChangeList represents a list of changed methods that reside on a file in fileOut format. The classes and methods in my list are not necessarily in this image!! Used as the model for both Version Lists and Changed Methods (in Screen Menu, Changes...). Note that the two kinds of window have different controller classes!!!!
It holds three lists:
changeList - a list of ChangeRecords
list - a list of one-line printable headers
listSelections - a list of Booleans (true = selected, false = not selected) multiple OK.
listIndex
Items that are removed (removeDoits, remove an item) are removed from all three lists.
Most recently clicked item is the one showing in the bottom pane.!
----- Method: ChangeList class>>browseChangesFile: (in category 'fileIn/Out') -----
browseChangesFile: fullName
"Browse the selected file in fileIn format."
fullName
ifNotNil:
[ChangeList browseStream: (FileStream readOnlyFileNamed: fullName)]
ifNil:
[Beeper beep]!
----- Method: ChangeList class>>browseCompressedChangesFile: (in category 'fileIn/Out') -----
browseCompressedChangesFile: fullName
"Browse the selected file in fileIn format."
| zipped unzipped stream |
fullName ifNil: [^Beeper beep].
stream := FileStream readOnlyFileNamed: fullName.
[stream converter: Latin1TextConverter new.
zipped := GZipReadStream on: stream.
unzipped := zipped contents asString]
ensure: [stream close].
stream := (MultiByteBinaryOrTextStream with: unzipped) reset.
ChangeList browseStream: stream!
----- Method: ChangeList class>>browseFile: (in category 'public access') -----
browseFile: fileName "ChangeList browseFile: 'AutoDeclareFix.st'"
"Opens a changeList on the file named fileName"
^ self browseStream: (FileStream readOnlyFileNamed: fileName)!
----- Method: ChangeList class>>browseRecent: (in category 'public access') -----
browseRecent: charCount
"ChangeList browseRecent: 5000"
"Opens a changeList on the end of the changes log file"
^ self browseRecent: charCount on: (SourceFiles at: 2) !
----- Method: ChangeList class>>browseRecent:on: (in category 'public access') -----
browseRecent: charCount on: origChangesFile
"Opens a changeList on the end of the specified changes log file"
| changeList end changesFile |
changesFile := origChangesFile readOnlyCopy.
changesFile setConverterForCode.
end := changesFile size.
Cursor read
showWhile: [changeList := self new
scanFile: changesFile
from: (0 max: end - charCount)
to: end].
changesFile close.
self
open: changeList
name: 'Recent changes'
multiSelect: true!
----- Method: ChangeList class>>browseRecentLog (in category 'public access') -----
browseRecentLog
"ChangeList browseRecentLog"
"Prompt with a menu of how far back to go to browse the current image's changes log file"
^ self
browseRecentLogOn: (SourceFiles at: 2)
startingFrom: SmalltalkImage current lastQuitLogPosition!
----- Method: ChangeList class>>browseRecentLogOn: (in category 'public access') -----
browseRecentLogOn: origChangesFile
"figure out where the last snapshot or quit was, then browse the recent entries."
| end done block pos chunk changesFile positions prevBlock |
changesFile := origChangesFile readOnlyCopy.
positions := SortedCollection new.
end := changesFile size.
prevBlock := end.
block := end - 1024 max: 0.
done := false.
[done
or: [positions size > 0]]
whileFalse: [changesFile position: block.
"ignore first fragment"
changesFile nextChunk.
[changesFile position < prevBlock]
whileTrue: [pos := changesFile position.
chunk := changesFile nextChunk.
((chunk indexOfSubCollection: '----' startingAt: 1) = 1) ifTrue: [
({ '----QUIT'. '----SNAPSHOT' } anySatisfy: [ :str |
chunk beginsWith: str ])
ifTrue: [positions add: pos]]].
block = 0
ifTrue: [done := true]
ifFalse: [prevBlock := block.
block := block - 1024 max: 0]].
changesFile close.
positions isEmpty
ifTrue: [self inform: 'File ' , changesFile name , ' does not appear to be a changes file']
ifFalse: [self browseRecentLogOn: origChangesFile startingFrom: positions last]!
----- Method: ChangeList class>>browseRecentLogOn:startingFrom: (in category 'public access') -----
browseRecentLogOn: origChangesFile startingFrom: initialPos
"Prompt with a menu of how far back to go when browsing a changes file."
| end banners positions pos chunk i changesFile |
changesFile := origChangesFile readOnlyCopy.
banners := OrderedCollection new.
positions := OrderedCollection new.
end := changesFile size.
changesFile setConverterForCode.
pos := initialPos.
[pos = 0
or: [banners size > 20]]
whileFalse: [changesFile position: pos.
chunk := changesFile nextChunk.
i := chunk indexOfSubCollection: 'priorSource: ' startingAt: 1.
i > 0
ifTrue: [positions addLast: pos.
banners
addLast: (chunk copyFrom: 5 to: i - 2).
pos := Number
readFrom: (chunk copyFrom: i + 13 to: chunk size)]
ifFalse: [pos := 0]].
changesFile close.
banners size == 0 ifTrue: [^ self inform:
'this image has never been saved
since changes were compressed'].
pos := UIManager default chooseFrom: banners values: positions
title: 'Browse as far back as...'.
pos == nil
ifTrue: [^ self].
self browseRecent: end - pos on: origChangesFile!
----- Method: ChangeList class>>browseRecentLogOnPath: (in category 'public access') -----
browseRecentLogOnPath: fullName
"figure out where the last snapshot or quit was, then browse the recent entries."
fullName
ifNotNil:
[self browseRecentLogOn: (FileStream readOnlyFileNamed: fullName)]
ifNil:
[Beeper beep]
!
----- Method: ChangeList class>>browseStream: (in category 'public access') -----
browseStream: changesFile
"Opens a changeList on a fileStream"
| changeList charCount |
changesFile readOnly.
changesFile setConverterForCode.
charCount := changesFile size.
charCount > 1000000 ifTrue:
[(self confirm: 'The file ', changesFile name , '
is really long (' , charCount printString , ' characters).
Would you prefer to view only the last million characters?')
ifTrue: [charCount := 1000000]].
"changesFile setEncoderForSourceCodeNamed: changesFile name."
Cursor read showWhile:
[changeList := self new
scanFile: changesFile from: changesFile size-charCount to: changesFile size].
changesFile close.
self open: changeList name: changesFile localName , ' log' multiSelect: true!
----- Method: ChangeList class>>fileReaderServicesForFile:suffix: (in category 'fileIn/Out') -----
fileReaderServicesForFile: fullName suffix: suffix
| services |
services := OrderedCollection new.
(FileStream isSourceFileSuffix: suffix) | (suffix = '*')
ifTrue: [ services add: self serviceBrowseChangeFile ].
(suffix = 'changes') | (suffix = '*')
ifTrue: [ services add: self serviceBrowseDotChangesFile ].
(fullName asLowercase endsWith: '.cs.gz') | (suffix = '*')
ifTrue: [ services add: self serviceBrowseCompressedChangeFile ].
^services!
----- Method: ChangeList class>>getRecentLocatorWithPrompt: (in category 'public access') -----
getRecentLocatorWithPrompt: aPrompt
"Prompt with a menu of how far back to go. Return nil if user backs out. Otherwise return the number of characters back from the end of the .changes file the user wishes to include"
"ChangeList getRecentPosition"
| end changesFile banners positions pos chunk i |
changesFile := (SourceFiles at: 2) readOnlyCopy.
banners := OrderedCollection new.
positions := OrderedCollection new.
end := changesFile size.
pos := SmalltalkImage current lastQuitLogPosition.
[pos = 0 or: [banners size > 20]] whileFalse:
[changesFile position: pos.
chunk := changesFile nextChunk.
i := chunk indexOfSubCollection: 'priorSource: ' startingAt: 1.
i > 0 ifTrue: [positions addLast: pos.
banners addLast: (chunk copyFrom: 5 to: i-2).
pos := Number readFrom: (chunk copyFrom: i+13 to: chunk size)]
ifFalse: [pos := 0]].
changesFile close.
pos := UIManager default chooseFrom: banners values: positions title: aPrompt.
pos == nil ifTrue: [^ nil].
^ end - pos!
----- Method: ChangeList class>>initialize (in category 'initialize-release') -----
initialize
FileList registerFileReader: self!
----- Method: ChangeList class>>open:name:multiSelect: (in category 'instance creation') -----
open: aChangeList name: aString multiSelect: multiSelect
"Create a standard system view for the messageSet, whose label is aString.
The listView may be either single or multiple selection type"
^ToolBuilder default open: aChangeList label: aString!
----- Method: ChangeList class>>serviceBrowseChangeFile (in category 'fileIn/Out') -----
serviceBrowseChangeFile
"Answer a service for opening a changelist browser on a file"
^ (SimpleServiceEntry
provider: self
label: 'changelist browser'
selector: #browseStream:
description: 'open a changelist tool on this file'
buttonLabel: 'changes')
argumentGetter: [ :fileList | fileList readOnlyStream ]!
----- Method: ChangeList class>>serviceBrowseCompressedChangeFile (in category 'fileIn/Out') -----
serviceBrowseCompressedChangeFile
"Answer a service for opening a changelist browser on a file"
^ SimpleServiceEntry
provider: self
label: 'changelist browser'
selector: #browseCompressedChangesFile:
description: 'open a changelist tool on this file'
buttonLabel: 'changes'!
----- Method: ChangeList class>>serviceBrowseDotChangesFile (in category 'fileIn/Out') -----
serviceBrowseDotChangesFile
"Answer a service for opening a changelist browser on the tail end of a .changes file"
^ SimpleServiceEntry
provider: self
label: 'recent changes in file'
selector: #browseRecentLogOnPath:
description: 'open a changelist tool on recent changes in file'
buttonLabel: 'recent changes'!
----- Method: ChangeList class>>services (in category 'fileIn/Out') -----
services
"Answer potential file services associated with this class"
^ { self serviceBrowseChangeFile.
self serviceBrowseDotChangesFile.
self serviceBrowseCompressedChangeFile }!
----- Method: ChangeList class>>unload (in category 'class initialization') -----
unload
FileList unregisterFileReader: self !
----- Method: ChangeList class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Change List' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'A tool that presents a list of all the changes found in an external file.'!
----- Method: ChangeList>>acceptFrom: (in category 'menu actions') -----
acceptFrom: aView
aView controller text = aView controller initialText ifFalse: [
aView flash.
^ self inform: 'You can only accept this version as-is.
If you want to edit, copy the text to a browser'].
(aView setText: aView controller text from: self) ifTrue:
[aView ifNotNil: [aView controller accept]]. "initialText"
!
----- Method: ChangeList>>addItem:text: (in category 'initialization-release') -----
addItem: item text: text
| cr |
cr := Character cr.
changeList addLast: item.
list addLast: (text collect: [:x | x = cr ifTrue: [$/] ifFalse: [x]])!
----- Method: ChangeList>>annotation (in category 'viewing access') -----
annotation
"Answer the string to be shown in an annotation pane. Make plain that the annotation is associated with the current in-image version of the code, not of the selected disk-based version, and if the corresponding method is missing from the in-image version, mention that fact."
| annot aChange aClass |
annot := super annotation.
annot asString = '------' ifTrue: [^ annot].
^ ((aChange := self currentChange) notNil and: [aChange methodSelector notNil])
ifFalse:
[annot]
ifTrue:
[((aClass := aChange methodClass) isNil or: [(aClass includesSelector: aChange methodSelector) not])
ifTrue:
[aChange methodClassName, ' >> ', aChange methodSelector, ' is not present in the current image.']
ifFalse:
['current version: ', annot]]!
----- Method: ChangeList>>browseAllVersionsOfSelections (in category 'menu actions') -----
browseAllVersionsOfSelections
"Opens a Versions browser on all the currently selected methods, showing each alongside all of their historical versions."
| oldSelection aList |
oldSelection := self listIndex.
aList := OrderedCollection new.
Cursor read showWhile: [
1 to: changeList size do: [:i |
(listSelections at: i) ifTrue: [
listIndex := i.
self browseVersions.
aList add: i.
]]].
listIndex := oldSelection.
aList size == 0 ifTrue: [^ self inform: 'no selected methods have in-memory counterparts'].
!
----- Method: ChangeList>>browseCurrentVersionsOfSelections (in category 'menu actions') -----
browseCurrentVersionsOfSelections
"Opens a message-list browser on the current in-memory versions of all methods that are currently seleted"
| aClass aChange aList |
aList := OrderedCollection new.
Cursor read showWhile: [
1 to: changeList size do: [:i |
(listSelections at: i) ifTrue: [
aChange := changeList at: i.
(aChange type = #method
and: [(aClass := aChange methodClass) notNil
and: [aClass includesSelector: aChange methodSelector]])
ifTrue: [
aList add: (
MethodReference new
setStandardClass: aClass
methodSymbol: aChange methodSelector
)
]]]].
aList size == 0 ifTrue: [^ self inform: 'no selected methods have in-memory counterparts'].
MessageSet
openMessageList: aList
name: 'Current versions of selected methods in ', file localName!
----- Method: ChangeList>>browseVersions (in category 'menu actions') -----
browseVersions
| change class browser |
listIndex = 0
ifTrue: [^ nil ].
change := changeList at: listIndex.
((class := change methodClass) notNil
and: [class includesSelector: change methodSelector])
ifFalse: [ ^nil ].
browser := super browseVersions.
browser ifNotNil: [ browser addedChangeRecord: change ].
^browser!
----- Method: ChangeList>>buildChangeListWith:multiSelect: (in category 'toolbuilder') -----
buildChangeListWith: builder multiSelect: multiSelect
| listSpec |
multiSelect ifTrue:[
listSpec := builder pluggableMultiSelectionListSpec new.
listSpec getSelectionList: #listSelectionAt:.
listSpec setSelectionList: #listSelectionAt:put:.
] ifFalse:[
listSpec := builder pluggableListSpec new.
].
listSpec
model: self;
list: #list;
getIndex: #listIndex;
setIndex: #toggleListIndex:;
menu: (self showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:]);
keyPress: #changeListKey:from:.
^listSpec!
----- Method: ChangeList>>buildMorphicCodePaneWith: (in category 'menu actions') -----
buildMorphicCodePaneWith: editString
| codePane |
codePane := AcceptableCleanTextMorph
on: self
text: #contents
accept: #contents:
readSelection: #contentsSelection
menu: #codePaneMenu:shifted:.
codePane font: Preferences standardCodeFont.
editString ifNotNil: [
codePane editString: editString.
codePane hasUnacceptedEdits: true
].
^codePane
!
----- Method: ChangeList>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
^self buildWith: builder multiSelect: self showsVersions not!
----- Method: ChangeList>>buildWith:multiSelect: (in category 'toolbuilder') -----
buildWith: builder multiSelect: multiSelect
"Open a morphic view for the messageSet, whose label is labelString.
The listView may be either single or multiple selection type"
| windowSpec max |
max := self wantsOptionalButtons ifTrue:[0.33] ifFalse:[0.4].
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 1 at max) -> [self buildChangeListWith: builder multiSelect: multiSelect].
(0 at max corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.
^builder build: windowSpec!
----- Method: ChangeList>>changeList (in category 'accessing') -----
changeList
^ changeList!
----- Method: ChangeList>>changeListButtonSpecs (in category 'initialization-release') -----
changeListButtonSpecs
^#(
('select all' selectAll 'select all entries')
('deselect all' deselectAll 'deselect all entries')
('select conflicts' selectAllConflicts 'select all methods that occur in any change set')
('file in selections' fileInSelections 'file in all selected entries')
)!
----- Method: ChangeList>>changeListKey:from: (in category 'menu actions') -----
changeListKey: aChar from: view
"Respond to a Command key in the list pane."
aChar == $D ifTrue: [^ self toggleDiffing].
aChar == $a ifTrue: [^ self selectAll].
^ self arrowKey: aChar from: view!
----- Method: ChangeList>>changeListMenu: (in category 'menu actions') -----
changeListMenu: aMenu
"Fill aMenu up so that it comprises the primary changelist-browser menu"
Smalltalk isMorphic ifTrue:
[aMenu addTitle: 'change list'.
aMenu addStayUpItemSpecial].
aMenu addList: #(
('fileIn selections' fileInSelections 'import the selected items into the image')
('fileOut selections... ' fileOutSelections 'create a new file containing the selected items')
-
('compare to current' compareToCurrentVersion 'open a separate window which shows the text differences between the on-file version and the in-image version.' )
('toggle diffing (D)' toggleDiffing 'start or stop showing diffs in the code pane.')
-
('select conflicts with any changeset' selectAllConflicts 'select methods in the file which also occur in any change-set in the system')
('select conflicts with current changeset' selectConflicts 'select methods in the file which also occur in the current change-set')
('select conflicts with...' selectConflictsWith 'allows you to designate a file or change-set against which to check for code conflicts.')
-
('select unchanged methods' selectUnchangedMethods 'select methods in the file whose in-image versions are the same as their in-file counterparts' )
('select new methods' selectNewMethods 'select methods in the file that do not current occur in the image')
('select methods for this class' selectMethodsForThisClass 'select all methods in the file that belong to the currently-selected class')
-
('select all (a)' selectAll 'select all the items in the list')
('deselect all' deselectAll 'deselect all the items in the list')
('invert selections' invertSelections 'select every item that is not currently selected, and deselect every item that *is* currently selected')
-
('browse all versions of single selection' browseVersions 'open a version browser showing the versions of the currently selected method')
('browse all versions of selections' browseAllVersionsOfSelections 'open a version browser showing all the versions of all the selected methods')
('browse current versions of selections' browseCurrentVersionsOfSelections 'open a message-list browser showing the current (in-image) counterparts of the selected methods')
('destroy current methods of selections' destroyCurrentCodeOfSelections 'remove (*destroy*) the in-image counterparts of all selected methods')
-
('remove doIts' removeDoIts 'remove all items that are doIts rather than methods')
('remove older versions' removeOlderMethodVersions 'remove all but the most recent versions of methods in the list')
('remove up-to-date versions' removeExistingMethodVersions 'remove all items whose code is the same as the counterpart in-image code')
('remove selected items' removeSelections 'remove the selected items from the change-list')
('remove unselected items' removeNonSelections 'remove all the items not currently selected from the change-list')).
^ aMenu
!
----- Method: ChangeList>>changes:file: (in category 'accessing') -----
changes: changeRecords file: aFile
file := aFile.
changeList := OrderedCollection new.
list := OrderedCollection new.
listIndex := 0.
changeRecords do: [:each |
(each respondsTo: #methodClass)
ifFalse: [self addItem: ChangeRecord new text: each asString]
ifTrue:
[self addItem: each text: ('method: ' , each methodClass name , (each isMetaClassChange ifTrue: [' class '] ifFalse: [' '])
, each methodSelector
, '; ' , each stamp)]].
listSelections := Array new: list size withAll: false!
----- Method: ChangeList>>compareToCurrentVersion (in category 'menu actions') -----
compareToCurrentVersion
"If the current selection corresponds to a method in the system, then spawn a window showing the diffs as text"
| change class s1 s2 |
listIndex = 0
ifTrue: [^ self].
change := changeList at: listIndex.
((class := change methodClass) notNil
and: [class includesSelector: change methodSelector])
ifTrue: [s1 := (class sourceCodeAt: change methodSelector) asString.
s2 := change string.
s1 = s2
ifTrue: [^ self inform: 'Exact Match'].
(StringHolder new
textContents: (TextDiffBuilder buildDisplayPatchFrom: s1 to: s2 inClass: class prettyDiffs: self showingPrettyDiffs))
openLabel: 'Comparison to Current Version']
ifFalse: [self flash]!
----- Method: ChangeList>>contents (in category 'viewing access') -----
contents
"Answer the contents string, obeying diffing directives if needed"
^ self showingAnyKindOfDiffs
ifFalse:
[self undiffedContents]
ifTrue:
[self showsVersions
ifTrue:
[self diffedVersionContents]
ifFalse:
[self contentsDiffedFromCurrent]]!
----- Method: ChangeList>>contents: (in category 'viewing access') -----
contents: aString
listIndex = 0 ifTrue: [self changed: #flash. ^ false].
lostMethodPointer ifNotNil: [^ self restoreDeletedMethod].
self okToChange "means not dirty" ifFalse: ["is dirty"
self inform: 'This is a view of a method on a file.\Please cancel your changes. You may\accept, but only when the method is untouched.' withCRs. ^ false].
"Can't accept changes here. Method text must be unchanged!!"
(changeList at: listIndex) fileIn.
^ true!
----- Method: ChangeList>>contentsDiffedFromCurrent (in category 'viewing access') -----
contentsDiffedFromCurrent
"Answer the contents diffed forward from current (in-memory) method version"
| aChange aClass |
listIndex = 0
ifTrue: [^ ''].
aChange := changeList at: listIndex.
^ ((aChange type == #method and: [(aClass := aChange methodClass) notNil]) and: [aClass includesSelector: aChange methodSelector])
ifTrue:
[Utilities methodDiffFor: aChange text class: aClass selector: aChange methodSelector prettyDiffs: self showingPrettyDiffs]
ifFalse:
[(changeList at: listIndex) text]!
----- Method: ChangeList>>contentsSymbolQuints (in category 'viewing access') -----
contentsSymbolQuints
"Answer a list of quintuplets representing information on the alternative views available in the code pane"
^ self sourceAndDiffsQuintsOnly!
----- Method: ChangeList>>currentChange (in category 'accessing') -----
currentChange
"return the current change being viewed, or nil if none"
listIndex = 0 ifTrue: [ ^nil ].
^changeList at: listIndex!
----- Method: ChangeList>>deselectAll (in category 'menu actions') -----
deselectAll
"Deselect all items in the list pane, and clear the code pane"
listIndex := 0.
listSelections atAllPut: false.
self changed: #allSelections.
self contentsChanged!
----- Method: ChangeList>>destroyCurrentCodeOfSelections (in category 'menu actions') -----
destroyCurrentCodeOfSelections
"Actually remove from the system any in-memory methods with class and selector identical to items current selected. This may seem rather arcane but believe me it has its great uses, when trying to split out code. To use effectively, first file out a change set that you wish to split off. Then open a ChangeList browser on that fileout. Now look through the methods, and select any of them which you want to remove completely from the system, then issue this command. For those methods where you have made changes to pre-existing versions, of course, you won't want to remove them from the system, so use this mechanism with care!!"
| aClass aChange aList |
aList := OrderedCollection new.
1 to: changeList size do:
[:index |
(listSelections at: index) ifTrue:
[aChange := changeList at: index.
(aChange type = #method
and: [(aClass := aChange methodClass) notNil
and: [aClass includesSelector: aChange methodSelector]])
ifTrue:
[aList add: {aClass. aChange methodSelector}]]].
aList size > 0 ifTrue:
[(self confirm: 'Warning!! This will actually remove ', aList size printString, ' method(s) from the system!!') ifFalse: [^ self]].
aList do:
[:aPair | Transcript cr; show: 'Removed: ', aPair first printString, '.', aPair second.
aPair first removeSelector: aPair second]!
----- Method: ChangeList>>diffedVersionContents (in category 'viewing access') -----
diffedVersionContents
"Answer diffed version contents, maybe pretty maybe not"
| change class earlier later |
(listIndex = 0
or: [changeList size < listIndex])
ifTrue: [^ ''].
change := changeList at: listIndex.
later := change text.
class := change methodClass.
(listIndex == changeList size or: [class == nil])
ifTrue: [^ later].
earlier := (changeList at: listIndex + 1) text.
^ TextDiffBuilder buildDisplayPatchFrom: earlier to: later inClass: class prettyDiffs: self showingPrettyDiffs!
----- Method: ChangeList>>file (in category 'accessing') -----
file
^file!
----- Method: ChangeList>>fileInSelections (in category 'menu actions') -----
fileInSelections
| any |
any := false.
listSelections with: changeList do:
[:selected :item | selected ifTrue: [any := true. item fileIn]].
any ifFalse:
[self inform: 'nothing selected, so nothing done']!
----- Method: ChangeList>>fileOutSelections (in category 'menu actions') -----
fileOutSelections
| fileName internalStream |
fileName := UIManager default request: 'Enter the base of file name' initialAnswer: 'Filename'.
internalStream := WriteStream on: (String new: 1000).
internalStream header; timeStamp.
listSelections with: changeList do:
[:selected :item | selected ifTrue: [item fileOutOn: internalStream]].
FileStream writeSourceCodeFrom: internalStream baseName: fileName isSt: true useHtml: false.
!
----- Method: ChangeList>>initialize (in category 'initialization-release') -----
initialize
"Initialize a blank ChangeList. Set the contentsSymbol to reflect whether diffs will initally be shown or not"
contentsSymbol := Preferences diffsInChangeList
ifTrue:
[self defaultDiffsSymbol]
ifFalse:
[#source].
changeList := OrderedCollection new.
list := OrderedCollection new.
listIndex := 0.
super initialize!
----- Method: ChangeList>>invertSelections (in category 'menu actions') -----
invertSelections
"Invert the selectedness of each item in the changelist"
listSelections := listSelections collect: [ :ea | ea not].
listIndex := 0.
self changed: #allSelections.
self contentsChanged!
----- Method: ChangeList>>list (in category 'viewing access') -----
list
^ list!
----- Method: ChangeList>>listHasSingleEntry (in category 'accessing') -----
listHasSingleEntry
"does the list of changes have only a single item?"
^list size = 1!
----- Method: ChangeList>>listIndex (in category 'viewing access') -----
listIndex
^ listIndex!
----- Method: ChangeList>>listSelectionAt: (in category 'viewing access') -----
listSelectionAt: index
^ listSelections at: index!
----- Method: ChangeList>>listSelectionAt:put: (in category 'viewing access') -----
listSelectionAt: index put: value
^ listSelections at: index put: value!
----- Method: ChangeList>>listSelections (in category 'accessing') -----
listSelections
listSelections ifNil: [
list ifNotNil: [
listSelections := Array new: list size withAll: false]].
^ listSelections!
----- Method: ChangeList>>optionalButtonHeight (in category 'initialization-release') -----
optionalButtonHeight
^ 15!
----- Method: ChangeList>>perform:orSendTo: (in category 'menu actions') -----
perform: selector orSendTo: otherTarget
"Selector was just chosen from a menu by a user. If I can respond, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked."
(#accept == selector) ifTrue:
[otherTarget isMorph ifFalse: [^ self acceptFrom: otherTarget view]].
"weird special case just for mvc changlist"
^ super perform: selector orSendTo: otherTarget!
----- Method: ChangeList>>removeDoIts (in category 'menu actions') -----
removeDoIts
"Remove doits from the receiver, other than initializes. 1/26/96 sw"
| newChangeList newList |
newChangeList := OrderedCollection new.
newList := OrderedCollection new.
changeList with: list do:
[:chRec :str |
(chRec type ~~ #doIt or:
[str endsWith: 'initialize'])
ifTrue:
[newChangeList add: chRec.
newList add: str]].
newChangeList size < changeList size
ifTrue:
[changeList := newChangeList.
list := newList.
listIndex := 0.
listSelections := Array new: list size withAll: false].
self changed: #list.
!
----- Method: ChangeList>>removeExistingMethodVersions (in category 'menu actions') -----
removeExistingMethodVersions
"Remove all up to date version of entries from the receiver"
| newChangeList newList str keep cls sel |
newChangeList := OrderedCollection new.
newList := OrderedCollection new.
changeList with: list do:[:chRec :strNstamp |
keep := true.
(cls := chRec methodClass) ifNotNil:[
str := chRec string.
sel := cls parserClass new parseSelector: str.
keep := (cls sourceCodeAt: sel ifAbsent:['']) asString ~= str.
].
keep ifTrue:[
newChangeList add: chRec.
newList add: strNstamp]].
newChangeList size < changeList size
ifTrue:
[changeList := newChangeList.
list := newList.
listIndex := 0.
listSelections := Array new: list size withAll: false].
self changed: #list!
----- Method: ChangeList>>removeNonSelections (in category 'menu actions') -----
removeNonSelections
"Remove the unselected items from the receiver."
| newChangeList newList |
newChangeList := OrderedCollection new.
newList := OrderedCollection new.
1 to: changeList size do:
[:i | (listSelections at: i) ifTrue:
[newChangeList add: (changeList at: i).
newList add: (list at: i)]].
newChangeList size == 0 ifTrue:
[^ self inform: 'That would remove everything.
Why would you want to do that?'].
newChangeList size < changeList size
ifTrue:
[changeList := newChangeList.
list := newList.
listIndex := 0.
listSelections := Array new: list size withAll: false].
self changed: #list
!
----- Method: ChangeList>>removeOlderMethodVersions (in category 'menu actions') -----
removeOlderMethodVersions
"Remove older versions of entries from the receiver."
| newChangeList newList found str |
newChangeList := OrderedCollection new.
newList := OrderedCollection new.
found := OrderedCollection new.
changeList reverseWith: list do:
[:chRec :strNstamp | str := strNstamp copyUpTo: $;.
(found includes: str)
ifFalse:
[found add: str.
newChangeList add: chRec.
newList add: strNstamp]].
newChangeList size < changeList size
ifTrue:
[changeList := newChangeList reversed.
list := newList reversed.
listIndex := 0.
listSelections := Array new: list size withAll: false].
self changed: #list!
----- Method: ChangeList>>removeSelections (in category 'menu actions') -----
removeSelections
"Remove the selected items from the receiver. 9/18/96 sw"
| newChangeList newList |
newChangeList := OrderedCollection new.
newList := OrderedCollection new.
1 to: changeList size do:
[:i | (listSelections at: i) ifFalse:
[newChangeList add: (changeList at: i).
newList add: (list at: i)]].
newChangeList size < changeList size
ifTrue:
[changeList := newChangeList.
list := newList.
listIndex := 0.
listSelections := Array new: list size withAll: false].
self changed: #list
!
----- Method: ChangeList>>restoreDeletedMethod (in category 'viewing access') -----
restoreDeletedMethod
"If lostMethodPointer is not nil, then this is a version browser for a method that has been removed. In this case we want to establish a sourceCode link to prior versions. We do this by installing a dummy method with the correct source code pointer prior to installing this version."
| dummyMethod class selector |
dummyMethod := CompiledMethod toReturnSelf setSourcePointer: lostMethodPointer.
class := (changeList at: listIndex) methodClass.
selector := (changeList at: listIndex) methodSelector.
class addSelectorSilently: selector withMethod: dummyMethod.
(changeList at: listIndex) fileIn.
"IF for some reason, the dummy remains, remove it, but (N.B.!!) we might not get control back if the compile (fileIn above) fails."
(class compiledMethodAt: selector) == dummyMethod
ifTrue: [class basicRemoveSelector: selector].
^ true!
----- Method: ChangeList>>scanCategory (in category 'scanning') -----
scanCategory
"Scan anything that involves more than one chunk; method name is historical only"
| itemPosition item tokens stamp isComment anIndex |
itemPosition := file position.
item := file nextChunk.
isComment := (item includesSubString: 'commentStamp:').
(isComment or: [item includesSubString: 'methodsFor:']) ifFalse:
["Maybe a preamble, but not one we recognize; bail out with the preamble trick"
^ self addItem: (ChangeRecord new file: file position: itemPosition type: #preamble)
text: ('preamble: ' , item contractTo: 50)].
tokens := Scanner new scanTokens: item.
tokens size >= 3 ifTrue:
[stamp := ''.
anIndex := tokens indexOf: #stamp: ifAbsent: [nil].
anIndex ifNotNil: [stamp := tokens at: (anIndex + 1)].
tokens second == #methodsFor:
ifTrue: [^ self scanCategory: tokens third class: tokens first
meta: false stamp: stamp].
tokens third == #methodsFor:
ifTrue: [^ self scanCategory: tokens fourth class: tokens first
meta: true stamp: stamp]].
tokens second == #commentStamp:
ifTrue:
[stamp := tokens third.
self addItem:
(ChangeRecord new file: file position: file position type: #classComment
class: tokens first category: nil meta: false stamp: stamp)
text: 'class comment for ' , tokens first,
(stamp isEmpty ifTrue: [''] ifFalse: ['; ' , stamp]).
file nextChunk.
^ file skipStyleChunk]!
----- Method: ChangeList>>scanCategory:class:meta:stamp: (in category 'scanning') -----
scanCategory: category class: class meta: meta stamp: stamp
| itemPosition method |
[itemPosition := file position.
method := file nextChunk.
file skipStyleChunk.
method size > 0] "done when double terminators"
whileTrue:
[self addItem: (ChangeRecord new file: file position: itemPosition type: #method
class: class category: category meta: meta stamp: stamp)
text: 'method: ' , class , (meta ifTrue: [' class '] ifFalse: [' '])
, (self class parserClass new parseSelector: method)
, (stamp isEmpty ifTrue: [''] ifFalse: ['; ' , stamp])]!
----- Method: ChangeList>>scanFile:from:to: (in category 'scanning') -----
scanFile: aFile from: startPosition to: stopPosition
| itemPosition item prevChar |
file := aFile.
changeList := OrderedCollection new.
list := OrderedCollection new.
listIndex := 0.
file position: startPosition.
'Scanning ', aFile localName, '...'
displayProgressAt: Sensor cursorPoint
from: startPosition to: stopPosition
during: [:bar |
[file position < stopPosition]
whileTrue:
[bar value: file position.
[file atEnd not and: [file peek isSeparator]]
whileTrue: [prevChar := file next].
(file peekFor: $!!)
ifTrue:
[(prevChar = Character cr or: [prevChar = Character lf])
ifTrue: [self scanCategory]]
ifFalse:
[itemPosition := file position.
item := file nextChunk.
file skipStyleChunk.
item size > 0 ifTrue:
[self addItem: (ChangeRecord new file: file position: itemPosition type: #doIt)
text: 'do it: ' , (item contractTo: 50)]]]].
listSelections := Array new: list size withAll: false!
----- Method: ChangeList>>selectAll (in category 'menu actions') -----
selectAll
listIndex := 0.
listSelections atAllPut: true.
self changed: #allSelections!
----- Method: ChangeList>>selectAllConflicts (in category 'menu actions') -----
selectAllConflicts
"Selects all method definitions in the receiver which are also in any existing change set in the system. This makes no statement about whether the content of the methods differ, only whether there is a change represented."
| aClass aChange |
Cursor read showWhile:
[1 to: changeList size do:
[:i | aChange := changeList at: i.
listSelections at: i put:
(aChange type = #method
and: [(aClass := aChange methodClass) notNil
and: [ChangesOrganizer doesAnyChangeSetHaveClass: aClass andSelector: aChange methodSelector]])]].
self changed: #allSelections!
----- Method: ChangeList>>selectConflicts (in category 'menu actions') -----
selectConflicts
"Selects all method definitions for which there is ALSO an entry in changes"
| change class |
Cursor read showWhile:
[1 to: changeList size do:
[:i | change := changeList at: i.
listSelections at: i put:
(change type = #method
and: [(class := change methodClass) notNil
and: [(ChangeSet current atSelector: change methodSelector
class: class) ~~ #none]])]].
self changed: #allSelections!
----- Method: ChangeList>>selectConflicts: (in category 'menu actions') -----
selectConflicts: changeSetOrList
"Selects all method definitions for which there is ALSO an entry in the specified changeSet or changList"
| change class systemChanges |
Cursor read showWhile:
[(changeSetOrList isKindOf: ChangeSet) ifTrue: [
1 to: changeList size do:
[:i | change := changeList at: i.
listSelections at: i put:
(change type = #method
and: [(class := change methodClass) notNil
and: [(changeSetOrList atSelector: change methodSelector
class: class) ~~ #none]])]]
ifFalse: ["a ChangeList"
1 to: changeList size do:
[:i | change := changeList at: i.
listSelections at: i put:
(change type = #method
and: [(class := change methodClass) notNil
and: [changeSetOrList list includes: (list at: i)]])]]
].
self changed: #allSelections!
----- Method: ChangeList>>selectConflictsWith (in category 'menu actions') -----
selectConflictsWith
"Selects all method definitions for which there is ALSO an entry in the specified changeSet or changList chosen by the user. 4/11/96 tk"
| aStream all index |
aStream := WriteStream on: (String new: 200).
(all := ChangesOrganizer allChangeSets copy) do:
[:sel | aStream nextPutAll: (sel name contractTo: 40); cr].
ChangeList allSubInstancesDo:
[:sel | aStream nextPutAll: (sel file name); cr.
all addLast: sel].
aStream skip: -1.
index := (UIManager default chooseFrom: (aStream contents substrings)).
index > 0 ifTrue: [
self selectConflicts: (all at: index)].
!
----- Method: ChangeList>>selectMethodsForThisClass (in category 'menu actions') -----
selectMethodsForThisClass
| name |
self currentChange ifNil: [ ^self ].
name := self currentChange methodClassName.
name ifNil: [ ^self ].
^self selectSuchThat: [ :change |
change methodClassName = name ].!
----- Method: ChangeList>>selectNewMethods (in category 'menu actions') -----
selectNewMethods
"Selects all method definitions for which there is no counterpart method in the current image"
| change class |
Cursor read showWhile:
[1 to: changeList size do:
[:i | change := changeList at: i.
listSelections at: i put:
((change type = #method and:
[((class := change methodClass) isNil) or:
[(class includesSelector: change methodSelector) not]]))]].
self changed: #allSelections!
----- Method: ChangeList>>selectSuchThat (in category 'menu actions') -----
selectSuchThat
"query the user for a selection criterio. By Lex Spoon. NB: the UI for invoking this from a changelist browser is currently commented out; to reenfranchise it, you'll need to mild editing to ChangeList method #changeListMenu:"
| code block |
code := UIManager default request: 'selection criteria for a change named aChangeRecord?\For instance, ''aChangeRecord category = ''System-Network''''' withCRs.
code isEmpty ifTrue: [^ self ].
block := Compiler evaluate: '[:aChangeRecord | ', code, ']'.
self selectSuchThat: block!
----- Method: ChangeList>>selectSuchThat: (in category 'menu actions') -----
selectSuchThat: aBlock
"select all changes for which block returns true"
listSelections := changeList collect: [ :change | aBlock value: change ].
self changed: #allSelections!
----- Method: ChangeList>>selectUnchangedMethods (in category 'menu actions') -----
selectUnchangedMethods
"Selects all method definitions for which there is already a method in the current image, whose source is exactly the same. 9/18/96 sw"
| change class |
Cursor read showWhile:
[1 to: changeList size do:
[:i | change := changeList at: i.
listSelections at: i put:
((change type = #method and:
[(class := change methodClass) notNil]) and:
[(class includesSelector: change methodSelector) and:
[change string withBlanksCondensed = (class sourceCodeAt: change methodSelector) asString withBlanksCondensed ]])]].
self changed: #allSelections!
----- Method: ChangeList>>selectedClass (in category 'viewing access') -----
selectedClass
^(self selectedClassOrMetaClass ifNil: [ ^nil ]) theNonMetaClass !
----- Method: ChangeList>>selectedClassOrMetaClass (in category 'viewing access') -----
selectedClassOrMetaClass
| c |
^ (c := self currentChange) ifNotNil: [c methodClass]!
----- Method: ChangeList>>selectedMessageName (in category 'viewing access') -----
selectedMessageName
| c |
^ (c := self currentChange) ifNotNil: [c methodSelector]!
----- Method: ChangeList>>setLostMethodPointer: (in category 'accessing') -----
setLostMethodPointer: sourcePointer
lostMethodPointer := sourcePointer!
----- Method: ChangeList>>showsVersions (in category 'accessing') -----
showsVersions
^ false!
----- Method: ChangeList>>toggleListIndex: (in category 'viewing access') -----
toggleListIndex: newListIndex
listIndex ~= 0 ifTrue: [listSelections at: listIndex put: false].
newListIndex ~= 0 ifTrue: [listSelections at: newListIndex put: true].
listIndex := newListIndex.
self changed: #listIndex.
self contentsChanged!
----- Method: ChangeList>>undiffedContents (in category 'viewing access') -----
undiffedContents
^ listIndex = 0
ifTrue: ['']
ifFalse: [(changeList at: listIndex) text]!
----- Method: ChangeList>>wantsPrettyDiffOption (in category 'initialization-release') -----
wantsPrettyDiffOption
"Answer whether pretty-diffs are meaningful for this tool"
^ true!
ChangeList subclass: #ChangeListForProjects
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!ChangeListForProjects commentStamp: '<historical>' prior: 0!
A ChangeList that looks at the changes in a revokable project. This class has no users at present.!
----- Method: ChangeListForProjects>>contents (in category 'contents') -----
contents
^ self showingAnyKindOfDiffs
ifFalse: [self undiffedContents]
ifTrue: [self currentDiffedFromContents]
"Current is writing over one in list. Show how I would change it"!
----- Method: ChangeListForProjects>>currentDiffedFromContents (in category 'contents') -----
currentDiffedFromContents
"Answer the current in-memory method diffed from the current contents"
| aChange aClass |
listIndex = 0
ifTrue: [^ ''].
aChange := changeList at: listIndex.
^ ((aChange type == #method
and: [(aClass := aChange methodClass) notNil])
and: [aClass includesSelector: aChange methodSelector])
ifTrue: [TextDiffBuilder
buildDisplayPatchFrom: aChange text
to: (aClass sourceCodeAt: aChange methodSelector)
inClass: aClass
prettyDiffs: self showingPrettyDiffs]
ifFalse: [(changeList at: listIndex) text]!
ChangeList subclass: #VersionsBrowser
instanceVariableNames: 'classOfMethod selectorOfMethod addedChangeRecord'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!VersionsBrowser commentStamp: 'nk 11/25/2003 10:04' prior: 0!
VersionsBrowser shows all the versions of a particular method, and lets you compare them, revert to selected versions, and so on.!
VersionsBrowser subclass: #ClassCommentVersionsBrowser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!ClassCommentVersionsBrowser commentStamp: 'asm 8/13/2002 23:20' prior: 0!
A class-comment-versions-browser tool!
----- Method: ClassCommentVersionsBrowser class>>browseCommentOf: (in category 'instance creation') -----
browseCommentOf: class
| changeList |
Cursor read showWhile:
[changeList := self new scanVersionsOf: class.
changeList ifNil: [^ self inform: 'No versions available'].
self open: changeList name: 'Recent versions of ',class name,'''s comments' multiSelect: false ]
!
----- Method: ClassCommentVersionsBrowser class>>commentRecordsOf: (in category 'utilities') -----
commentRecordsOf: aClass
"Return a list of ChangeRecords for all versions of the method at selector. Source code can be retrieved by sending string to any one. Return nil if the method is absent."
| aList |
aList := self new
scanVersionsOf: aClass.
^ aList ifNotNil: [aList changeList]!
----- Method: ClassCommentVersionsBrowser class>>timeStampFor:class:reverseOrdinal: (in category 'utilities') -----
timeStampFor: aSelector class: aClass reverseOrdinal: anInteger
"Answer the time stamp corresponding to some version of the given method, nil if none. The reverseOrdinal parameter is interpreted as: 1 = current version; 2 = last-but-one version, etc."
| aChangeList |
aChangeList := self new scanVersionsOf: aClass.
^ aChangeList ifNil: [nil] ifNotNil:
[aChangeList list size >= anInteger
ifTrue:
[(aChangeList changeList at: anInteger) stamp]
ifFalse:
[nil]]!
----- Method: ClassCommentVersionsBrowser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Class Comment Versions Browser' brightColor: #(0.769 0.653 1.0) pastelColor: #(0.819 0.753 1.0) helpMessage: 'A tool for viewing prior versions of a class comment.'!
----- Method: ClassCommentVersionsBrowser>>classCommentIndicated (in category 'misc') -----
classCommentIndicated
"Answer whether the receiver is pointed at a class comment"
^ true!
----- Method: ClassCommentVersionsBrowser>>compareToCurrentVersion (in category 'menu') -----
compareToCurrentVersion
"If the current selection corresponds to a method in the system, then spawn a window showing the diffs as text"
| change s1 s2 |
listIndex = 0
ifTrue: [^ self].
change := changeList at: listIndex.
s1 := classOfMethod organization classComment.
s2 := change string.
s1 = s2
ifTrue: [^ self inform: 'Exact Match'].
(StringHolder new
textContents: (TextDiffBuilder buildDisplayPatchFrom: s1 to: s2 inClass: classOfMethod prettyDiffs: self showingPrettyDiffs))
openLabel: 'Comparison to Current Version'!
----- Method: ClassCommentVersionsBrowser>>contentsSymbolQuints (in category 'misc') -----
contentsSymbolQuints
"Answer a list of quintuplets representing information on the alternative views available in the code pane"
^ #(
(source togglePlainSource showingPlainSourceString 'source' 'the textual source code as writen')
(showDiffs toggleRegularDiffing showingRegularDiffsString 'showDiffs' 'the textual source diffed from its prior version'))!
----- Method: ClassCommentVersionsBrowser>>diffedVersionContents (in category 'basic function') -----
diffedVersionContents
"Answer diffed version contents, maybe pretty maybe not"
| change class earlier later |
(listIndex = 0
or: [changeList size < listIndex])
ifTrue: [^ ''].
change := changeList at: listIndex.
later := change text.
class := self selectedClass.
(listIndex == changeList size or: [class == nil])
ifTrue: [^ later].
earlier := (changeList at: listIndex + 1) text.
^ TextDiffBuilder buildDisplayPatchFrom: earlier to: later inClass: class prettyDiffs: self showingPrettyDiffs!
----- Method: ClassCommentVersionsBrowser>>offerVersionsHelp (in category 'menu') -----
offerVersionsHelp
(StringHolder new contents: self versionsHelpString)
openLabel: 'Class Comment Versions Browsers'!
----- Method: ClassCommentVersionsBrowser>>openSingleMessageBrowser (in category 'menu') -----
openSingleMessageBrowser
| mr |
"Create and schedule a message list browser populated only by the currently selected message"
mr := MethodReference new
setStandardClass: self selectedClass
methodSymbol: #Comment.
self systemNavigation
browseMessageList: (Array with: mr)
name: mr asStringOrText
autoSelect: nil!
----- Method: ClassCommentVersionsBrowser>>priorSourceOrNil (in category 'misc') -----
priorSourceOrNil
"If the currently-selected method has a previous version, return its source, else return nil"
| aClass aSelector changeRecords |
(aClass := self selectedClass) ifNil: [^ nil].
(aSelector := self selectedMessageName) ifNil: [^ nil].
changeRecords := self class commentRecordsOf: self selectedClass.
(changeRecords == nil or: [changeRecords size <= 1]) ifTrue: [^ nil].
^ (changeRecords at: 2) string
!
----- Method: ClassCommentVersionsBrowser>>reformulateList (in category 'basic function') -----
reformulateList
classOfMethod organization classComment ifNil: [^ self].
self scanVersionsOf: classOfMethod.
self changed: #list. "for benefit of mvc"
listIndex := 1.
self changed: #listIndex.
self contentsChanged!
----- Method: ClassCommentVersionsBrowser>>scanVersionsOf: (in category 'basic function') -----
scanVersionsOf: class
"Scan for all past versions of the class comment of the given class"
| oldCommentRemoteStr sourceFilesCopy position prevPos stamp preamble tokens prevFileIndex |
classOfMethod := class.
oldCommentRemoteStr := class organization commentRemoteStr.
currentCompiledMethod := oldCommentRemoteStr.
selectorOfMethod := #Comment.
changeList := OrderedCollection new.
list := OrderedCollection new.
listIndex := 0.
oldCommentRemoteStr ifNil:[^ nil] ifNotNil: [oldCommentRemoteStr sourcePointer].
sourceFilesCopy := SourceFiles collect:
[:x | x isNil ifTrue: [ nil ]
ifFalse: [x readOnlyCopy]].
position := oldCommentRemoteStr position.
file := sourceFilesCopy at: oldCommentRemoteStr sourceFileNumber.
[position notNil & file notNil]
whileTrue:
[file position: (0 max: position-150). " Skip back to before the preamble"
[file position < (position-1)] "then pick it up from the front"
whileTrue: [preamble := file nextChunk].
prevPos := nil.
stamp := ''.
(preamble findString: 'commentStamp:' startingAt: 1) > 0
ifTrue: [tokens := Scanner new scanTokens: preamble.
(tokens at: tokens size-3) = #commentStamp:
ifTrue: ["New format gives change stamp and unified prior pointer"
stamp := tokens at: tokens size-2.
prevPos := tokens last.
prevFileIndex := sourceFilesCopy fileIndexFromSourcePointer: prevPos.
prevPos := sourceFilesCopy filePositionFromSourcePointer: prevPos]]
ifFalse: ["The stamp get lost, maybe after a condenseChanges"
stamp := '<historical>'].
self addItem:
(ChangeRecord new file: file position: position type: #classComment
class: class name category: nil meta: class stamp: stamp)
text: stamp , ' ' , class name , ' class comment'.
prevPos = 0 ifTrue:[prevPos := nil].
position := prevPos.
prevPos notNil
ifTrue:[file := sourceFilesCopy at: prevFileIndex]].
sourceFilesCopy do: [:x | x notNil ifTrue: [x close]].
listSelections := Array new: list size withAll: false!
----- Method: ClassCommentVersionsBrowser>>selectedClass (in category 'misc') -----
selectedClass
"Answer the class currently selected in the browser. In the case of a VersionsBrowser, the class and selector are always the same, regardless of which version is selected and indeed whether or not any entry is selected in the list pane"
^ classOfMethod!
----- Method: ClassCommentVersionsBrowser>>updateListsAndCodeIn: (in category 'basic function') -----
updateListsAndCodeIn: aWindow
| aComment |
aComment := classOfMethod organization commentRemoteStr.
aComment == currentCompiledMethod
ifFalse:
["Do not attempt to formulate if there is no source pointer.
It probably means it has been recompiled, but the source hasn't been written
(as during a display of the 'save text simply?' confirmation)."
aComment last ~= 0 ifTrue: [self reformulateList]].
^ true
!
----- Method: ClassCommentVersionsBrowser>>versionsMenu: (in category 'menu') -----
versionsMenu: aMenu
"Fill aMenu with menu items appropriate to the receiver"
Smalltalk isMorphic ifTrue:
[aMenu title: 'versions'.
aMenu addStayUpItemSpecial].
^ aMenu addList: #(
('compare to current' compareToCurrentVersion 'compare selected version to the current version')
('revert to selected version' fileInSelections 'resubmit the selected version, so that it becomes the current version')
('remove from changes' removeMethodFromChanges 'remove this method from the current change set, if present')
('edit current method (O)' openSingleMessageBrowser 'open a single-message browser on the current version of this method')
-
('toggle diffing (D)' toggleDiffing 'toggle whether or not diffs should be shown here')
('update list' reformulateList 'reformulate the list of versions, in case it somehow got out of synch with reality')
-
('help...' offerVersionsHelp 'provide an explanation of the use of this tool'))
!
----- Method: ClassCommentVersionsBrowser>>wantsPrettyDiffOption (in category 'misc') -----
wantsPrettyDiffOption
"Answer whether pretty-diffs are meaningful for this tool"
^ false!
----- Method: VersionsBrowser class>>browseVersionsForClass:selector: (in category 'instance creation') -----
browseVersionsForClass: aClass selector: aSelector
self
browseVersionsOf: (aClass compiledMethodAt: aSelector)
class: aClass
meta: aClass isMeta
category: (aClass organization categoryOfElement: aSelector)
selector: aSelector!
----- Method: VersionsBrowser class>>browseVersionsOf:class:meta:category:selector: (in category 'instance creation') -----
browseVersionsOf: method class: class meta: meta category: msgCategory selector: selector
^ self browseVersionsOf: method class: class meta: meta category: msgCategory selector: selector lostMethodPointer: nil!
----- Method: VersionsBrowser class>>browseVersionsOf:class:meta:category:selector:lostMethodPointer: (in category 'instance creation') -----
browseVersionsOf: method class: class meta: meta category: msgCategory selector: selector lostMethodPointer: sourcePointer
| changeList browser |
Cursor read showWhile:
[changeList := (browser := self new)
scanVersionsOf: method class: class meta: meta
category: msgCategory selector: selector].
changeList ifNil: [ self inform: 'No versions available'. ^nil ].
sourcePointer ifNotNil:
[changeList setLostMethodPointer: sourcePointer].
self open: changeList name: 'Recent versions of ' ,
selector multiSelect: false.
^browser!
----- Method: VersionsBrowser class>>timeStampFor:class:reverseOrdinal: (in category 'instance creation') -----
timeStampFor: aSelector class: aClass reverseOrdinal: anInteger
"Answer the time stamp corresponding to some version of the given method, nil if none. The reverseOrdinal parameter is interpreted as: 1 = current version; 2 = last-but-one version, etc."
| method aChangeList |
method := aClass compiledMethodAt: aSelector ifAbsent: [^ nil].
aChangeList := self new
scanVersionsOf: method class: aClass meta: aClass isMeta
category: nil selector: aSelector.
^ aChangeList ifNil: [nil] ifNotNil:
[aChangeList list size >= anInteger
ifTrue:
[(aChangeList changeList at: anInteger) stamp]
ifFalse:
[nil]]!
----- Method: VersionsBrowser class>>versionCountForSelector:class: (in category 'instance creation') -----
versionCountForSelector: aSelector class: aClass
"Answer the number of versions known to the system for the given class and method, including the current version. A result of greater than one means that there is at least one superseded version. Answer zero if no logged version can be obtained."
| method aChangeList |
method := aClass compiledMethodAt: aSelector ifAbsent: [^ 0].
aChangeList := self new
scanVersionsOf: method class: aClass meta: aClass isMeta
category: nil selector: aSelector.
^ aChangeList ifNil: [0] ifNotNil: [aChangeList list size]!
----- Method: VersionsBrowser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Versions Browser' brightColor: #(0.869 0.753 1.0) pastelColor: #(0.919 0.853 1.0) helpMessage: 'A tool for viewing prior versions of a method.'!
----- Method: VersionsBrowser>>addPriorVersionsCountForSelector:ofClass:to: (in category 'misc') -----
addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream
"Add an annotation detailing the prior versions count. Specially handled here for the case of a selector no longer in the system, whose prior versions are seen in a versions browser -- in this case, the inherited version of this method will not work."
(aClass includesSelector: aSelector) ifTrue:
[^ super addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream].
aStream nextPutAll:
((changeList size > 0
ifTrue:
[changeList size == 1
ifTrue:
['Deleted - one prior version']
ifFalse:
['Deleted - ', changeList size printString, ' prior versions']]
ifFalse:
['surprisingly, no prior versions']), self annotationSeparator)!
----- Method: VersionsBrowser>>addedChangeRecord (in category 'init & update') -----
addedChangeRecord
^addedChangeRecord!
----- Method: VersionsBrowser>>addedChangeRecord: (in category 'init & update') -----
addedChangeRecord: aChangeRecord
addedChangeRecord := aChangeRecord.
self reformulateList.!
----- Method: VersionsBrowser>>changeListButtonSpecs (in category 'init & update') -----
changeListButtonSpecs
^#(
('compare to current'
compareToCurrentVersion
'opens a separate window which shows the text differences between the selected version and the current version')
('revert'
fileInSelections
'reverts the method to the version selected')
('remove from changes'
removeMethodFromChanges
'remove this method from the current change set')
('help'
offerVersionsHelp
'further explanation about use of Versions browsers')
)!
----- Method: VersionsBrowser>>changeListKey:from: (in category 'menu') -----
changeListKey: aChar from: view
"Respond to a Command key in the list pane. of the versions browser"
^ self messageListKey: aChar from: view!
----- Method: VersionsBrowser>>classCommentIndicated (in category 'misc') -----
classCommentIndicated
"Answer whether the receiver is pointed at a class comment"
^ false!
----- Method: VersionsBrowser>>compareToOtherVersion (in category 'menu') -----
compareToOtherVersion
"Prompt the user for a reference version, then spawn a window
showing the diffs between the older and the newer of the current
version and the reference version as text."
| change1 change2 s1 s2 |
change1 := changeList at: listIndex ifAbsent: [ ^self ].
change2 := ((UIManager default
chooseFrom: (list copyWithoutIndex: listIndex)
values: (changeList copyWithoutIndex: listIndex))) ifNil: [ ^self ].
"compare earlier -> later"
"change1 timeStamp < change2 timeStamp
ifFalse: [ | temp | temp := change1. change1 := change2. change2 := temp ]."
s1 := change1 string.
s2 := change2 string.
s1 = s2
ifTrue: [^ self inform: 'Exact Match' translated].
(StringHolder new
textContents: (TextDiffBuilder
buildDisplayPatchFrom: s1
to: s2
inClass: classOfMethod
prettyDiffs: self showingPrettyDiffs))
openLabel: (('Comparison from {1} to {2}' translated) format: { change1 stamp. change2 stamp })!
----- Method: VersionsBrowser>>fileInSelections (in category 'menu') -----
fileInSelections
super fileInSelections.
self reformulateList!
----- Method: VersionsBrowser>>findOriginalChangeSet (in category 'menu') -----
findOriginalChangeSet
| changeSet |
self currentChange ifNil: [^ self].
changeSet := self currentChange originalChangeSetForSelector: self selectedMessageName.
changeSet = #sources ifTrue:
[^ self inform: 'This version is in the .sources file.'].
changeSet ifNil:
[^ self inform: 'This version was not found in any changeset nor in the .sources file.'].
(ChangeSorter new myChangeSet: changeSet) open!
----- Method: VersionsBrowser>>initialExtent (in category 'user interface') -----
initialExtent
"adopt the extent so that the revert button fits in"
^650 at 400!
----- Method: VersionsBrowser>>offerVersionsHelp (in category 'menu') -----
offerVersionsHelp
(StringHolder new contents: self versionsHelpString)
openLabel: 'Versions Browsers'!
----- Method: VersionsBrowser>>optionalButtonPairs (in category 'toolbuilder') -----
optionalButtonPairs
^ #(('revert' fileInSelections 'resubmit the selected version, so that it becomes the current version')) , super optionalButtonPairs!
----- Method: VersionsBrowser>>reformulateList (in category 'init & update') -----
reformulateList
| aMethod |
"Some uncertainty about how to deal with lost methods here"
aMethod := classOfMethod compiledMethodAt: selectorOfMethod ifAbsent: [^ self].
self scanVersionsOf: aMethod class: classOfMethod theNonMetaClass meta: classOfMethod isMeta category: (classOfMethod whichCategoryIncludesSelector: selectorOfMethod) selector: selectorOfMethod.
self changed: #list. "for benefit of mvc"
listIndex := 1.
self changed: #listIndex.
self contentsChanged
!
----- Method: VersionsBrowser>>removeMethodFromChanges (in category 'menu') -----
removeMethodFromChanges
"Remove my method from the current change set"
ChangeSet current removeSelectorChanges: selectorOfMethod class: classOfMethod.
self changed: #annotation
!
----- Method: VersionsBrowser>>scanVersionsOf:class:meta:category:selector: (in category 'init & update') -----
scanVersionsOf: method class: class meta: meta category: category selector: selector
| position prevPos prevFileIndex preamble tokens sourceFilesCopy stamp cat |
selectorOfMethod := selector.
currentCompiledMethod := method.
classOfMethod := meta ifTrue: [class class] ifFalse: [class].
cat := category ifNil: [''].
changeList := OrderedCollection new.
list := OrderedCollection new.
self addedChangeRecord ifNotNilDo: [ :change |
self addItem: change text: ('{1} (in {2})' translated format: { change stamp. change fileName }) ].
listIndex := 0.
position := method filePosition.
sourceFilesCopy := SourceFiles collect:
[:x | x isNil ifTrue: [ nil ]
ifFalse: [x readOnlyCopy]].
method fileIndex == 0 ifTrue: [^ nil].
file := sourceFilesCopy at: method fileIndex.
[position notNil & file notNil]
whileTrue:
[file position: (0 max: position-150). "Skip back to before the preamble"
preamble := method getPreambleFrom: file at: (0 max: position - 3).
"Preamble is likely a linked method preamble, if we're in
a changes file (not the sources file). Try to parse it
for prior source position and file index"
prevPos := nil.
stamp := ''.
(preamble findString: 'methodsFor:' startingAt: 1) > 0
ifTrue: [tokens := Scanner new scanTokens: preamble]
ifFalse: [tokens := Array new "ie cant be back ref"].
((tokens size between: 7 and: 8)
and: [(tokens at: tokens size-5) = #methodsFor:])
ifTrue:
[(tokens at: tokens size-3) = #stamp:
ifTrue: ["New format gives change stamp and unified prior pointer"
stamp := tokens at: tokens size-2.
prevPos := tokens last.
prevFileIndex := sourceFilesCopy fileIndexFromSourcePointer: prevPos.
prevPos := sourceFilesCopy filePositionFromSourcePointer: prevPos]
ifFalse: ["Old format gives no stamp; prior pointer in two parts"
prevPos := tokens at: tokens size-2.
prevFileIndex := tokens last].
cat := tokens at: tokens size-4.
(prevPos = 0 or: [prevFileIndex = 0]) ifTrue: [prevPos := nil]].
((tokens size between: 5 and: 6)
and: [(tokens at: tokens size-3) = #methodsFor:])
ifTrue:
[(tokens at: tokens size-1) = #stamp:
ifTrue: ["New format gives change stamp and unified prior pointer"
stamp := tokens at: tokens size].
cat := tokens at: tokens size-2].
self addItem:
(ChangeRecord new file: file position: position type: #method
class: class name category: category meta: meta stamp: stamp)
text: stamp , ' ' , class name , (meta ifTrue: [' class '] ifFalse: [' ']) , selector, ' {', cat, '}'.
position := prevPos.
prevPos notNil ifTrue:
[file := sourceFilesCopy at: prevFileIndex]].
sourceFilesCopy do: [:x | x notNil ifTrue: [x close]].
listSelections := Array new: list size withAll: false!
----- Method: VersionsBrowser>>selectedClass (in category 'misc') -----
selectedClass
"Answer the class currently selected in the browser. In the case of a VersionsBrowser, the class and selector are always the same, regardless of which version is selected and indeed whether or not any entry is selected in the list pane"
^ classOfMethod theNonMetaClass!
----- Method: VersionsBrowser>>selectedClassOrMetaClass (in category 'misc') -----
selectedClassOrMetaClass
"Answer the class or metaclass currently selected in the browser. In the case of a VersionsBrowser, the class and selector are always the same, regardless of which version is selected and indeed whether or not any entry is selected in the list pane"
^ classOfMethod!
----- Method: VersionsBrowser>>selectedMessageName (in category 'misc') -----
selectedMessageName
"Answer the message name currently selected in the browser. In the case of a VersionsBrowser, the class and selector are always the same, regardless of which version is selected and indeed whether or not any entry is selected in the list pane"
^ selectorOfMethod!
----- Method: VersionsBrowser>>showsVersions (in category 'misc') -----
showsVersions
^ true!
----- Method: VersionsBrowser>>updateListsAndCodeIn: (in category 'init & update') -----
updateListsAndCodeIn: aWindow
| aMethod |
aMethod := classOfMethod compiledMethodAt: selectorOfMethod ifAbsent: [^ false].
aMethod == currentCompiledMethod
ifFalse:
["Do not attempt to formulate if there is no source pointer.
It probably means it has been recompiled, but the source hasn't been written
(as during a display of the 'save text simply?' confirmation)."
aMethod last ~= 0 ifTrue: [self reformulateList]].
^ true
!
----- Method: VersionsBrowser>>versionFrom: (in category 'menu') -----
versionFrom: secsSince1901
| strings vTime |
"Return changeRecord of the version in effect at that time. Accept in the VersionsBrowser does not use this code."
changeList do: [:cngRec |
(strings := cngRec stamp findTokens: ' ') size > 2 ifTrue: [
vTime := strings second asDate asSeconds +
strings third asTime asSeconds.
vTime <= secsSince1901 ifTrue: ["this one"
^ cngRec == changeList first ifTrue: [nil] ifFalse: [cngRec]]]].
"was not defined that early. Don't delete the method."
^ changeList last "earliest one may be OK" !
----- Method: VersionsBrowser>>versionsHelpString (in category 'menu') -----
versionsHelpString
^ 'Each entry in the list pane represents a version of the source code for the same method; the topmost entry is the current version, the next entry is the next most recent, etc.
To revert to an earlier version, select it (in the list pane) and then do any of the following:
* Choose "revert to this version" from the list pane menu.
* Hit the "revert" button,
* Type ENTER in the code pane
* Type cmd-s (alt-s) in the code pane.
The code pane shows the source for the selected version. If "diffing" is in effect, then differences betwen the selected version and the version before it are pointed out in the pane. Turn diffing on and off by choosing "toggle diffing" from the list pane menu, or hitting the "diffs" button, or hitting cmd-D when the cursor is over the list pane.
To get a comparison between the selected version and the current version, choose "compare to current" from the list pane menu or hit the "compare to current" button. (This is meaningless if the current version is selected, and is unnecessary if you''re interested in diffs from between the current version and the next-most-recent version, since the standard in-pane "diff" feature will give you that.)
You can also compare the selected version with any other version using the "compare to version..." menu choice.
If further versions of the method in question have been submitted elsewhere since you launched a particular Versions Browser, it will still stay nicely up-to-date if you''re in Morphic and have asked that smart updating be maintained; if you''re in mvc or in morphic but with smart-updating turned off, a versions browser is only brought up to date when you activate its window (and when you issue "revert" from within it, of course,) and you can also use the "update list" command to make certain the versions list is up to date.
Hit the "remove from changes" button, or choose the corresponding command in the list pane menu, to have the method in question deleted from the current change set. This is useful if you''ve put debugging code into a method, and now want to strip it out and cleanse your current change set of all memory of the excursion.
Note: the annotation pane in versions browsers shows information about the *current* version of the method in the image, not about the selected version.'!
----- Method: VersionsBrowser>>versionsMenu: (in category 'menu') -----
versionsMenu: aMenu
"Fill aMenu with menu items appropriate to the receiver"
Smalltalk isMorphic ifTrue:
[aMenu title: 'Versions' translated.
aMenu addStayUpItemSpecial].
listIndex > 0 ifTrue:[
(list size > 1 ) ifTrue: [ aMenu addTranslatedList: #(
('compare to current' compareToCurrentVersion 'compare selected version to the current version')
('compare to version...' compareToOtherVersion 'compare selected version to another selected version'))].
"Note: Revert to selected should be visible for lists of length one for having the ability to revert to an accidentally deleted method"
aMenu addTranslatedList: #(
('revert to selected version' fileInSelections 'resubmit the selected version, so that it becomes the current version') )].
aMenu addTranslatedList: #(
('remove from changes' removeMethodFromChanges 'remove this method from the current change set, if present')
('edit current method (O)' openSingleMessageBrowser 'open a single-message browser on the current version of this method')
('find original change set' findOriginalChangeSet 'locate the changeset which originally contained this version')
-
('toggle diffing (D)' toggleDiffing 'toggle whether or not diffs should be shown here')
('update list' reformulateList 'reformulate the list of versions, in case it somehow got out of synch with reality')
-
('senders (n)' browseSenders 'browse all senders of this selector')
('implementors (m)' browseImplementors 'browse all implementors of this selector')
-
('help...' offerVersionsHelp 'provide an explanation of the use of this tool')).
^aMenu!
CodeHolder subclass: #ChangeSorter
instanceVariableNames: 'parent myChangeSet currentClassName currentSelector priorChangeSetList changeSetCategory'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!ChangeSorter commentStamp: '<historical>' prior: 0!
I display a ChangeSet. Two of me are in a DualChangeSorter.!
ChangeSorter subclass: #ChangeSetBrowser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!ChangeSetBrowser commentStamp: '<historical>' prior: 0!
A tool allowing you to browse the methods of a single change set.!
----- Method: ChangeSetBrowser>>addModelItemsToWindowMenu: (in category 'initialization') -----
addModelItemsToWindowMenu: aMenu
"Add model-related items to the given window menu"
| oldTarget |
oldTarget := aMenu defaultTarget.
aMenu defaultTarget: self.
aMenu addLine.
aMenu add: 'rename change set' action: #rename.
aMenu add: 'make changes go to me' action: #newCurrent.
aMenu addLine.
aMenu add: 'file out' action: #fileOut.
aMenu add: 'browse methods' action: #browseChangeSet.
aMenu addLine.
myChangeSet hasPreamble
ifTrue:
[aMenu add: 'edit preamble' action: #addPreamble.
aMenu add: 'remove preamble' action: #removePreamble]
ifFalse:
[aMenu add: 'add preamble' action: #addPreamble].
myChangeSet hasPostscript
ifTrue:
[aMenu add: 'edit postscript...' action: #editPostscript.
aMenu add: 'remove postscript' action: #removePostscript]
ifFalse:
[aMenu add: 'add postscript...' action: #editPostscript].
aMenu addLine.
aMenu add: 'destroy change set' action: #remove.
aMenu addLine.
Smalltalk isMorphic ifTrue:
[aMenu addLine.
aMenu add: 'what to show...' target: self action: #offerWhatToShowMenu].
aMenu addLine.
aMenu add: 'more...' action: #offerShiftedChangeSetMenu.
aMenu defaultTarget: oldTarget.
^ aMenu!
----- Method: ChangeSetBrowser>>offerUnshiftedChangeSetMenu (in category 'menu') -----
offerUnshiftedChangeSetMenu
"The user chose 'more' from the shifted window menu; go back to the regular window menu"
self containingWindow ifNotNil: [self containingWindow offerWindowMenu] !
----- Method: ChangeSetBrowser>>shiftedChangeSetMenu: (in category 'menu') -----
shiftedChangeSetMenu: aMenu
"Set up aMenu to hold items relating to the change-set-list pane when the shift key is down"
Smalltalk isMorphic ifTrue:
[aMenu title: 'Change set (shifted)'.
aMenu addStayUpItemSpecial].
aMenu add: 'conflicts with other change sets' action: #browseMethodConflicts.
aMenu balloonTextForLastItem:
'Browse all methods that occur both in this change set and in at least one other change set.'.
aMenu addLine.
aMenu add: 'check for slips' action: #lookForSlips.
aMenu balloonTextForLastItem:
'Check this change set for halts and references to Transcript.'.
aMenu add: 'check for unsent messages' action: #checkForUnsentMessages.
aMenu balloonTextForLastItem:
'Check this change set for messages that are not sent anywhere in the system'.
aMenu add: 'check for uncommented methods' action: #checkForUncommentedMethods.
aMenu balloonTextForLastItem:
'Check this change set for methods that do not have comments'.
aMenu add: 'check for uncommented classes' action: #checkForUncommentedClasses.
aMenu balloonTextForLastItem:
'Check for classes with code in this changeset which lack class comments'.
Utilities authorInitialsPerSe isEmptyOrNil ifFalse:
[aMenu add: 'check for other authors' action: #checkForAlienAuthorship.
aMenu balloonTextForLastItem:
'Check this change set for methods whose current authoring stamp does not start with "', Utilities authorInitials, '"'.
aMenu add: 'check for any other authors' action: #checkForAnyAlienAuthorship.
aMenu balloonTextForLastItem:
'Check this change set for methods any of whose previous authoring stamps do not start with "', Utilities authorInitials, '"'].
aMenu add: 'check for uncategorized methods' action: #checkForUnclassifiedMethods.
aMenu balloonTextForLastItem:
'Check to see if any methods in the selected change set have not yet been assigned to a category. If any are found, open a browser on them.'.
aMenu addLine.
aMenu add: 'inspect change set' action: #inspectChangeSet.
aMenu balloonTextForLastItem:
'Open an inspector on this change set. (There are some details in a change set which you don''t see in a change sorter.)'.
aMenu add: 'update' action: #update.
aMenu balloonTextForLastItem:
'Update the display for this change set. (This is done automatically when you activate this window, so is seldom needed.)'.
aMenu add: 'go to change set''s project' action: #goToChangeSetsProject.
aMenu balloonTextForLastItem:
'If this change set is currently associated with a Project, go to that project right now.'.
aMenu add: 'trim history' action: #trimHistory.
aMenu balloonTextForLastItem:
' Drops any methods added and then removed, as well as renaming and reorganization of newly-added classes. NOTE: can cause confusion if later filed in over an earlier version of these changes'.
aMenu add: 'clear this change set' action: #clearChangeSet.
aMenu balloonTextForLastItem:
'Reset this change set to a pristine state where it holds no information. CAUTION: this is destructive and irreversible!!'.
aMenu add: 'expunge uniclasses' action: #expungeUniclasses.
aMenu balloonTextForLastItem:
'Remove from the change set all memory of uniclasses, e.g. classes added on behalf of etoys, fabrik, etc., whose classnames end with a digit.'.
aMenu add: 'uninstall this change set' action: #uninstallChangeSet.
aMenu balloonTextForLastItem:
'Attempt to uninstall this change set. CAUTION: this may not work completely and is irreversible!!'.
aMenu addLine.
aMenu add: 'more...' action: #offerUnshiftedChangeSetMenu.
aMenu balloonTextForLastItem:
'Takes you back to the primary change-set menu.'.
^ aMenu!
----- Method: ChangeSetBrowser>>wantsAnnotationPane (in category 'initialization') -----
wantsAnnotationPane
"This kind of browser always wants annotation panes, so answer true"
^ true!
----- Method: ChangeSetBrowser>>wantsOptionalButtons (in category 'initialization') -----
wantsOptionalButtons
"Sure, why not?"
^ true!
----- Method: ChangeSorter class>>allChangeSetNames (in category 'deprecated') -----
allChangeSetNames
^ ChangesOrganizer allChangeSetNames!
----- Method: ChangeSorter class>>allChangeSets (in category 'deprecated') -----
allChangeSets
^ ChangesOrganizer allChangeSets!
----- Method: ChangeSorter class>>allChangeSetsWithClass:selector: (in category 'deprecated') -----
allChangeSetsWithClass: class selector: selector
^ ChangesOrganizer allChangeSetsWithClass: class selector: selector!
----- Method: ChangeSorter class>>assuredChangeSetNamed: (in category 'deprecated') -----
assuredChangeSetNamed: aName
^ ChangesOrganizer assuredChangeSetNamed: aName!
----- Method: ChangeSorter class>>basicNewChangeSet: (in category 'deprecated') -----
basicNewChangeSet: newName
^ ChangesOrganizer basicNewChangeSet: newName!
----- Method: ChangeSorter class>>belongsInAdditions: (in category 'deprecated') -----
belongsInAdditions: aChangeSet
^ ChangesOrganizer belongsInAdditions: aChangeSet!
----- Method: ChangeSorter class>>belongsInAll: (in category 'deprecated') -----
belongsInAll: aChangeSet
^ ChangesOrganizer belongsInAll: aChangeSet!
----- Method: ChangeSorter class>>belongsInMyInitials: (in category 'deprecated') -----
belongsInMyInitials: aChangeSet
^ ChangesOrganizer belongsInMyInitials: aChangeSet!
----- Method: ChangeSorter class>>belongsInNumbered: (in category 'deprecated') -----
belongsInNumbered: aChangeSet
^ ChangesOrganizer belongsInNumbered: aChangeSet!
----- Method: ChangeSorter class>>belongsInProjectChangeSets: (in category 'deprecated') -----
belongsInProjectChangeSets: aChangeSet
^ ChangesOrganizer belongsInProjectChangeSets: aChangeSet!
----- Method: ChangeSorter class>>belongsInProjectsInRelease: (in category 'deprecated') -----
belongsInProjectsInRelease: aChangeSet
^ ChangesOrganizer belongsInProjectsInRelease: aChangeSet!
----- Method: ChangeSorter class>>belongsInRecentUpdates: (in category 'deprecated') -----
belongsInRecentUpdates: aChangeSet
^ ChangesOrganizer belongsInRecentUpdates: aChangeSet!
----- Method: ChangeSorter class>>browseChangeSetsWithClass:selector: (in category 'browse') -----
browseChangeSetsWithClass: class selector: selector
"Put up a menu comprising a list of change sets that hold changes for the given class and selector. If the user selects one, open a single change-sorter onto it"
| hits index |
hits := self allChangeSets select:
[:cs | (cs atSelector: selector class: class) ~~ #none].
hits isEmpty ifTrue: [^ self inform: class name, '.', selector , '
is not in any change set'].
index := hits size == 1
ifTrue: [1]
ifFalse: [(UIManager default chooseFrom: (hits collect: [:cs | cs name])
lines: #())].
index = 0 ifTrue: [^ self].
(ChangeSorter new myChangeSet: (hits at: index)) open.
!
----- Method: ChangeSorter class>>browseChangeSetsWithSelector: (in category 'browse') -----
browseChangeSetsWithSelector: aSelector
"Put up a list of all change sets that contain an addition, deletion, or change of any method with the given selector"
| hits index |
hits := self allChangeSets select:
[:cs | cs hasAnyChangeForSelector: aSelector].
hits isEmpty ifTrue: [^ self inform: aSelector , '
is not in any change set'].
index := hits size == 1
ifTrue: [1]
ifFalse: [(UIManager default chooseFrom: (hits collect: [:cs | cs name])
lines: #())].
index = 0 ifTrue: [^ self].
(ChangeSetBrowser new myChangeSet: (hits at: index)) open
"ChangeSorter browseChangeSetsWithSelector: #clearPenTrails"
!
----- Method: ChangeSorter class>>buildAggregateChangeSet (in category 'deprecated') -----
buildAggregateChangeSet
^ ChangesOrganizer buildAggregateChangeSet
!
----- Method: ChangeSorter class>>changeSet:containsClass: (in category 'deprecated') -----
changeSet: aChangeSet containsClass: aClass
^ ChangesOrganizer changeSet: aChangeSet containsClass: aClass!
----- Method: ChangeSorter class>>changeSetCategoryNamed: (in category 'deprecated') -----
changeSetCategoryNamed: aName
^ ChangesOrganizer changeSetCategoryNamed: aName!
----- Method: ChangeSorter class>>changeSetNamed: (in category 'deprecated') -----
changeSetNamed: aName
^ ChangesOrganizer changeSetNamed: aName!
----- Method: ChangeSorter class>>changeSetNamesInReleaseImage (in category 'deprecated') -----
changeSetNamesInReleaseImage
^ ChangesOrganizer changeSetNamesInReleaseImage!
----- Method: ChangeSorter class>>changeSetNamesInThreeOh (in category 'deprecated') -----
changeSetNamesInThreeOh
^ ChangesOrganizer changeSetNamesInThreeOh!
----- Method: ChangeSorter class>>changeSetsNamedSuchThat: (in category 'deprecated') -----
changeSetsNamedSuchThat: nameBlock
^ ChangesOrganizer changeSetsNamedSuchThat: nameBlock!
----- Method: ChangeSorter class>>countOfChangeSetsWithClass:andSelector: (in category 'deprecated') -----
countOfChangeSetsWithClass: aClass andSelector: aSelector
^ ChangesOrganizer countOfChangeSetsWithClass: aClass andSelector: aSelector!
----- Method: ChangeSorter class>>deleteChangeSetsNumberedLowerThan: (in category 'deprecated') -----
deleteChangeSetsNumberedLowerThan: anInteger
^ ChangesOrganizer deleteChangeSetsNumberedLowerThan: anInteger!
----- Method: ChangeSorter class>>doesAnyChangeSetHaveClass:andSelector: (in category 'deprecated') -----
doesAnyChangeSetHaveClass: aClass andSelector: aSelector
^ ChangesOrganizer doesAnyChangeSetHaveClass: aClass andSelector: aSelector!
----- Method: ChangeSorter class>>existingOrNewChangeSetNamed: (in category 'deprecated') -----
existingOrNewChangeSetNamed: aName
^ ChangesOrganizer existingOrNewChangeSetNamed: aName!
----- Method: ChangeSorter class>>fileOutChangeSetsNamed: (in category 'deprecated') -----
fileOutChangeSetsNamed: nameList
^ ChangesOrganizer fileOutChangeSetsNamed: nameList!
----- Method: ChangeSorter class>>gatherChangeSets (in category 'deprecated') -----
gatherChangeSets
^ ChangesOrganizer gatherChangeSets!
----- Method: ChangeSorter class>>highestNumberedChangeSet (in category 'deprecated') -----
highestNumberedChangeSet
^ ChangesOrganizer highestNumberedChangeSet
!
----- Method: ChangeSorter class>>initialize (in category 'class initialization') -----
initialize
"ChangeSorter initialize"
FileList registerFileReader: self.
self registerInFlapsRegistry.
!
----- Method: ChangeSorter class>>initializeChangeSetCategories (in category 'deprecated') -----
initializeChangeSetCategories
^ ChangesOrganizer initializeChangeSetCategories!
----- Method: ChangeSorter class>>mostRecentChangeSetWithChangeForClass:selector: (in category 'deprecated') -----
mostRecentChangeSetWithChangeForClass: class selector: selector
^ ChangesOrganizer mostRecentChangeSetWithChangeForClass: class selector: selector!
----- Method: ChangeSorter class>>newChangeSet (in category 'deprecated') -----
newChangeSet
^ ChangesOrganizer newChangeSet!
----- Method: ChangeSorter class>>newChangeSet: (in category 'deprecated') -----
newChangeSet: aName
^ ChangesOrganizer newChangeSet: aName!
----- Method: ChangeSorter class>>newChangesFromStream:named: (in category 'deprecated') -----
newChangesFromStream: aStream named: aName
^ ChangesOrganizer newChangesFromStream: aStream named: aName!
----- Method: ChangeSorter class>>noteChangeSetsInRelease (in category 'deprecated') -----
noteChangeSetsInRelease
^ ChangesOrganizer noteChangeSetsInRelease!
----- Method: ChangeSorter class>>open (in category 'instance creation') -----
open
"Open a new instance of the receiver's class"
self new open!
----- Method: ChangeSorter class>>promoteToTop: (in category 'deprecated') -----
promoteToTop: aChangeSet
^ ChangesOrganizer promoteToTop: aChangeSet!
----- Method: ChangeSorter class>>prototypicalToolWindow (in category 'browse') -----
prototypicalToolWindow
"Answer a window representing a prototypical instance of the receiver"
^ToolBuilder build: self new!
----- Method: ChangeSorter class>>recentUpdateMarker (in category 'deprecated') -----
recentUpdateMarker
^ ChangesOrganizer recentUpdateMarker!
----- Method: ChangeSorter class>>recentUpdateMarker: (in category 'deprecated') -----
recentUpdateMarker: aNumber
^ ChangesOrganizer recentUpdateMarker: aNumber!
----- Method: ChangeSorter class>>registerInFlapsRegistry (in category 'deprecated') -----
registerInFlapsRegistry
"Register the receiver in the system's flaps registry"
self environment
at: #Flaps
ifPresent: [:cl | cl registerQuad: #(ChangeSorter prototypicalToolWindow 'Change Set' 'A tool that allows you to view and manipulate all the code changes in a single change set')
forFlapNamed: 'Tools']!
----- Method: ChangeSorter class>>removeChangeSet: (in category 'deprecated') -----
removeChangeSet: aChangeSet
^ ChangesOrganizer removeChangeSet: aChangeSet!
----- Method: ChangeSorter class>>removeChangeSetsNamedSuchThat: (in category 'deprecated') -----
removeChangeSetsNamedSuchThat: nameBlock
^ ChangesOrganizer removeChangeSetsNamedSuchThat: nameBlock!
----- Method: ChangeSorter class>>removeEmptyUnnamedChangeSets (in category 'deprecated') -----
removeEmptyUnnamedChangeSets
^ ChangesOrganizer removeEmptyUnnamedChangeSets!
----- Method: ChangeSorter class>>reorderChangeSets (in category 'deprecated') -----
reorderChangeSets
^ ChangesOrganizer reorderChangeSets!
----- Method: ChangeSorter class>>secondaryChangeSet (in category 'deprecated') -----
secondaryChangeSet
^ ChangesOrganizer secondaryChangeSet!
----- Method: ChangeSorter class>>unload (in category 'deprecated') -----
unload
"Unload the receiver from global registries"
self environment at: #FileList ifPresent: [:cl |
cl unregisterFileReader: self].
self environment at: #Flaps ifPresent: [:cl |
cl unregisterQuadsWithReceiver: self] !
----- Method: ChangeSorter class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Change Sorter' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'A tool that lets you see the code for one change set at a time.'!
----- Method: ChangeSorter>>aboutToStyle: (in category 'code pane') -----
aboutToStyle: aStyler
"This is a notification that aStyler is about to re-style its text.
Set the classOrMetaClass in aStyler, so that identifiers
will be resolved correctly.
Answer true to allow styling to proceed, or false to veto the styling"
self isModeStyleable ifFalse: [^false].
self currentSelector ifNil: [^false].
aStyler classOrMetaClass: self selectedClassOrMetaClass.
^true!
----- Method: ChangeSorter>>addPreamble (in category 'changeSet menu') -----
addPreamble
myChangeSet assurePreambleExists.
self okToChange ifTrue:
[currentClassName := nil.
currentSelector := nil.
self showChangeSet: myChangeSet]!
----- Method: ChangeSorter>>addPriorVersionsCountForSelector:ofClass:to: (in category 'annotation') -----
addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream
"Add an annotation detailing the prior versions count. Specially handled here for the case of a selector no longer in the system, whose prior version is pointed to by the lost-method pointer in the change held on to by the changeset"
(aClass includesSelector: aSelector) ifTrue:
[^ super addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream].
aStream nextPutAll:
((myChangeSet methodInfoFromRemoval: {aClass name. aSelector})
ifNil:
['no prior versions']
ifNotNil:
['version(s) retrievable here']), self annotationSeparator!
----- Method: ChangeSorter>>addToCategoryOpposite (in category 'changeSet menu') -----
addToCategoryOpposite
"Add the current change set to the category viewed on the opposite side, if it's of the sort to accept things like that"
| categoryOpposite |
categoryOpposite := (parent other: self) changeSetCategory.
categoryOpposite acceptsManualAdditions
ifTrue:
[categoryOpposite addChangeSet: myChangeSet.
categoryOpposite reconstituteList.
self update]
ifFalse:
[self inform:
'sorry, this command only makes sense
if the category showing on the opposite
side is a static category whose
members are manually maintained']!
----- Method: ChangeSorter>>annotationForPackageforSelector:ofClass: (in category 'annotation') -----
annotationForPackageforSelector: aSelector ofClass: aClass
"Provide a line of content for an annotation pane, representing
information about the given selector and class"
"requestList"
| aCategory |
aClass ifNil: [ ^nil] .
aSelector ifNil: [ aClass theNonMetaClass category asString ] .
aSelector ifNotNil: [ aCategory := aClass organization categoryOfElement: aSelector.
(aCategory notNil and: [ aCategory first = $* ])
ifTrue: [^ aCategory asString]] .
"Ok. So the selector catagory does not indicate our package. We defer to the class catagory"
^ aClass theNonMetaClass category asString.
!
----- Method: ChangeSorter>>basicClassList (in category 'class list') -----
basicClassList
"Computed. View should try to preserve selections, even though index changes"
^ myChangeSet ifNotNil: [myChangeSet changedClassNames] ifNil: [OrderedCollection new]
!
----- Method: ChangeSorter>>basicMessageList (in category 'message list') -----
basicMessageList
| probe newSelectors className |
currentClassName ifNil: [^ #()].
className := (self withoutItemAnnotation: currentClassName) .
probe := (className endsWith: ' class')
ifTrue: [className]
ifFalse: [className asSymbol].
newSelectors := myChangeSet selectorsInClass: probe.
(newSelectors includes: (self selectedMessageName))
ifFalse: [currentSelector := nil].
^ newSelectors asSortedCollection
!
----- Method: ChangeSorter>>beginNote (in category 'annotation') -----
beginNote
"return the string at the beginning of item annotation"
^' {'
!
----- Method: ChangeSorter>>browseChangeSet (in category 'changeSet menu') -----
browseChangeSet
"Open a message list browser on the new and changed methods in the current change set"
ChangedMessageSet openFor: myChangeSet
!
----- Method: ChangeSorter>>browseMethodConflicts (in category 'changeSet menu') -----
browseMethodConflicts
"Check to see if any other change set also holds changes to any methods in the selected change set; if so, open a browser on all such."
| aList |
aList := myChangeSet
messageListForChangesWhich: [ :aClass :aSelector |
(ChangesOrganizer allChangeSetsWithClass: aClass selector: aSelector) size > 1
]
ifNone: [^ self inform: 'No other change set has changes
for any method in this change set.'].
MessageSet
openMessageList: aList
name: 'Methods in "', myChangeSet name, '" that are also in other change sets (', aList size printString, ')'
!
----- Method: ChangeSorter>>browseVersions (in category 'message list') -----
browseVersions
"Create and schedule a changelist browser on the versions of the
selected message."
| class selector method category pair sourcePointer |
(selector := self selectedMessageName) ifNil: [^ self].
class := self selectedClassOrMetaClass.
(class includesSelector: selector)
ifTrue: [method := class compiledMethodAt: selector.
category := class whichCategoryIncludesSelector: selector.
sourcePointer := nil]
ifFalse: [pair := myChangeSet methodInfoFromRemoval: {class name. selector}.
pair ifNil: [^ nil].
sourcePointer := pair first.
method := CompiledMethod toReturnSelf setSourcePointer: sourcePointer.
category := pair last].
VersionsBrowser
browseVersionsOf: method
class: self selectedClass meta: class isMeta
category: category selector: selector
lostMethodPointer: sourcePointer.
!
----- Method: ChangeSorter>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"
ToolBuilder open: ChangeSorter.
"
| windowSpec |
windowSpec := builder pluggableWindowSpec new.
windowSpec label: 'Change Sorter'.
windowSpec model: self.
windowSpec children: OrderedCollection new.
windowSpec label: #labelString.
self buildWith: builder in: windowSpec rect: (0 at 0 extent: 1 at 1).
^builder build: windowSpec!
----- Method: ChangeSorter>>buildWith:in:rect: (in category 'toolbuilder') -----
buildWith: builder in: window rect: rect
| csListHeight msgListHeight csMsgListHeight listSpec textSpec |
contents := ''.
csListHeight := 0.25.
msgListHeight := 0.25.
csMsgListHeight := csListHeight + msgListHeight.
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #changeSetList;
getSelected: #currentCngSet;
setSelected: #showChangeSetNamed:;
menu: #changeSetMenu:shifted:;
keyPress: #changeSetListKey:from:;
autoDeselect: false;
frame: (((0 at 0 extent: 0.5 at csListHeight)
scaleBy: rect extent) translateBy: rect origin).
window children add: listSpec.
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #classList;
getSelected: #currentClassName;
setSelected: #currentClassName:;
menu: #classListMenu:shifted:;
keyPress: #classListKey:from:;
frame: (((0.5 at 0 extent: 0.5 at csListHeight)
scaleBy: rect extent) translateBy: rect origin).
window children add: listSpec.
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #messageList;
getSelected: #currentSelector;
setSelected: #currentSelector:;
menu: #messageMenu:shifted:;
keyPress: #messageListKey:from:;
frame: (((0 at csListHeight extent: 1 at msgListHeight)
scaleBy: rect extent) translateBy: rect origin).
window children add: listSpec.
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #contents;
setText: #contents:notifying:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:;
frame: (((0 at csMsgListHeight corner: 1 at 1) scaleBy: rect extent) translateBy: rect origin).
window children add: textSpec.
^window!
----- Method: ChangeSorter>>categorySubmenu:shifted: (in category 'changeSet menu') -----
categorySubmenu: aMenu shifted: shiftedIgnored
"Fill aMenu with less-frequently-needed category items"
aMenu title: 'Change set category'.
aMenu addStayUpItem.
aMenu addList: #(
('make a new category...' makeNewCategory 'Creates a new change-set-category (you will be asked to supply a name) which will start out its life with this change set in it')
('make a new category with class...' makeNewCategoryShowingClassChanges 'Creates a new change-set-category that includes change sets that change a particular class (you will be asked to supply a name)')
('rename this category' renameCategory 'Rename this change-set category. Only applies when the current category being shown is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.')
('remove this category' removeCategory 'Remove this change-set category. Only applies when the current category being shown is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.')
('show categories of this changeset' showCategoriesOfChangeSet 'Show a list of all the change-set categories that contain this change-set; if the you choose one of the categories from this pop-up, that category will be installed in this change sorter')
-).
parent ifNotNil:
[aMenu addList: #(
('add change set to category opposite' addToCategoryOpposite 'Adds this change set to the category on the other side of the change sorter. Only applies if the category shown on the opposite side is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.'))].
aMenu addList: #(
('remove change set from this category' removeFromCategory 'Removes this change set from the current category. Only applies when the current category being shown is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.')
-
('file out category''s change sets' fileOutAllChangeSets 'File out every change set in this category that has anything in it. The usual checks for slips are suppressed when this command is done.')
('set recent-updates marker' setRecentUpdatesMarker 'Allows you to specify a number that will demarcate which updates are considered "recent" and which are not. This will govern which updates are included in the RecentUpdates category in a change sorter')
('fill aggregate change set' fillAggregateChangeSet 'Creates a change-set named Aggregate into which all the changes in all the change sets in this category will be copied.')
-
('back to main menu' offerUnshiftedChangeSetMenu 'Takes you back to the shifted change-set menu.')
('back to shifted menu' offerShiftedChangeSetMenu 'Takes you back to the primary change-set menu.')).
^ aMenu!
----- Method: ChangeSorter>>changeSet (in category 'access') -----
changeSet
^ myChangeSet!
----- Method: ChangeSorter>>changeSetCategories (in category 'changeSet menu') -----
changeSetCategories
^ ChangesOrganizer changeSetCategories!
----- Method: ChangeSorter>>changeSetCategory (in category 'access') -----
changeSetCategory
"Answer the current changeSetCategory object that governs which change sets are shown in this ChangeSorter"
^ changeSetCategory ifNil:
[self setDefaultChangeSetCategory]!
----- Method: ChangeSorter>>changeSetCurrentlyDisplayed (in category 'access') -----
changeSetCurrentlyDisplayed
^ myChangeSet!
----- Method: ChangeSorter>>changeSetList (in category 'changeSet menu') -----
changeSetList
"Answer a list of ChangeSet names to be shown in the change sorter."
^ self changeSetCategory changeSetList!
----- Method: ChangeSorter>>changeSetListKey:from: (in category 'changeSet menu') -----
changeSetListKey: aChar from: view
"Respond to a Command key. I am a model with a listView that has a list of changeSets."
aChar == $b ifTrue: [^ self browseChangeSet].
aChar == $B ifTrue: [^ self openChangeSetBrowser].
aChar == $c ifTrue: [^ self copyAllToOther].
aChar == $D ifTrue: [^ self toggleDiffing].
aChar == $f ifTrue: [^ self findCngSet].
aChar == $m ifTrue: [^ self newCurrent].
aChar == $n ifTrue: [^ self newSet].
aChar == $o ifTrue: [^ self fileOut].
aChar == $p ifTrue: [^ self addPreamble].
aChar == $r ifTrue: [^ self rename].
aChar == $s ifTrue: [^ self chooseChangeSetCategory].
aChar == $x ifTrue: [^ self remove].
aChar == $- ifTrue: [^ self subtractOtherSide].
^ self messageListKey: aChar from: view!
----- Method: ChangeSorter>>changeSetMenu:shifted: (in category 'changeSet menu') -----
changeSetMenu: aMenu shifted: isShifted
"Set up aMenu to hold commands for the change-set-list pane. This could be for a single or double changeSorter"
isShifted ifTrue: [^ self shiftedChangeSetMenu: aMenu].
Smalltalk isMorphic
ifTrue:
[aMenu title: 'Change Set'.
aMenu addStayUpItemSpecial]
ifFalse:
[aMenu title: 'Change Set:
' , myChangeSet name].
aMenu add: 'make changes go to me (m)' action: #newCurrent.
aMenu addLine.
aMenu add: 'new change set... (n)' action: #newSet.
aMenu add: 'find...(f)' action: #findCngSet.
aMenu add: 'show category... (s)' action: #chooseChangeSetCategory.
aMenu balloonTextForLastItem:
'Lets you choose which change sets should be listed in this change sorter'.
aMenu add: 'select change set...' action: #chooseCngSet.
aMenu addLine.
aMenu add: 'rename change set (r)' action: #rename.
aMenu add: 'file out (o)' action: #fileOut.
aMenu add: 'mail to list' action: #mailOut.
aMenu add: 'browse methods (b)' action: #browseChangeSet.
aMenu add: 'browse change set (B)' action: #openChangeSetBrowser.
aMenu addLine.
parent
ifNotNil:
[aMenu add: 'copy all to other side (c)' action: #copyAllToOther.
aMenu add: 'submerge into other side' action: #submergeIntoOtherSide.
aMenu add: 'subtract other side (-)' action: #subtractOtherSide.
aMenu addLine].
myChangeSet hasPreamble
ifTrue:
[aMenu add: 'edit preamble (p)' action: #addPreamble.
aMenu add: 'remove preamble' action: #removePreamble]
ifFalse: [aMenu add: 'add preamble (p)' action: #addPreamble].
myChangeSet hasPostscript
ifTrue:
[aMenu add: 'edit postscript...' action: #editPostscript.
aMenu add: 'remove postscript' action: #removePostscript]
ifFalse: [aMenu add: 'add postscript...' action: #editPostscript].
aMenu addLine.
aMenu add: 'category functions...' action: #offerCategorySubmenu.
aMenu balloonTextForLastItem:
'Various commands relating to change-set-categories'.
aMenu addLine.
aMenu add: 'destroy change set (x)' action: #remove.
aMenu addLine.
aMenu add: 'more...' action: #offerShiftedChangeSetMenu.
^ aMenu!
----- Method: ChangeSorter>>checkForAlienAuthorship (in category 'changeSet menu') -----
checkForAlienAuthorship
"Open a message list browser on all uncommented methods in the current change set that have alien authorship"
myChangeSet checkForAlienAuthorship
!
----- Method: ChangeSorter>>checkForAnyAlienAuthorship (in category 'changeSet menu') -----
checkForAnyAlienAuthorship
"Open a message list browser on all uncommented methods in the current change set that have alien authorship, even historically"
myChangeSet checkForAnyAlienAuthorship
!
----- Method: ChangeSorter>>checkForUnclassifiedMethods (in category 'changeSet menu') -----
checkForUnclassifiedMethods
"Open a message list browser on all methods in the current change set that have not been categorized"
myChangeSet checkForUnclassifiedMethods
!
----- Method: ChangeSorter>>checkForUncommentedClasses (in category 'changeSet menu') -----
checkForUncommentedClasses
"Open a class list browser on classes in the change set that lack class comments"
myChangeSet checkForUncommentedClasses!
----- Method: ChangeSorter>>checkForUncommentedMethods (in category 'changeSet menu') -----
checkForUncommentedMethods
"Open a message list browser on all uncommented methods in the current change set"
myChangeSet checkForUncommentedMethods
!
----- Method: ChangeSorter>>checkForUnsentMessages (in category 'changeSet menu') -----
checkForUnsentMessages
"Open a message list browser on all unsent messages in the current change set"
myChangeSet checkForUnsentMessages
!
----- Method: ChangeSorter>>checkThatSidesDiffer: (in category 'changeSet menu') -----
checkThatSidesDiffer: escapeBlock
"If the change sets on both sides of the dual sorter are the same, put up an error message and escape via escapeBlock, else proceed happily"
(myChangeSet == (parent other: self) changeSet)
ifTrue:
[self inform:
'This command requires that the
change sets selected on the two
sides of the change sorter *not*
be the same.'.
^ escapeBlock value]
!
----- Method: ChangeSorter>>chooseChangeSetCategory (in category 'changeSet menu') -----
chooseChangeSetCategory
"Present the user with a list of change-set-categories and let her choose one"
| cats result |
self okToChange ifFalse: [^ self].
Smalltalk isMorphic ifTrue: [^ self chooseChangeSetCategoryInMorphic]. "gives balloon help"
cats := self changeSetCategories elementsInOrder.
result := UIManager default
chooseFrom: (cats collect: [:cat | cat categoryName])
values: cats.
result ifNotNil:
[changeSetCategory := result.
self changed: #changeSetList.
(self changeSetList includes: myChangeSet name) ifFalse:
[self showChangeSet: (ChangesOrganizer changeSetNamed: self changeSetList first)].
self changed: #relabel]!
----- Method: ChangeSorter>>chooseChangeSetCategoryInMorphic (in category 'changeSet menu') -----
chooseChangeSetCategoryInMorphic
"Present the user with a list of change-set-categories and let her choose one. In this morphic variant, we include balloon help"
| aMenu |
aMenu := MenuMorph new defaultTarget: self.
aMenu title:
'Choose the category of
change sets to show in
this Change Sorter
(red = current choice)'.
self changeSetCategories elementsInOrder do:
[:aCategory |
aMenu add: aCategory categoryName target: self selector: #showChangeSetCategory: argument: aCategory.
aCategory == changeSetCategory ifTrue:
[aMenu lastItem color: Color red].
aMenu balloonTextForLastItem: aCategory documentation].
aMenu popUpInWorld!
----- Method: ChangeSorter>>chooseCngSet (in category 'changeSet menu') -----
chooseCngSet
"Present the user with an alphabetical list of change set names, and let her choose one"
| changeSetsSortedAlphabetically chosen |
self okToChange ifFalse: [^ self].
changeSetsSortedAlphabetically := self changeSetList asSortedCollection:
[:a :b | a asLowercase withoutLeadingDigits < b asLowercase withoutLeadingDigits].
chosen := UIManager default chooseFrom: changeSetsSortedAlphabetically values: changeSetsSortedAlphabetically.
chosen ifNil: [^ self].
self showChangeSet: (ChangesOrganizer changeSetNamed: chosen)!
----- Method: ChangeSorter>>classList (in category 'class list') -----
classList
"return the classlist with package note appended."
^ self basicClassList collect: [: each |
each asString, (self packageNoteForClass: (Smalltalk classNamed: each) selector: nil) ] .!
----- Method: ChangeSorter>>classListKey:from: (in category 'class list') -----
classListKey: aChar from: view
"Respond to a Command key in the class-list pane."
aChar == $x ifTrue: [^ self removeClass].
aChar == $d ifTrue: [^ self forgetClass].
^ self messageListKey: aChar from: view "picks up b,h,p"!
----- Method: ChangeSorter>>classListMenu:shifted: (in category 'class list') -----
classListMenu: aMenu shifted: shifted
"Fill aMenu with items appropriate for the class list"
aMenu title: 'class list'.
Smalltalk isMorphic ifTrue: [aMenu addStayUpItemSpecial].
(parent notNil and: [shifted not])
ifTrue: [aMenu addList: #( "These two only apply to dual change sorters"
('copy class chgs to other side' copyClassToOther)
('move class chgs to other side' moveClassToOther))].
aMenu addList: (shifted
ifFalse: [#(
-
('delete class from change set (d)' forgetClass)
('remove class from system (x)' removeClass)
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' spawnHierarchy)
('browse protocol (p)' browseFullProtocol)
-
('printOut' printOutClass)
('fileOut' fileOutClass)
-
('inst var refs...' browseInstVarRefs)
('inst var defs...' browseInstVarDefs)
('class var refs...' browseClassVarRefs)
('class vars' browseClassVariables)
('class refs (N)' browseClassRefs)
-
('more...' offerShiftedClassListMenu))]
ifTrue: [#(
-
('unsent methods' browseUnusedMethods)
('unreferenced inst vars' showUnreferencedInstVars)
('unreferenced class vars' showUnreferencedClassVars)
-
('sample instance' makeSampleInstance)
('inspect instances' inspectInstances)
('inspect subinstances' inspectSubInstances)
-
('more...' offerUnshiftedClassListMenu ))]).
^ aMenu!
----- Method: ChangeSorter>>classMenu: (in category 'class list') -----
classMenu: aMenu
"Set up aMenu for the class-list. Retained for backward compatibility with old change sorters in image segments"
^ self classListMenu: aMenu shifted: false!
----- Method: ChangeSorter>>classMenu:shifted: (in category 'class list') -----
classMenu: aMenu shifted: shifted
"Fill aMenu with items appropriate for the class list. Retained for bkwd compatibility"
^ self classListMenu: aMenu shifted: shifted!
----- Method: ChangeSorter>>clearChangeSet (in category 'changeSet menu') -----
clearChangeSet
"Clear out the current change set, after getting a confirmation."
| message |
self okToChange ifFalse: [^ self].
myChangeSet isEmpty ifFalse:
[message := 'Are you certain that you want to\forget all the changes in this set?' withCRs.
(self confirm: message) ifFalse: [^ self]].
myChangeSet clear.
self changed: #classList.
self changed: #messageList.
self setContents.
self contentsChanged.
!
----- Method: ChangeSorter>>contents:notifying: (in category 'code pane') -----
contents: aString notifying: aController
"Compile the code in aString. Notify aController of any syntax errors.
Create an error if the category of the selected message is unknown.
Answer false if the compilation fails. Otherwise, if the compilation
created a new method, deselect the current selection. Then answer true."
| category selector class oldSelector |
(class := self selectedClassOrMetaClass) ifNil:
[(myChangeSet preambleString == nil or: [aString size == 0]) ifTrue: [ ^ false].
(aString count: [:char | char == $"]) odd
ifTrue: [self inform: 'unmatched double quotes in preamble']
ifFalse: [(Scanner new scanTokens: aString) size > 0 ifTrue: [
self inform: 'Part of the preamble is not within double-quotes.
To put a double-quote inside a comment, type two double-quotes in a row.
(Ignore this warning if you are including a doIt in the preamble.)']].
myChangeSet preambleString: aString.
self currentSelector: nil. "forces update with no 'unsubmitted chgs' feedback"
^ true].
oldSelector := self selectedMessageName.
category := class organization categoryOfElement: oldSelector.
selector := class compile: aString
classified: category
notifying: aController.
selector ifNil: [^ false].
(self messageList includes: selector)
ifTrue: [self currentSelector: selector]
ifFalse: [self currentSelector: oldSelector].
self update.
^ true!
----- Method: ChangeSorter>>copyAllToOther (in category 'changeSet menu') -----
copyAllToOther
"Copy this entire change set into the one on the other side"
| companionSorter |
self checkThatSidesDiffer: [^ self].
(companionSorter := parent other: self) changeSetCurrentlyDisplayed assimilateAllChangesFoundIn: myChangeSet.
companionSorter changed: #classList. "Later the changeSet itself will notice..."
companionSorter changed: #messageList!
----- Method: ChangeSorter>>copyClassToOther (in category 'class list') -----
copyClassToOther
"Place these changes in the other changeSet also"
| otherSorter otherChangeSet |
self checkThatSidesDiffer: [^ self].
self okToChange ifFalse: [^ Beeper beep].
currentClassName ifNil: [^ Beeper beep].
otherSorter := parent other: self.
otherChangeSet := otherSorter changeSet.
otherChangeSet absorbClass: self selectedClassOrMetaClass name from: myChangeSet.
otherSorter showChangeSet: otherChangeSet.!
----- Method: ChangeSorter>>copyMethodToOther (in category 'message list') -----
copyMethodToOther
"Place this change in the other changeSet also"
| other cls sel |
self checkThatSidesDiffer: [^ self].
currentSelector ifNotNil:
[other := (parent other: self) changeSet.
cls := self selectedClassOrMetaClass.
sel := self selectedMessageName.
other absorbMethod: sel class: cls from: myChangeSet.
(parent other: self) showChangeSet: other]
!
----- Method: ChangeSorter>>currentClassName (in category 'class list') -----
currentClassName
^ currentClassName!
----- Method: ChangeSorter>>currentClassName: (in category 'class list') -----
currentClassName: aString
currentClassName := aString.
currentSelector := nil. "fix by wod"
self changed: #currentClassName.
self changed: #messageList.
self setContents.
self contentsChanged.!
----- Method: ChangeSorter>>currentCngSet (in category 'changeSet menu') -----
currentCngSet
^ myChangeSet name!
----- Method: ChangeSorter>>currentSelector (in category 'message list') -----
currentSelector
^ currentSelector!
----- Method: ChangeSorter>>currentSelector: (in category 'message list') -----
currentSelector: messageName
currentSelector := messageName.
self changed: #currentSelector.
self setContents.
self contentsChanged.!
----- Method: ChangeSorter>>editPostscript (in category 'changeSet menu') -----
editPostscript
"Allow the user to edit the receiver's change-set's postscript -- in a separate window"
myChangeSet editPostscript!
----- Method: ChangeSorter>>editPreamble (in category 'changeSet menu') -----
editPreamble
"Allow the user to edit the receiver's change-set's preamble -- in a separate window."
myChangeSet editPreamble!
----- Method: ChangeSorter>>endNote (in category 'annotation') -----
endNote
"return the string at the beginning of item annotation"
^'}'
!
----- Method: ChangeSorter>>expungeUniclasses (in category 'changeSet menu') -----
expungeUniclasses
"remove all memory of uniclasses in the receiver"
self okToChange ifFalse: [^ self].
myChangeSet expungeUniclasses.
self changed: #classList.
self changed: #messageList.
!
----- Method: ChangeSorter>>fileIntoNewChangeSet (in category 'changeSet menu') -----
fileIntoNewChangeSet
"Obtain a file designation from the user, and file its contents into a
new change set whose name is a function of the filename. Show the
new set and leave the current changeSet unaltered."
| aNewChangeSet stream |
self okToChange
ifFalse: [^ self].
ChangeSet promptForDefaultChangeSetDirectoryIfNecessary.
stream := StandardFileMenu oldFileStreamFrom: ChangeSet defaultChangeSetDirectory.
stream
ifNil: [^ self].
aNewChangeSet := self class
newChangesFromStream: stream
named: (FileDirectory localNameFor: stream name).
aNewChangeSet
ifNotNil: [self showChangeSet: aNewChangeSet]!
----- Method: ChangeSorter>>fileOut (in category 'changeSet menu') -----
fileOut
"File out the current change set."
myChangeSet fileOut.
parent modelWakeUp. "notice object conversion methods created"
!
----- Method: ChangeSorter>>fileOutAllChangeSets (in category 'changeSet menu') -----
fileOutAllChangeSets
"File out all nonempty change sets in the current category, probably"
self changeSetCategory fileOutAllChangeSets!
----- Method: ChangeSorter>>fileOutClass (in category 'class list') -----
fileOutClass
"this is a hack!!!! makes a new change set, called the class name, adds author initials to try to make a unique change set name, files it out and removes it. kfr 16 june 2000"
| aSet |
"File out the selected class set."
aSet := self class newChangeSet: (self withoutItemAnnotation: currentClassName).
aSet absorbClass: self selectedClassOrMetaClass name from: myChangeSet.
aSet fileOut.
self class removeChangeSet: aSet.
parent modelWakeUp. "notice object conversion methods created"
!
----- Method: ChangeSorter>>fillAggregateChangeSet (in category 'changeSet menu') -----
fillAggregateChangeSet
"Create a change-set named Aggregate and pour into it all the changes in all the change-sets of the currently-selected category"
self changeSetCategory fillAggregateChangeSet!
----- Method: ChangeSorter>>findCngSet (in category 'changeSet menu') -----
findCngSet
"Search for a changeSet by name. Pop up a menu of all changeSets whose name contains the string entered by the user. If only one matches, then the pop-up menu is bypassed"
| index pattern candidates nameList |
self okToChange ifFalse: [^ self].
pattern := UIManager default request: 'ChangeSet name or fragment?'.
pattern isEmpty ifTrue: [^ self].
nameList := self changeSetList asSet.
candidates := ChangeSet allChangeSets select:
[:c | (nameList includes: c name) and:
[c name includesSubstring: pattern caseSensitive: false]].
candidates size = 0 ifTrue: [^ Beeper beep].
candidates size = 1 ifTrue:
[^ self showChangeSet: candidates first].
index := (UIManager default chooseFrom:
(candidates collect: [:each | each name]) asStringWithCr).
index = 0 ifFalse: [self showChangeSet: (candidates at: index)].
!
----- Method: ChangeSorter>>forget (in category 'message list') -----
forget
"Drop this method from the changeSet"
self okToChange ifFalse: [^ self].
currentSelector ifNotNil: [
myChangeSet removeSelectorChanges: self selectedMessageName
class: self selectedClassOrMetaClass.
currentSelector := nil.
self showChangeSet: myChangeSet]!
----- Method: ChangeSorter>>forgetClass (in category 'class list') -----
forgetClass
"Remove all mention of this class from the changeSet"
self okToChange ifFalse: [^ self].
currentClassName ifNotNil: [
myChangeSet removeClassChanges: (self withoutItemAnnotation: currentClassName).
currentClassName := nil.
currentSelector := nil.
self showChangeSet: myChangeSet].
!
----- Method: ChangeSorter>>goToChangeSetsProject (in category 'changeSet menu') -----
goToChangeSetsProject
"Transport the user to a project which bears the selected changeSet as its current changeSet"
| aProject |
(aProject := myChangeSet correspondingProject)
ifNotNil:
[aProject enter: false revert: false saveForRevert: false]
ifNil:
[self inform: 'Has no project']!
----- Method: ChangeSorter>>initialize (in category 'initialize') -----
initialize
super initialize.
myChangeSet := ChangeSet current.!
----- Method: ChangeSorter>>inspectChangeSet (in category 'changeSet menu') -----
inspectChangeSet
"Open a message list browser on the new and changed methods in the current change set"
myChangeSet inspectWithLabel: 'Change set: ', myChangeSet name
!
----- Method: ChangeSorter>>label (in category 'access') -----
label
^ self labelString!
----- Method: ChangeSorter>>labelString (in category 'access') -----
labelString
"The label for my entire window. The large button that displays my name is gotten via mainButtonName"
^ String streamContents:
[:aStream |
aStream nextPutAll: (ChangeSet current == myChangeSet
ifTrue: ['Changes go to "', myChangeSet name, '"']
ifFalse: ['ChangeSet: ', myChangeSet name]).
(self changeSetCategory categoryName ~~ #All)
ifTrue:
[aStream nextPutAll: ' - ', self parenthesizedCategoryName]]!
----- Method: ChangeSorter>>lookForSlips (in category 'changeSet menu') -----
lookForSlips
"Open a message list browser on the new and changed methods in the current change set"
myChangeSet lookForSlips
!
----- Method: ChangeSorter>>mailOut (in category 'changeSet menu') -----
mailOut
"Create a mail with a gzipped attachment holding out the current change
set. "
myChangeSet mailOut.
parent modelWakeUp!
----- Method: ChangeSorter>>mainButtonName (in category 'changeSet menu') -----
mainButtonName
^ myChangeSet name!
----- Method: ChangeSorter>>makeNewCategory (in category 'changeSet menu') -----
makeNewCategory
"Create a new, static change-set category, which will be populated entirely by change sets that have been manually placed in it"
| catName aCategory |
catName := UIManager default request: 'Please give the new category a name' initialAnswer: ''.
catName isEmptyOrNil ifTrue: [^ self].
catName := catName asSymbol.
(self changeSetCategories includesKey: catName) ifTrue:
[^ self inform: 'Sorry, there is already a category of that name'].
aCategory := StaticChangeSetCategory new categoryName: catName.
self changeSetCategories elementAt: catName put: aCategory.
aCategory addChangeSet: myChangeSet.
self showChangeSetCategory: aCategory!
----- Method: ChangeSorter>>makeNewCategoryShowingClassChanges (in category 'changeSet menu') -----
makeNewCategoryShowingClassChanges
"Create a new, static change-set category, which will be populated entirely by change sets that have been manually placed in it"
| catName aCategory clsName |
clsName := self selectedClass ifNotNil: [self selectedClass name ] ifNil: [''].
clsName := UIManager default request: 'Which class?' initialAnswer: clsName.
clsName isEmptyOrNil ifTrue: [^ self].
catName := ('Changes to ', clsName) asSymbol.
(self changeSetCategories includesKey: catName) ifTrue:
[^ self inform: 'Sorry, there is already a category of that name'].
aCategory := ChangeSetCategoryWithParameters new categoryName: catName.
aCategory membershipSelector: #changeSet:containsClass: ; parameters: { clsName }.
self changeSetCategories elementAt: catName put: aCategory.
aCategory reconstituteList.
self showChangeSetCategory: aCategory!
----- Method: ChangeSorter>>messageList (in category 'message list') -----
messageList
| thisClass |
(thisClass := self selectedClassOrMetaClass) ifNil: [^ #() ] .
^self basicMessageList collect: [ :each |
each asString , (self packageNoteForClass: thisClass selector: each) ] .
!
----- Method: ChangeSorter>>messageListKey:from: (in category 'class list') -----
messageListKey: aChar from: view
"Respond to a Command key in the message-list pane."
aChar == $d ifTrue: [^ self forget].
super messageListKey: aChar from: view!
----- Method: ChangeSorter>>messageListMenu:shifted: (in category 'message list') -----
messageListMenu: aMenu shifted: shifted
"Fill aMenu with items appropriate for the message list; could be for a single or double changeSorter"
^ self messageMenu: aMenu shifted: shifted!
----- Method: ChangeSorter>>messageMenu:shifted: (in category 'message list') -----
messageMenu: aMenu shifted: shifted
"Fill aMenu with items appropriate for the message list; could be for a single or double changeSorter"
shifted ifTrue: [^ self shiftedMessageMenu: aMenu].
aMenu title: 'message list'.
Smalltalk isMorphic ifTrue: [aMenu addStayUpItemSpecial].
parent ifNotNil:
[aMenu addList: #(
('copy method to other side' copyMethodToOther)
('move method to other side' moveMethodToOther))].
aMenu addList: #(
('delete method from changeSet (d)' forget)
-
('remove method from system (x)' removeMessage)
-
('browse full (b)' browseMethodFull)
('browse hierarchy (h)' spawnHierarchy)
('browse method (O)' openSingleMessageBrowser)
('browse protocol (p)' browseFullProtocol)
-
('fileOut' fileOutMessage)
('printOut' printOutMessage)
-
('senders of... (n)' browseSendersOfMessages)
('implementors of... (m)' browseMessages)
('inheritance (i)' methodHierarchy)
('versions (v)' browseVersions)
-
('more...' shiftedYellowButtonActivity)).
^ aMenu
!
----- Method: ChangeSorter>>methodConflictsWithOppositeCategory (in category 'changeSet menu') -----
methodConflictsWithOppositeCategory
"Check to see if ANY change set on the other side shares any methods with the selected change set; if so, open a browser on all such."
| aList otherCategory |
otherCategory := (parent other: self) changeSetCategory.
aList := myChangeSet
messageListForChangesWhich: [ :aClass :aSelector |
aClass notNil and:
[otherCategory
hasChangeForClassName: aClass name
selector: aSelector
otherThanIn: myChangeSet]
]
ifNone: [^ self inform:
'There are no methods that appear both in
this change set and in any change set
(other than this one) on the other side.'].
MessageSet
openMessageList: aList
name: 'Methods in "', myChangeSet name, '" also in some other change set in category ', otherCategory categoryName,' (', aList size printString, ')'
!
----- Method: ChangeSorter>>methodConflictsWithOtherSide (in category 'changeSet menu') -----
methodConflictsWithOtherSide
"Check to see if the change set on the other side shares any methods with the selected change set; if so, open a browser on all such."
| aList other |
self checkThatSidesDiffer: [^ self].
other := (parent other: self) changeSet.
aList := myChangeSet
messageListForChangesWhich: [ :aClass :aSelector |
aClass notNil and: [(other methodChangesAtClass: aClass name) includesKey: aSelector]
]
ifNone: [^ self inform: 'There are no methods that appear
both in this change set and
in the one on the other side.'].
MessageSet
openMessageList: aList
name: 'Methods in "', myChangeSet name, '" that are also in ', other name,' (', aList size printString, ')'
!
----- Method: ChangeSorter>>modelWakeUp (in category 'access') -----
modelWakeUp
"A window with me as model is being entered.
Make sure I am up-to-date with the changeSets."
self canDiscardEdits ifTrue: [self update]!
----- Method: ChangeSorter>>moveClassToOther (in category 'class list') -----
moveClassToOther
"Place class changes in the other changeSet and remove them from this one"
self checkThatSidesDiffer: [^ self].
(self okToChange and: [currentClassName notNil]) ifFalse: [^ Beeper beep].
self copyClassToOther.
self forgetClass!
----- Method: ChangeSorter>>moveMethodToOther (in category 'message list') -----
moveMethodToOther
"Place this change in the other changeSet and remove it from this side"
| other cls sel |
self checkThatSidesDiffer: [^self].
self okToChange ifFalse: [^Beeper beep].
currentSelector ifNotNil:
[other := (parent other: self) changeSet.
other == myChangeSet ifTrue: [^Beeper beep].
cls := self selectedClassOrMetaClass.
sel := self selectedMessageName.
other
absorbMethod: sel
class: cls
from: myChangeSet.
(parent other: self) showChangeSet: other.
self forget "removes the method from this side"]!
----- Method: ChangeSorter>>myChangeSet: (in category 'access') -----
myChangeSet: anObject
myChangeSet := anObject!
----- Method: ChangeSorter>>newCurrent (in category 'changeSet menu') -----
newCurrent
"make my change set be the current one that changes go into"
ChangeSet newChanges: myChangeSet.
self update. "Because list of changes in a category may thus have changed"
self changed: #relabel.!
----- Method: ChangeSorter>>newSet (in category 'changeSet menu') -----
newSet
"Create a new changeSet and show it., making it the current one. Reject name if already in use."
| aSet |
self okToChange ifFalse: [^ self].
aSet := self class newChangeSet.
aSet ifNotNil:
[self changeSetCategory acceptsManualAdditions ifTrue:
[changeSetCategory addChangeSet: aSet].
self update.
(changeSetCategory includesChangeSet: aSet) ifTrue:
[self showChangeSet: aSet].
self changed: #relabel]!
----- Method: ChangeSorter>>noteString: (in category 'annotation') -----
noteString: aString
^ self beginNote , aString asString , self endNote!
----- Method: ChangeSorter>>offerCategorySubmenu (in category 'changeSet menu') -----
offerCategorySubmenu
"Offer a menu of category-related items"
self offerMenuFrom: #categorySubmenu:shifted: shifted: false!
----- Method: ChangeSorter>>offerShiftedChangeSetMenu (in category 'changeSet menu') -----
offerShiftedChangeSetMenu
"Offer the shifted version of the change set menu"
self offerMenuFrom: #changeSetMenu:shifted: shifted: true!
----- Method: ChangeSorter>>offerUnshiftedChangeSetMenu (in category 'changeSet menu') -----
offerUnshiftedChangeSetMenu
"Offer the unshifted version of the change set menu"
self offerMenuFrom: #changeSetMenu:shifted: shifted: false!
----- Method: ChangeSorter>>open (in category 'creation') -----
open
"ChangeSorterPluggable new open"
^ToolBuilder open: self!
----- Method: ChangeSorter>>openChangeSetBrowser (in category 'changeSet menu') -----
openChangeSetBrowser
"Open a ChangeSet browser on the current change set"
ToolBuilder open: (ChangeSetBrowser new myChangeSet: myChangeSet)!
----- Method: ChangeSorter>>packageNoteForClass:selector: (in category 'annotation') -----
packageNoteForClass: aClass selector: aSelector
"return the category name that represents the package name for aClass>>aSelector.
when selector is nil or in a normal catagory return "
| package |
package := (self annotationForPackageforSelector: aSelector
ofClass: aClass) ifNil: ['<class was deleted???>'] .
^ self noteString: package
!
----- Method: ChangeSorter>>parent (in category 'access') -----
parent
^ parent!
----- Method: ChangeSorter>>parent: (in category 'access') -----
parent: anObject
parent := anObject!
----- Method: ChangeSorter>>parenthesizedCategoryName (in category 'access') -----
parenthesizedCategoryName
"Answer my category name in parentheses"
^ ' (', self changeSetCategory categoryName, ')'!
----- Method: ChangeSorter>>promoteToTopChangeSet (in category 'changeSet menu') -----
promoteToTopChangeSet
"Move the selected change-set to the top of the list"
self class promoteToTop: myChangeSet.
(parent ifNil: [self]) modelWakeUp!
----- Method: ChangeSorter>>remove (in category 'changeSet menu') -----
remove
"Completely destroy my change set. Check if it's OK first"
self okToChange ifFalse: [^ self].
self removePrompting: true.
self update!
----- Method: ChangeSorter>>removeCategory (in category 'changeSet menu') -----
removeCategory
"Remove the current category"
| itsName |
self changeSetCategory acceptsManualAdditions ifFalse:
[^ self inform: 'sorry, you can only remove manually-added categories.'].
(self confirm: 'Really remove the change-set-category
named ', (itsName := changeSetCategory categoryName), '?') ifFalse: [^ self].
self changeSetCategories removeElementAt: itsName.
self setDefaultChangeSetCategory.
self update!
----- Method: ChangeSorter>>removeContainedInClassCategories (in category 'changeSet menu') -----
removeContainedInClassCategories
| matchExpression |
myChangeSet removePreamble.
matchExpression := UIManager default request: 'Enter class category name (wildcard is ok)' initialAnswer: 'System-*'.
(Smalltalk organization categories
select: [:each | matchExpression match: each])
do: [:eachCat |
| classNames |
classNames := Smalltalk organization listAtCategoryNamed: eachCat.
classNames
do: [:eachClassName |
myChangeSet removeClassChanges: eachClassName.
myChangeSet removeClassChanges: eachClassName , ' class'].
self showChangeSet: myChangeSet]!
----- Method: ChangeSorter>>removeFromCategory (in category 'changeSet menu') -----
removeFromCategory
"Add the current change set to the category viewed on the opposite side, if it's of the sort to accept things like that"
| aCategory |
(aCategory := self changeSetCategory) acceptsManualAdditions
ifTrue:
[aCategory removeElementAt: myChangeSet name.
aCategory reconstituteList.
self update]
ifFalse:
[self inform:
'sorry, this command only makes
sense for static categories whose
members are manually maintained']!
----- Method: ChangeSorter>>removeFromCurrentChanges (in category 'message list') -----
removeFromCurrentChanges
"Redisplay after removal in case we are viewing the current changeSet"
super removeFromCurrentChanges.
currentSelector := nil.
self showChangeSet: myChangeSet!
----- Method: ChangeSorter>>removeMessage (in category 'message list') -----
removeMessage
"Remove the selected msg from the system. Real work done by the
parent, a ChangeSorter"
| confirmation sel |
self okToChange
ifFalse: [^ self].
currentSelector
ifNotNil: [confirmation := self systemNavigation confirmRemovalOf: (sel := self selectedMessageName) on: self selectedClassOrMetaClass.
confirmation == 3
ifTrue: [^ self].
self selectedClassOrMetaClass removeSelector: sel.
self update.
confirmation == 2
ifTrue: [self systemNavigation browseAllCallsOn: sel]]!
----- Method: ChangeSorter>>removePostscript (in category 'changeSet menu') -----
removePostscript
(myChangeSet hasPostscript and: [myChangeSet postscriptHasDependents]) ifTrue:
[^ self inform:
'Cannot remove the postscript right
now because there is at least one
window open on that postscript.
Close that window and try again.'].
myChangeSet removePostscript.
self showChangeSet: myChangeSet!
----- Method: ChangeSorter>>removePreamble (in category 'changeSet menu') -----
removePreamble
myChangeSet removePreamble.
self showChangeSet: myChangeSet!
----- Method: ChangeSorter>>removePrompting: (in category 'changeSet menu') -----
removePrompting: doPrompt
"Completely destroy my change set. Check if it's OK first, and if doPrompt is true, get the user to confirm his intentions first."
| message aName changeSetNumber msg |
aName := myChangeSet name.
myChangeSet okayToRemove ifFalse: [^ self]. "forms current changes for some project"
(myChangeSet isEmpty or: [doPrompt not]) ifFalse:
[message := 'Are you certain that you want to
remove (destroy) the change set
named "', aName, '" ?'.
(self confirm: message) ifFalse: [^ self]].
doPrompt ifTrue:
[msg := myChangeSet hasPreamble
ifTrue:
[myChangeSet hasPostscript
ifTrue:
['a preamble and a postscript']
ifFalse:
['a preamble']]
ifFalse:
[myChangeSet hasPostscript
ifTrue:
['a postscript']
ifFalse:
['']].
msg isEmpty ifFalse:
[(self confirm:
'Caution!! This change set has
', msg, ' which will be
lost if you destroy the change set.
Do you really want to go ahead with this?') ifFalse: [^ self]]].
"Go ahead and remove the change set"
changeSetNumber := myChangeSet name initialIntegerOrNil.
changeSetNumber ifNotNil: [SystemVersion current unregisterUpdate: changeSetNumber].
ChangesOrganizer removeChangeSet: myChangeSet.
self showChangeSet: ChangeSet current.!
----- Method: ChangeSorter>>rename (in category 'changeSet menu') -----
rename
"Store a new name string into the selected ChangeSet. reject duplicate name; allow user to back out"
| newName |
newName := UIManager default request: 'New name for this change set'
initialAnswer: myChangeSet name.
(newName = myChangeSet name or: [newName size == 0]) ifTrue:
[^ Beeper beep].
(self class changeSetNamed: newName) ifNotNil:
[^ Utilities inform: 'Sorry that name is already used'].
myChangeSet name: newName.
self update.
self changed: #mainButtonName.
self changed: #relabel.!
----- Method: ChangeSorter>>renameCategory (in category 'changeSet menu') -----
renameCategory
"Obtain a new name for the category and, if acceptable, apply it"
| catName oldName |
self changeSetCategory acceptsManualAdditions ifFalse:
[^ self inform: 'sorry, you can only rename manually-added categories.'].
catName := UIManager default request: 'Please give the new category a name' initialAnswer: (oldName := changeSetCategory categoryName).
catName isEmptyOrNil ifTrue: [^ self].
(catName := catName asSymbol) = oldName ifTrue: [^ self inform: 'no change.'].
(self changeSetCategories includesKey: catName) ifTrue:
[^ self inform: 'Sorry, there is already a category of that name'].
changeSetCategory categoryName: catName.
self changeSetCategories removeElementAt: oldName.
self changeSetCategories elementAt: catName put: changeSetCategory.
self update!
----- Method: ChangeSorter>>reorderChangeSets (in category 'changeSet menu') -----
reorderChangeSets
"apply a standard reordering -- let the class handle this"
^ self class reorderChangeSets!
----- Method: ChangeSorter>>selectedClass (in category 'class list') -----
selectedClass
"Answer the currently-selected class. If there is no selection, or if the selection refers to a class no longer extant, return nil"
| c |
^ currentClassName ifNotNil: [(c := self selectedClassOrMetaClass)
ifNotNil: [c theNonMetaClass]]!
----- Method: ChangeSorter>>selectedClassOrMetaClass (in category 'traits') -----
selectedClassOrMetaClass
"Careful, the class may have been removed!!"
| cName tName className |
currentClassName ifNil: [^ nil].
className := (self withoutItemAnnotation: currentClassName) .
(className endsWith: ' class')
ifTrue: [cName := (className copyFrom: 1 to: className size-6) asSymbol.
^ (Smalltalk at: cName ifAbsent: [^nil]) class].
(currentClassName endsWith: ' classTrait')
ifTrue: [tName := (className copyFrom: 1 to: className size-11) asSymbol.
^ (Smalltalk at: tName ifAbsent: [^nil]) classTrait].
cName := className asSymbol.
^ Smalltalk at: cName ifAbsent: [nil]!
----- Method: ChangeSorter>>selectedMessageName (in category 'message list') -----
selectedMessageName
currentSelector ifNil: [^ nil].
^ (self withoutItemAnnotation: currentSelector) asSymbol!
----- Method: ChangeSorter>>setContents (in category 'code pane') -----
setContents
"return the source code that shows in the bottom pane"
| sel class strm changeType |
self clearUserEditFlag.
currentClassName ifNil: [^ contents := myChangeSet preambleString ifNil: ['']].
class := self selectedClassOrMetaClass.
(sel := self selectedMessageName) == nil
ifFalse: [changeType := (myChangeSet atSelector: (sel := sel asSymbol) class: class).
changeType == #remove
ifTrue: [^ contents := 'Method has been removed (see versions)'].
changeType == #addedThenRemoved
ifTrue: [^ contents := 'Added then removed (see versions)'].
class ifNil: [^ contents := 'Method was added, but cannot be found!!'].
(class includesSelector: sel)
ifFalse: [^ contents := 'Method was added, but cannot be found!!'].
contents := class sourceCodeAt: sel.
(#(prettyPrint prettyDiffs) includes: contentsSymbol) ifTrue:
[contents := class prettyPrinterClass
format: contents in: class notifying: nil].
self showingAnyKindOfDiffs
ifTrue: [contents := self diffFromPriorSourceFor: contents].
^ contents := contents asText makeSelectorBoldIn: class]
ifTrue: [strm := WriteStream on: (String new: 100).
(myChangeSet classChangeAt: currentClassName) do:
[:each |
each = #remove ifTrue: [strm nextPutAll: 'Entire class was removed.'; cr].
each = #addedThenRemoved ifTrue: [strm nextPutAll: 'Class was added then removed.'].
each = #rename ifTrue: [strm nextPutAll: 'Class name was changed.'; cr].
each = #add ifTrue: [strm nextPutAll: 'Class definition was added.'; cr].
each = #change ifTrue: [strm nextPutAll: 'Class definition was changed.'; cr].
each = #reorganize ifTrue: [strm nextPutAll: 'Class organization was changed.'; cr].
each = #comment ifTrue: [strm nextPutAll: 'New class comment.'; cr.
]].
^ contents := strm contents].!
----- Method: ChangeSorter>>setDefaultChangeSetCategory (in category 'creation') -----
setDefaultChangeSetCategory
"Set a default ChangeSetCategory for the receiver, and answer it"
^ changeSetCategory := self class changeSetCategoryNamed: #All!
----- Method: ChangeSorter>>setRecentUpdatesMarker (in category 'changeSet menu') -----
setRecentUpdatesMarker
"Allow the user to change the recent-updates marker"
| result |
result := UIManager default request:
('Enter the lowest change-set number
that you wish to consider "recent"?
(note: highest change-set number
in this image at this time is ', ChangeSet highestNumberedChangeSet asString, ')') initialAnswer: self class recentUpdateMarker asString.
(result notNil and: [result startsWithDigit]) ifTrue:
[self class recentUpdateMarker: result asInteger.
Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]]!
----- Method: ChangeSorter>>shiftedChangeSetMenu: (in category 'changeSet menu') -----
shiftedChangeSetMenu: aMenu
"Set up aMenu to hold items relating to the change-set-list pane when the shift key is down"
Smalltalk isMorphic ifTrue:
[aMenu title: 'Change set (shifted)'.
aMenu addStayUpItemSpecial].
"CONFLICTS SECTION"
aMenu add: 'conflicts with other change sets' action: #browseMethodConflicts.
aMenu balloonTextForLastItem:
'Browse all methods that occur both in this change set and in at least one other change set.'.
parent ifNotNil:
[aMenu add: 'conflicts with change set opposite' action: #methodConflictsWithOtherSide.
aMenu balloonTextForLastItem:
'Browse all methods that occur both in this change set and in the one on the opposite side of the change sorter.'.
aMenu add: 'conflicts with category opposite' action: #methodConflictsWithOppositeCategory.
aMenu balloonTextForLastItem:
'Browse all methods that occur both in this change set and in ANY change set in the category list on the opposite side of this change sorter, other of course than this change set itself. (Caution -- this could be VERY slow)'].
aMenu addLine.
"CHECKS SECTION"
aMenu add: 'check for slips' action: #lookForSlips.
aMenu balloonTextForLastItem:
'Check this change set for halts and references to Transcript.'.
aMenu add: 'check for unsent messages' action: #checkForUnsentMessages.
aMenu balloonTextForLastItem:
'Check this change set for messages that are not sent anywhere in the system'.
aMenu add: 'check for uncommented methods' action: #checkForUncommentedMethods.
aMenu balloonTextForLastItem:
'Check this change set for methods that do not have comments'.
aMenu add: 'check for uncommented classes' action: #checkForUncommentedClasses.
aMenu balloonTextForLastItem:
'Check for classes with code in this changeset which lack class comments'.
Utilities authorInitialsPerSe isEmptyOrNil ifFalse:
[aMenu add: 'check for other authors' action: #checkForAlienAuthorship.
aMenu balloonTextForLastItem:
'Check this change set for methods whose current authoring stamp does not start with "', Utilities authorInitials, '"'.
aMenu add: 'check for any other authors' action: #checkForAnyAlienAuthorship.
aMenu balloonTextForLastItem:
'Check this change set for methods any of whose authoring stamps do not start with "', Utilities authorInitials, '"'].
aMenu add: 'check for uncategorized methods' action: #checkForUnclassifiedMethods.
aMenu balloonTextForLastItem:
'Check to see if any methods in the selected change set have not yet been assigned to a category. If any are found, open a browser on them.'.
aMenu addLine.
aMenu add: 'inspect change set' action: #inspectChangeSet.
aMenu balloonTextForLastItem:
'Open an inspector on this change set. (There are some details in a change set which you don''t see in a change sorter.)'.
aMenu add: 'update' action: #update.
aMenu balloonTextForLastItem:
'Update the display for this change set. (This is done automatically when you activate this window, so is seldom needed.)'.
aMenu add: 'go to change set''s project' action: #goToChangeSetsProject.
aMenu balloonTextForLastItem:
'If this change set is currently associated with a Project, go to that project right now.'.
aMenu add: 'promote to top of list' action: #promoteToTopChangeSet.
aMenu balloonTextForLastItem:
'Make this change set appear first in change-set lists in all change sorters.'.
aMenu add: 'trim history' action: #trimHistory.
aMenu balloonTextForLastItem:
' Drops any methods added and then removed, as well as renaming and reorganization of newly-added classes. NOTE: can cause confusion if later filed in over an earlier version of these changes'.
aMenu add: 'remove contained in class categories...' action: #removeContainedInClassCategories.
aMenu balloonTextForLastItem: ' Drops any changes in given class categories'.
aMenu add: 'clear this change set' action: #clearChangeSet.
aMenu balloonTextForLastItem:
'Reset this change set to a pristine state where it holds no information. CAUTION: this is destructive and irreversible!!'.
aMenu add: 'expunge uniclasses' action: #expungeUniclasses.
aMenu balloonTextForLastItem:
'Remove from the change set all memory of uniclasses, e.g. classes added on behalf of etoys, fabrik, etc., whose classnames end with a digit.'.
aMenu add: 'uninstall this change set' action: #uninstallChangeSet.
aMenu balloonTextForLastItem:
'Attempt to uninstall this change set. CAUTION: this may not work completely and is irreversible!!'.
aMenu addLine.
aMenu add: 'file into new...' action: #fileIntoNewChangeSet.
aMenu balloonTextForLastItem:
'Load a fileout from disk and place its changes into a new change set (seldom needed -- much better to do this from a file-list browser these days.)'.
aMenu add: 'reorder all change sets' action: #reorderChangeSets.
aMenu balloonTextForLastItem:
'Applies a standard reordering of all change-sets in the system -- at the bottom will come the sets that come with the release; next will come all the numbered updates; finally, at the top, will come all other change sets'.
aMenu addLine.
aMenu add: 'more...' action: #offerUnshiftedChangeSetMenu.
aMenu balloonTextForLastItem:
'Takes you back to the primary change-set menu.'.
^ aMenu!
----- Method: ChangeSorter>>shiftedMessageMenu: (in category 'message list') -----
shiftedMessageMenu: aMenu
"Arm the menu so that it holds items appropriate to the message-list while the shift key is down. Answer the menu."
^ aMenu addList: #(
-
('method pane' makeIsolatedCodePane)
('toggle diffing (D)' toggleDiffing)
('implementors of sent messages' browseAllMessages)
('change category...' changeCategory)
-
('sample instance' makeSampleInstance)
('inspect instances' inspectInstances)
('inspect subinstances' inspectSubInstances)
-
('change sets with this method' findMethodInChangeSets)
('revert to previous version' revertToPreviousVersion)
('revert & remove from changes' revertAndForget)
-
('more...' unshiftedYellowButtonActivity))!
----- Method: ChangeSorter>>showCategoriesOfChangeSet (in category 'changeSet menu') -----
showCategoriesOfChangeSet
"Show a list of all the categories in which the selected change-set occurs at the moment. Install the one the user chooses, if any."
| aMenu |
Smalltalk isMorphic
ifFalse:
[self inform:
'Only available in morphic, right now, sorry.
It would not take much to make this
also work in mvc, so if you are
inclined to do that, thanks in advance...']
ifTrue:
[aMenu := MenuMorph new defaultTarget: self.
aMenu title:
'Categories which
contain change set
"', myChangeSet name, '"'.
self changeSetCategories elementsInOrder do:
[:aCategory |
(aCategory includesChangeSet: myChangeSet)
ifTrue:
[aMenu add: aCategory categoryName target: self selector: #showChangeSetCategory: argument: aCategory.
aCategory == changeSetCategory ifTrue:
[aMenu lastItem color: Color red]].
aMenu balloonTextForLastItem: aCategory documentation].
aMenu popUpInWorld]!
----- Method: ChangeSorter>>showChangeSet: (in category 'access') -----
showChangeSet: chgSet
myChangeSet == chgSet ifFalse: [
myChangeSet := chgSet.
currentClassName := nil.
currentSelector := nil].
self changed: #relabel.
self changed: #currentCngSet. "new -- list of sets"
self changed: #mainButtonName. "old, button"
self changed: #classList.
self changed: #messageList.
self setContents.
self contentsChanged.!
----- Method: ChangeSorter>>showChangeSetCategory: (in category 'changeSet menu') -----
showChangeSetCategory: aChangeSetCategory
"Show the given change-set category"
changeSetCategory := aChangeSetCategory.
self changed: #changeSetList.
(self changeSetList includes: myChangeSet name) ifFalse:
[self showChangeSet: (ChangesOrganizer changeSetNamed: self changeSetList first)].
self changed: #relabel!
----- Method: ChangeSorter>>showChangeSetNamed: (in category 'access') -----
showChangeSetNamed: aName
self showChangeSet: (ChangesOrganizer changeSetNamed: aName) !
----- Method: ChangeSorter>>submergeIntoOtherSide (in category 'changeSet menu') -----
submergeIntoOtherSide
"Copy the contents of the receiver to the other side, then remove the receiver -- all after checking that all is well."
| other message nextToView i all |
self checkThatSidesDiffer: [^ self].
self okToChange ifFalse: [^ self].
other := (parent other: self) changeSet.
other == myChangeSet ifTrue: [^ self inform: 'Both sides are the same!!'].
myChangeSet isEmpty ifTrue: [^ self inform: 'Nothing to copy. To remove,
simply choose "remove".'].
myChangeSet okayToRemove ifFalse: [^ self].
message := 'Please confirm: copy all changes
in "', myChangeSet name, '" into "', other name, '"
and then destroy the change set
named "', myChangeSet name, '"?'.
(self confirm: message) ifFalse: [^ self].
(myChangeSet hasPreamble or: [myChangeSet hasPostscript]) ifTrue:
[(self confirm:
'Caution!! This change set has a preamble or
a postscript or both. If you submerge it into
the other side, these will be lost.
Do you really want to go ahead with this?') ifFalse: [^ self]].
other assimilateAllChangesFoundIn: myChangeSet.
all := ChangeSet allChangeSets.
nextToView := ((all includes: myChangeSet)
and: [(i := all indexOf: myChangeSet) < all size])
ifTrue: [all at: i+1]
ifFalse: [other].
self removePrompting: false.
self showChangeSet: nextToView.
parent modelWakeUp.
!
----- Method: ChangeSorter>>subtractOtherSide (in category 'changeSet menu') -----
subtractOtherSide
"Subtract the changes found on the other side from the requesting side."
self checkThatSidesDiffer: [^ self].
myChangeSet forgetAllChangesFoundIn: ((parent other: self) changeSet).
self showChangeSet: myChangeSet!
----- Method: ChangeSorter>>toggleDiffing (in category 'code pane') -----
toggleDiffing
"Toggle whether diffs should be shown in the code pane"
self okToChange ifTrue:
[super toggleDiffing.
self changed: #contents.
self update]
!
----- Method: ChangeSorter>>trimHistory (in category 'changeSet menu') -----
trimHistory
"Drop non-essential history (rename, reorg, method removals) from newly-added classes."
myChangeSet trimHistory
!
----- Method: ChangeSorter>>uninstallChangeSet (in category 'changeSet menu') -----
uninstallChangeSet
"Attempt to uninstall the current change set, after confirmation."
self okToChange ifFalse: [^ self].
(self confirm: 'Uninstalling a changeSet is unreliable at best.
It will only work if the changeSet consists only of single
changes, additions and removals of methods, and if
no subsequent changes have been to any of them.
No changes to classes will be undone.
The changeSet will be cleared after uninstallation.
Do you still wish to attempt to uninstall this changeSet?')
ifFalse: [^ self].
myChangeSet uninstall.
self changed: #relabel.
self changed: #classList.
self changed: #messageList.
self setContents.
self contentsChanged.
!
----- Method: ChangeSorter>>update (in category 'changeSet menu') -----
update
"recompute all of my panes"
self updateIfNecessary.
parent ifNotNil: [(parent other: self) updateIfNecessary]!
----- Method: ChangeSorter>>updateIfNecessary (in category 'changeSet menu') -----
updateIfNecessary
"Recompute all of my panes."
| newList |
self okToChange ifFalse: [^ self].
myChangeSet ifNil: [^ self]. "Has been known to happen though shouldn't"
(myChangeSet isMoribund or: [(changeSetCategory notNil and: [changeSetCategory includesChangeSet: myChangeSet]) not]) ifTrue:
[self changed: #changeSetList.
^ self showChangeSet: self changeSetCategory defaultChangeSetToShow].
newList := self changeSetList.
(priorChangeSetList == nil or: [priorChangeSetList ~= newList])
ifTrue:
[priorChangeSetList := newList.
self changed: #changeSetList].
self showChangeSet: myChangeSet!
----- Method: ChangeSorter>>veryDeepFixupWith: (in category 'creation') -----
veryDeepFixupWith: deepCopier
super veryDeepFixupWith: deepCopier.
parent := deepCopier references at: parent ifAbsent: [parent].
self updateIfNecessary!
----- Method: ChangeSorter>>veryDeepInner: (in category 'creation') -----
veryDeepInner: deepCopier
"Copy all of my instance variables. Some need to be not copied at all, but shared."
super veryDeepInner: deepCopier.
"parent := parent. Weakly copied"
"myChangeSet := myChangeSet. Weakly copied"
currentClassName := currentClassName veryDeepCopyWith: deepCopier.
"currentSelector := currentSelector. Symbol"
priorChangeSetList := priorChangeSetList veryDeepCopyWith: deepCopier.
changeSetCategory := changeSetCategory.
!
----- Method: ChangeSorter>>wantsOptionalButtons (in category 'code pane') -----
wantsOptionalButtons
"No optional buttons for ChangeSorter"
^false!
----- Method: ChangeSorter>>withoutItemAnnotation: (in category 'annotation') -----
withoutItemAnnotation: aStringOrNil
"return the current item without the package annotation we added on"
| endItemIndex |
aStringOrNil ifNil: [^nil] .
( endItemIndex := aStringOrNil findString: self beginNote) = 0
ifTrue: [^ aStringOrNil ] .
^ aStringOrNil first: endItemIndex - 1!
----- Method: CodeHolder>>abbreviatedWordingFor: (in category 'commands') -----
abbreviatedWordingFor: aButtonSelector
"Answer the abbreviated form of wording, from a static table which you're welcome to edit. Answer nil if there is no entry -- in which case the long firm will be used on the corresponding browser button."
#(
(browseMethodFull 'browse')
(browseSendersOfMessages 'senders')
(browseMessages 'impl')
(browseVersions 'vers')
(methodHierarchy 'inher')
(classHierarchy 'hier')
(browseInstVarRefs 'iVar')
(browseClassVarRefs 'cVar')
(offerMenu 'menu')) do:
[:pair | pair first == aButtonSelector ifTrue: [^ pair second]].
^ nil!
----- Method: CodeHolder>>aboutToStyle: (in category 'contents') -----
aboutToStyle: aStyler
"This is a notification that aStyler is about to re-style its text.
The default is to answer false to veto the styling"
^false!
----- Method: CodeHolder>>addModelItemsToWindowMenu: (in category 'tiles') -----
addModelItemsToWindowMenu: aMenu
"Add model-related item to the window menu"
super addModelItemsToWindowMenu: aMenu.
Smalltalk isMorphic ifTrue:
[aMenu addLine.
aMenu add: 'what to show...' translated target: self action: #offerWhatToShowMenu]!
----- Method: CodeHolder>>addOptionalAnnotationsTo:at:plus: (in category 'annotation') -----
addOptionalAnnotationsTo: window at: fractions plus: verticalOffset
"Add an annotation pane to the window if preferences indicate a desire for it, and return the incoming verticalOffset plus the height of the added pane, if any"
| aTextMorph divider delta |
self wantsAnnotationPane ifFalse: [^ verticalOffset].
aTextMorph := PluggableTextMorph
on: self
text: #annotation
accept: #annotation:
readSelection: nil
menu: #annotationPaneMenu:shifted:.
aTextMorph
askBeforeDiscardingEdits: true;
acceptOnCR: true;
borderWidth: 0;
hideScrollBarsIndefinitely.
divider := BorderedSubpaneDividerMorph forBottomEdge.
divider extent: 4 at 4; color: Color transparent; borderColor: #raised; borderWidth: 2.
delta := self defaultAnnotationPaneHeight.
window
addMorph: aTextMorph
fullFrame: (LayoutFrame
fractions: fractions
offsets: (0 at verticalOffset corner: 0@(verticalOffset + delta - 2))).
window
addMorph: divider
fullFrame: (LayoutFrame
fractions: fractions
offsets: (0@(verticalOffset + delta - 2) corner: 0@(verticalOffset + delta))).
^ verticalOffset + delta!
----- Method: CodeHolder>>addPriorVersionsCountForSelector:ofClass:to: (in category 'annotation') -----
addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream
"add an annotation detailing the prior versions count"
| versionsCount |
versionsCount := VersionsBrowser versionCountForSelector: aSelector class: aClass.
aStream nextPutAll:
((versionsCount > 1
ifTrue:
[versionsCount == 2 ifTrue:
['1 prior version']
ifFalse:
[versionsCount printString, ' prior versions']]
ifFalse:
['no prior versions']), self annotationSeparator)!
----- Method: CodeHolder>>adoptMessageInCurrentChangeset (in category 'commands') -----
adoptMessageInCurrentChangeset
"Add the receiver's method to the current change set if not already there"
self setClassAndSelectorIn: [:cl :sel |
cl ifNotNil:
[ChangeSet current adoptSelector: sel forClass: cl.
self changed: #annotation]]
!
----- Method: CodeHolder>>annotation (in category 'annotation') -----
annotation
"Provide a line of content for an annotation pane, representing information about the method associated with the selected class and selector in the receiver."
| aSelector aClass |
((aSelector := self selectedMessageName) == nil or: [(aClass := self selectedClassOrMetaClass) == nil]) ifTrue: [^ ''].
^ self annotationForSelector: aSelector ofClass: aClass!
----- Method: CodeHolder>>annotationForClassCommentFor: (in category 'annotation') -----
annotationForClassCommentFor: aClass
"Provide a line of content for an annotation pane, given that the receiver is pointing at the clas comment of the given class."
| aStamp nonMeta |
aStamp := (nonMeta := aClass theNonMetaClass) organization commentStamp.
^ aStamp
ifNil:
[nonMeta name, ' has no class comment']
ifNotNil:
['class comment for ', nonMeta name,
(aStamp = '<historical>'
ifFalse:
[' - ', aStamp]
ifTrue:
[''])]!
----- Method: CodeHolder>>annotationForClassDefinitionFor: (in category 'annotation') -----
annotationForClassDefinitionFor: aClass
"Provide a line of content for an annotation pane, given that the receiver is pointing at the class definition of the given class."
^ 'Class definition for ', aClass name!
----- Method: CodeHolder>>annotationForHierarchyFor: (in category 'annotation') -----
annotationForHierarchyFor: aClass
"Provide a line of content for an annotation pane, given that the receiver is pointing at the hierarchy of the given class."
^ 'Hierarchy for ', aClass name!
----- Method: CodeHolder>>annotationForSelector:ofClass: (in category 'annotation') -----
annotationForSelector: aSelector ofClass: aClass
"Provide a line of content for an annotation pane, representing
information about the given selector and class"
| stamp sendersCount implementorsCount aCategory separator aString aList aComment aStream requestList |
aSelector == #Comment
ifTrue: [^ self annotationForClassCommentFor: aClass].
aSelector == #Definition
ifTrue: [^ self annotationForClassDefinitionFor: aClass].
aSelector == #Hierarchy
ifTrue: [^ self annotationForHierarchyFor: aClass].
aStream := ReadWriteStream on: ''.
requestList := self annotationRequests.
separator := requestList size > 1
ifTrue: [self annotationSeparator]
ifFalse: [''].
requestList
do: [:aRequest |
aRequest == #firstComment
ifTrue: [aComment := aClass firstCommentAt: aSelector.
aComment isEmptyOrNil
ifFalse: [aStream nextPutAll: aComment , separator]].
aRequest == #masterComment
ifTrue: [aComment := aClass supermostPrecodeCommentFor: aSelector.
aComment isEmptyOrNil
ifFalse: [aStream nextPutAll: aComment , separator]].
aRequest == #documentation
ifTrue: [aComment := aClass precodeCommentOrInheritedCommentFor: aSelector.
aComment isEmptyOrNil
ifFalse: [aStream nextPutAll: aComment , separator]].
aRequest == #timeStamp
ifTrue: [stamp := self timeStamp.
aStream
nextPutAll: (stamp size > 0
ifTrue: [stamp , separator]
ifFalse: ['no timeStamp' , separator])].
aRequest == #messageCategory
ifTrue: [aCategory := aClass organization categoryOfElement: aSelector.
aCategory
ifNotNil: ["woud be nil for a method no longer present,
e.g. in a recent-submissions browser"
aStream nextPutAll: aCategory , separator]].
aRequest == #sendersCount
ifTrue: [sendersCount := (self systemNavigation allCallsOn: aSelector) size.
sendersCount := sendersCount == 1
ifTrue: ['1 sender']
ifFalse: [sendersCount printString , ' senders'].
aStream nextPutAll: sendersCount , separator].
aRequest == #implementorsCount
ifTrue: [implementorsCount := self systemNavigation numberOfImplementorsOf: aSelector.
implementorsCount := implementorsCount == 1
ifTrue: ['1 implementor']
ifFalse: [implementorsCount printString , ' implementors'].
aStream nextPutAll: implementorsCount , separator].
aRequest == #priorVersionsCount
ifTrue: [self
addPriorVersionsCountForSelector: aSelector
ofClass: aClass
to: aStream].
aRequest == #priorTimeStamp
ifTrue: [stamp := VersionsBrowser
timeStampFor: aSelector
class: aClass
reverseOrdinal: 2.
stamp
ifNotNil: [aStream nextPutAll: 'prior time stamp: ' , stamp , separator]].
aRequest == #recentChangeSet
ifTrue: [aString := ChangesOrganizer mostRecentChangeSetWithChangeForClass: aClass selector: aSelector.
aString size > 0
ifTrue: [aStream nextPutAll: aString , separator]].
aRequest == #allChangeSets
ifTrue: [aList := ChangesOrganizer allChangeSetsWithClass: aClass selector: aSelector.
aList size > 0
ifTrue: [aList size = 1
ifTrue: [aStream nextPutAll: 'only in change set ']
ifFalse: [aStream nextPutAll: 'in change sets: '].
aList
do: [:aChangeSet | aStream nextPutAll: aChangeSet name , ' ']]
ifFalse: [aStream nextPutAll: 'in no change set'].
aStream nextPutAll: separator]].
^ aStream contents!
----- Method: CodeHolder>>annotationPaneMenu:shifted: (in category 'annotation') -----
annotationPaneMenu: aMenu shifted: shifted
^ aMenu
labels: 'change pane size'
lines: #()
selections: #(toggleAnnotationPaneSize)!
----- Method: CodeHolder>>annotationRequests (in category 'annotation') -----
annotationRequests
^ Preferences defaultAnnotationRequests!
----- Method: CodeHolder>>annotationSeparator (in category 'annotation') -----
annotationSeparator
"Answer the separator to be used between annotations"
^ ' · '!
----- Method: CodeHolder>>browseImplementors (in category 'commands') -----
browseImplementors
"Create and schedule a message set browser on all implementors of the currently selected message selector. Do nothing if no message is selected."
| aMessageName |
(aMessageName := self selectedMessageName) ifNotNil:
[self systemNavigation browseAllImplementorsOf: aMessageName]!
----- Method: CodeHolder>>browseSenders (in category 'commands') -----
browseSenders
"Create and schedule a message set browser on all senders of the currently selected message selector. Of there is no message currently selected, offer a type-in"
self sendQuery: #browseAllCallsOn: to: self systemNavigation!
----- Method: CodeHolder>>buildClassBrowserEditString: (in category 'construction') -----
buildClassBrowserEditString: aString
"Create and schedule a new class browser for the current selection, with initial textual contents set to aString. This is used specifically in spawning where a class is established but a method-category is not."
| newBrowser |
newBrowser := Browser new.
newBrowser setClass: self selectedClassOrMetaClass selector: nil.
newBrowser editSelection: #newMessage.
Browser openBrowserView: (newBrowser openOnClassWithEditString: aString)
label: 'Class Browser: ', self selectedClassOrMetaClass name
!
----- Method: CodeHolder>>buildCodePaneWith: (in category 'toolbuilder') -----
buildCodePaneWith: builder
| textSpec top buttonSpec annoSpec |
self wantsOptionalButtons ifTrue:[
top := builder pluggablePanelSpec new.
top children: OrderedCollection new.
buttonSpec := self buildOptionalButtonsWith: builder.
buttonSpec frame: (0 at 0 corner: 1 at 0.11).
top children add: buttonSpec.
].
textSpec := builder pluggableCodePaneSpec new.
textSpec
model: self;
getText: #contents;
setText: #contents:notifying:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:.
self wantsAnnotationPane ifTrue:[
top ifNil:[
top := builder pluggablePanelSpec new.
top children: OrderedCollection new.
].
annoSpec := builder pluggableInputFieldSpec new.
annoSpec model: self; getText: #annotation.
annoSpec frame: (0 at 0.88 corner: 1 at 1).
top children add: annoSpec.
].
top ifNotNil:[
textSpec frame: (0 @ (buttonSpec ifNil:[0] ifNotNil:[0.12])
corner: 1 @ (annoSpec ifNil:[1] ifNotNil:[0.88])).
top children add: textSpec.
].
^top ifNil:[textSpec]!
----- Method: CodeHolder>>buildCodeProvenanceButtonWith: (in category 'toolbuilder') -----
buildCodeProvenanceButtonWith: builder
| buttonSpec |
buttonSpec := builder pluggableActionButtonSpec new.
buttonSpec model: self.
buttonSpec label: #codePaneProvenanceString.
buttonSpec action: #offerWhatToShowMenu.
buttonSpec help: 'Governs what view is shown in the code pane. Click here to change the view'.
^buttonSpec!
----- Method: CodeHolder>>buildMorphicCodePaneWith: (in category 'construction') -----
buildMorphicCodePaneWith: editString
"Construct the pane that shows the code.
Respect the Preference for standardCodeFont."
| codePane |
codePane := MorphicTextEditor default
on: self
text: #contents
accept: #contents:notifying:
readSelection: #contentsSelection
menu: #codePaneMenu:shifted:.
codePane font: Preferences standardCodeFont.
editString
ifNotNil: [codePane editString: editString.
codePane hasUnacceptedEdits: true].
^ codePane!
----- Method: CodeHolder>>buildOptionalButtonsWith: (in category 'toolbuilder') -----
buildOptionalButtonsWith: builder
| panelSpec buttonSpec |
panelSpec := builder pluggablePanelSpec new.
panelSpec children: OrderedCollection new.
self optionalButtonPairs do:[:spec|
buttonSpec := builder pluggableActionButtonSpec new.
buttonSpec model: self.
buttonSpec label: spec first.
buttonSpec action: spec second.
spec second == #methodHierarchy ifTrue:[
buttonSpec color: #inheritanceButtonColor.
].
spec size > 2 ifTrue:[buttonSpec help: spec third].
panelSpec children add: buttonSpec.
].
"What to show"
panelSpec children add: (self buildCodeProvenanceButtonWith: builder).
panelSpec layout: #horizontal. "buttons"
^panelSpec!
----- Method: CodeHolder>>canShowMultipleMessageCategories (in category 'message category functions') -----
canShowMultipleMessageCategories
"Answer whether the receiver is capable of showing multiple message categories"
^ false!
----- Method: CodeHolder>>categoryFromUserWithPrompt:for: (in category 'categories') -----
categoryFromUserWithPrompt: aPrompt for: aClass
"self new categoryFromUserWithPrompt: 'testing' for: SystemDictionary"
| labels myCategories reject lines cats newName menuIndex |
labels := OrderedCollection with: 'new...'.
labels addAll: (myCategories := aClass organization categories asSortedCollection:
[:a :b | a asLowercase < b asLowercase]).
reject := myCategories asSet.
reject
add: ClassOrganizer nullCategory;
add: ClassOrganizer default.
lines := OrderedCollection with: 1 with: (myCategories size + 1).
aClass allSuperclasses do:
[:cls |
cats := cls organization categories reject:
[:cat | reject includes: cat].
cats isEmpty ifFalse:
[lines add: labels size.
labels addAll: (cats asSortedCollection:
[:a :b | a asLowercase < b asLowercase]).
reject addAll: cats]].
newName := (labels size = 1 or:
[menuIndex := (UIManager default chooseFrom: labels lines: lines title: aPrompt).
menuIndex = 0 ifTrue: [^ nil].
menuIndex = 1])
ifTrue:
[UIManager default request: 'Please type new category name'
initialAnswer: 'category name']
ifFalse:
[labels at: menuIndex].
^ newName ifNotNil: [newName asSymbol]!
----- Method: CodeHolder>>categoryOfCurrentMethod (in category 'categories') -----
categoryOfCurrentMethod
"Answer the category that owns the current method. If unable to determine a category, answer nil."
| aClass aSelector |
^ (aClass := self selectedClassOrMetaClass)
ifNotNil: [(aSelector := self selectedMessageName)
ifNotNil: [aClass whichCategoryIncludesSelector: aSelector]]!
----- Method: CodeHolder>>changeCategory (in category 'categories') -----
changeCategory
"Present a menu of the categories of messages for the current class,
and let the user choose a new category for the current message"
| aClass aSelector |
(aClass := self selectedClassOrMetaClass) ifNotNil:
[(aSelector := self selectedMessageName) ifNotNil:
[(self letUserReclassify: aSelector in: aClass) ifTrue:
["ChangeSet current reorganizeClass: aClass."
"Decided on further review that the above, when present, could cause more
unexpected harm than good"
self methodCategoryChanged]]]!
----- Method: CodeHolder>>codePaneProvenanceButton (in category 'controls') -----
codePaneProvenanceButton
"Answer a button that reports on, and allow the user to modify,
the code-pane-provenance setting"
| aButton |
aButton := UpdatingSimpleButtonMorph newWithLabel: 'source'.
aButton setNameTo: 'codeProvenance'.
aButton useSquareCorners.
aButton target: self;
wordingSelector: #codePaneProvenanceString;
actionSelector: #offerWhatToShowMenu.
aButton setBalloonText: 'Governs what view is shown in the code pane. Click here to change the view'.
aButton actWhen: #buttonDown.
aButton color: Color white;
borderStyle: BorderStyle thinGray; vResizing: #spaceFill.
^ aButton!
----- Method: CodeHolder>>codePaneProvenanceString (in category 'controls') -----
codePaneProvenanceString
"Answer a string that reports on code-pane-provenance"
| symsAndWordings |
(symsAndWordings := self contentsSymbolQuints) do:
[:aQuad |
contentsSymbol == aQuad first ifTrue: [^ aQuad fourth]].
^ symsAndWordings first fourth "default to plain source, for example if nil as initially"!
----- Method: CodeHolder>>commentContents (in category 'contents') -----
commentContents
"documentation for the selected method"
| poss aClass aSelector |
^ (poss := (aClass := self selectedClassOrMetaClass)
ifNil:
['----']
ifNotNil:
[(aSelector := self selectedMessageName)
ifNil:
['---']
ifNotNil:
[(aClass precodeCommentOrInheritedCommentFor: aSelector)", String cr, String cr, self timeStamp"
"which however misses comments that are between the temps declaration and the body of the method; those are picked up by ·aClass commentOrInheritedCommentFor: aSelector· but that method will get false positives from comments *anywhere* in the method source"]])
isEmptyOrNil
ifTrue:
[aSelector
ifNotNil:
[((aClass methodHeaderFor: aSelector), '
Has no comment') asText makeSelectorBoldIn: aClass]
ifNil:
['Hamna']]
ifFalse: [aSelector
ifNotNil: [((aClass methodHeaderFor: aSelector), '
', poss) asText makeSelectorBoldIn: aClass]
ifNil: [poss]]!
----- Method: CodeHolder>>contents (in category 'contents') -----
contents
"Answer the source code or documentation for the selected method"
self showingByteCodes ifTrue:
[^ self selectedBytecodes].
self showingDocumentation ifTrue:
[^ self commentContents].
^ self selectedMessage!
----- Method: CodeHolder>>contentsChanged (in category 'contents') -----
contentsChanged
super contentsChanged.
self changed: #annotation!
----- Method: CodeHolder>>contentsSymbol (in category 'contents') -----
contentsSymbol
"Answer a symbol indicating what kind of content should be shown for the method; for normal showing of source code, this symbol is #source. A nil value in the contentsSymbol slot will be set to #source by this method"
^ contentsSymbol ifNil:
[contentsSymbol := Preferences browseWithPrettyPrint
ifTrue:
[#prettyPrint]
ifFalse:
[#source]]!
----- Method: CodeHolder>>contentsSymbol: (in category 'contents') -----
contentsSymbol: aSymbol
"Set the contentsSymbol as indicated. #source means to show source code, #comment means to show the first comment found in the source code"
contentsSymbol := aSymbol!
----- Method: CodeHolder>>contentsSymbolQuints (in category 'controls') -----
contentsSymbolQuints
"Answer a list of quintuplets representing information on the alternative views available in the code pane
first element: the contentsSymbol used
second element: the selector to call when this item is chosen.
third element: the selector to call to obtain the wording of the menu item.
fourth element: the wording to represent this view
fifth element: balloon help
A hypen indicates a need for a seperator line in a menu of such choices"
^ #(
(source togglePlainSource showingPlainSourceString 'source' 'the textual source code as written')
(documentation toggleShowDocumentation showingDocumentationString 'documentation' 'the first comment in the method')
-
(prettyPrint togglePrettyPrint prettyPrintString 'prettyPrint' 'the method source presented in a standard text format')
-
(showDiffs toggleRegularDiffing showingRegularDiffsString 'showDiffs' 'the textual source diffed from its prior version')
(prettyDiffs togglePrettyDiffing showingPrettyDiffsString 'prettyDiffs' 'formatted textual source diffed from formatted form of prior version')
-
(decompile toggleDecompile showingDecompileString 'decompile' 'source code decompiled from byteCodes')
(byteCodes toggleShowingByteCodes showingByteCodesString 'byteCodes' 'the bytecodes that comprise the compiled method')
-
(tiles toggleShowingTiles showingTilesString 'tiles' 'universal tiles representing the method'))!
----- Method: CodeHolder>>copyUpOrCopyDown (in category 'commands') -----
copyUpOrCopyDown
"Used to copy down code from a superclass to a subclass or vice-versa in one easy step, if you know what you're doing. Prompt the user for which class to copy down or copy up to, then spawn a fresh browser for that class, with the existing code planted in it, and with the existing method category also established."
| aClass aSelector allClasses implementors aMenu aColor |
Smalltalk isMorphic ifFalse: [^ self inform:
'Sorry, for the moment you have to be in
Morphic to use this feature.'].
((aClass := self selectedClassOrMetaClass) isNil or: [(aSelector := self selectedMessageName) == nil])
ifTrue: [^ Beeper beep].
allClasses := self systemNavigation hierarchyOfClassesSurrounding: aClass.
implementors := self systemNavigation hierarchyOfImplementorsOf: aSelector forClass: aClass.
aMenu := MenuMorph new defaultTarget: self.
aMenu title:
aClass name, '.', aSelector, '
Choose where to insert a copy of this method
(blue = current, black = available, red = other implementors'.
allClasses do:
[:cl |
aColor := cl == aClass
ifTrue: [#blue]
ifFalse:
[(implementors includes: cl)
ifTrue: [#red]
ifFalse: [#black]].
(aColor == #red)
ifFalse:
[aMenu add: cl name selector: #spawnToClass: argument: cl]
ifTrue:
[aMenu add: cl name selector: #spawnToCollidingClass: argument: cl].
aMenu lastItem color: (Color colorFrom: aColor)].
aMenu popUpInWorld!
----- Method: CodeHolder>>decompiledSourceIntoContents (in category 'message list') -----
decompiledSourceIntoContents
"Obtain a source string by decompiling the method's code, and place
that source string into my contents. Also return the string.
Get temps from source file if shift key is pressed."
| class |
class := self selectedClassOrMetaClass.
"Was method deleted while in another project?"
currentCompiledMethod := (class compiledMethodAt: self selectedMessageName ifAbsent: [^ '']).
contents := (Sensor leftShiftDown not)
ifTrue: [currentCompiledMethod decompileWithTemps]
ifFalse: [currentCompiledMethod decompile].
contents := contents decompileString asText makeSelectorBoldIn: class.
^ contents copy!
----- Method: CodeHolder>>decorateButtons (in category 'controls') -----
decorateButtons
"Change screen feedback for any buttons in the UI of the receiver that may wish it. Initially, it is only the Inheritance button that is decorated, but one can imagine others."
self changed: #inheritanceButtonColor.!
----- Method: CodeHolder>>defaultAnnotationPaneHeight (in category 'annotation') -----
defaultAnnotationPaneHeight
"Answer the receiver's preferred default height for new annotation panes."
^ Preferences parameterAt: #defaultAnnotationPaneHeight ifAbsentPut: [25]!
----- Method: CodeHolder>>defaultButtonPaneHeight (in category 'annotation') -----
defaultButtonPaneHeight
"Answer the user's preferred default height for new button panes."
^ Preferences parameterAt: #defaultButtonPaneHeight ifAbsentPut: [25]!
----- Method: CodeHolder>>defaultDiffsSymbol (in category 'diffs') -----
defaultDiffsSymbol
"Answer the code symbol to use when generically switching to diffing"
^ Preferences diffsWithPrettyPrint
ifTrue:
[#prettyDiffs]
ifFalse:
[#showDiffs]!
----- Method: CodeHolder>>didCodeChangeElsewhere (in category 'self-updating') -----
didCodeChangeElsewhere
"Determine whether the code for the currently selected method and class has been changed somewhere else."
| aClass aSelector aCompiledMethod |
currentCompiledMethod ifNil: [^ false].
(aClass := self selectedClassOrMetaClass) ifNil: [^ false].
(aSelector := self selectedMessageName) ifNil: [^ false].
self classCommentIndicated
ifTrue: [^ currentCompiledMethod ~~ aClass organization commentRemoteStr].
^ (aCompiledMethod := aClass compiledMethodAt: aSelector ifAbsent: [^ false]) ~~ currentCompiledMethod
and: [aCompiledMethod last ~= 0 "either not yet installed"
or: [ currentCompiledMethod last = 0 "or these methods don't have source pointers"]]!
----- Method: CodeHolder>>diffButton (in category 'diffs') -----
diffButton
"Return a checkbox that lets the user decide whether diffs should be shown or not. Not sent any more but retained against the possibility of existing subclasses outside the base image using it."
| outerButton aButton |
outerButton := AlignmentMorph newRow.
outerButton wrapCentering: #center; cellPositioning: #leftCenter.
outerButton color: Color transparent.
outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap.
outerButton addMorph: (aButton := UpdatingThreePhaseButtonMorph checkBox).
aButton
target: self;
actionSelector: #toggleRegularDiffing;
getSelector: #showingRegularDiffs.
outerButton addMorphBack: (StringMorph contents: 'diffs') lock.
outerButton setBalloonText: 'If checked, then code differences from the previous version, if any, will be shown.'.
^ outerButton
!
----- Method: CodeHolder>>diffFromPriorSourceFor: (in category 'diffs') -----
diffFromPriorSourceFor: sourceCode
"If there is a prior version of source for the selected method, return a diff, else just return the source code"
| prior |
^ (prior := self priorSourceOrNil)
ifNil: [sourceCode]
ifNotNil: [TextDiffBuilder buildDisplayPatchFrom: prior to: sourceCode inClass: self selectedClass prettyDiffs: self showingPrettyDiffs]!
----- Method: CodeHolder>>getSelectorAndSendQuery:to: (in category 'misc') -----
getSelectorAndSendQuery: querySelector to: queryPerformer
"Obtain a selector relevant to the current context, and then send the querySelector to the queryPerformer with the selector obtained as its argument. If no message is currently selected, then obtain a method name from a user type-in"
self getSelectorAndSendQuery: querySelector to: queryPerformer with: { }.
!
----- Method: CodeHolder>>getSelectorAndSendQuery:to:with: (in category 'misc') -----
getSelectorAndSendQuery: querySelector to: queryPerformer with: queryArgs
"Obtain a selector relevant to the current context, and then send the querySelector to the queryPerformer with the selector obtained and queryArgs as its arguments. If no message is currently selected, then obtain a method name from a user type-in"
| strm array |
strm := WriteStream on: (array := Array new: queryArgs size + 1).
strm nextPut: nil.
strm nextPutAll: queryArgs.
self selectedMessageName ifNil: [ | selector |
selector := UIManager default request: 'Type selector:' initialAnswer: 'flag:'.
selector := selector copyWithout: Character space.
^ selector isEmptyOrNil ifFalse: [
(Symbol hasInterned: selector
ifTrue: [ :aSymbol |
array at: 1 put: aSymbol.
queryPerformer perform: querySelector withArguments: array])
ifFalse: [ self inform: 'no such selector']
]
].
self selectMessageAndEvaluate: [:selector |
array at: 1 put: selector.
queryPerformer perform: querySelector withArguments: array
]!
----- Method: CodeHolder>>inheritanceButtonColor (in category 'toolbuilder') -----
inheritanceButtonColor
"Check to see if the currently-viewed method has a super send or an override, and if so, change screen feedback, unless the #decorateBrowserButtons says not to."
| flags aColor |
((currentCompiledMethod isKindOf: CompiledMethod) and: [Preferences decorateBrowserButtons])
ifFalse: [^Color transparent].
"This table duplicates the old logic, but adds two new colors for the cases where there is a superclass definition, but this method doesn't call it."
flags := 0.
self isThisAnOverride ifTrue: [ flags := flags bitOr: 4 ].
currentCompiledMethod sendsToSuper ifTrue: [ flags := flags bitOr: 2 ].
self isThereAnOverride ifTrue: [ flags := flags bitOr: 1 ].
aColor := {
Color transparent.
Color tan lighter.
Color green muchLighter.
Color blue muchLighter.
Color red muchLighter. "has super but doesn't call it"
(Color r: 0.94 g: 0.823 b: 0.673). "has sub; has super but doesn't call it"
Color green muchLighter.
Color blue muchLighter.
} at: flags + 1.
^aColor!
----- Method: CodeHolder>>installTextualCodingPane (in category 'tiles') -----
installTextualCodingPane
"Install text into the code pane"
| aWindow codePane aPane boundsToUse |
(aWindow := self containingWindow) ifNil: [self error: 'where''s that window?'].
codePane := aWindow findDeepSubmorphThat:
[:m | ((m isKindOf: PluggableTextMorph) and: [m getTextSelector == #contents]) or:
[m isKindOf: PluggableTileScriptorMorph]] ifAbsent: [self error: 'no code pane'].
aPane := self buildMorphicCodePaneWith: nil.
boundsToUse := (codePane bounds origin- (1 at 1)) corner: (codePane owner bounds corner " (1 at 1").
aWindow replacePane: codePane with: aPane.
aPane vResizing: #spaceFill; hResizing: #spaceFill; borderWidth: 0.
aPane bounds: boundsToUse.
aPane owner clipSubmorphs: false.
self contentsChanged!
----- Method: CodeHolder>>isModeStyleable (in category 'contents') -----
isModeStyleable
"determine the current mode can be styled"
^ self showingSource or: [self showingPrettyPrint]!
----- Method: CodeHolder>>isThereAnOverride (in category 'misc') -----
isThereAnOverride
"Answer whether any subclass of my selected class implements my
selected selector"
| aName aClass |
aName := self selectedMessageName ifNil: [^ false].
aClass := self selectedClassOrMetaClass ifNil: [^ false].
aClass allSubclassesDo: [ :cls | (cls includesSelector: aName) ifTrue: [ ^true ]].
^ false!
----- Method: CodeHolder>>isThisAnOverride (in category 'misc') -----
isThisAnOverride
"Answer whether any superclass of my selected class implements my selected selector"
| aName aClass |
aName := self selectedMessageName ifNil: [^ false].
aClass := self selectedClassOrMetaClass ifNil: [^ false].
aClass allSuperclassesDo: [ :cls | (cls includesSelector: aName) ifTrue: [ ^true ]].
^ false!
----- Method: CodeHolder>>letUserReclassify:in: (in category 'categories') -----
letUserReclassify: anElement in: aClass
"Put up a list of categories and solicit one from the user.
Answer true if user indeed made a change, else false"
| currentCat newCat |
currentCat := aClass organization categoryOfElement: anElement.
newCat := self
categoryFromUserWithPrompt: 'choose category (currently "', currentCat, '")'
for: aClass.
(newCat ~~ nil and: [newCat ~= currentCat])
ifTrue:
[aClass organization classify: anElement under: newCat suppressIfDefault: false.
^ true]
ifFalse:
[^ false]!
----- Method: CodeHolder>>listPaneWithSelector: (in category 'categories & search pane') -----
listPaneWithSelector: aSelector
"If, among my window's paneMorphs, there is a list pane defined with aSelector as its retriever, answer it, else answer nil"
| aWindow |
^ (aWindow := self containingWindow) ifNotNil:
[aWindow paneMorphSatisfying:
[:aMorph | (aMorph isKindOf: PluggableListMorph) and:
[aMorph getListSelector == aSelector]]]!
----- Method: CodeHolder>>makeSampleInstance (in category 'traits') -----
makeSampleInstance
| aClass nonMetaClass anInstance |
((aClass := self selectedClassOrMetaClass) isNil or: [aClass isTrait]) ifTrue: [^ self].
nonMetaClass := aClass theNonMetaClass.
anInstance := self sampleInstanceOfSelectedClass.
(anInstance isNil and: [nonMetaClass ~~ UndefinedObject]) ifTrue:
[^ self inform: 'Sorry, cannot make an instance of ', nonMetaClass name].
(Smalltalk isMorphic and: [anInstance isMorph])
ifTrue:
[self currentHand attachMorph: anInstance]
ifFalse:
[anInstance inspectWithLabel: 'An instance of ', nonMetaClass name]!
----- Method: CodeHolder>>menuButton (in category 'misc') -----
menuButton
"Answer a button that brings up a menu. Useful when adding new features, but at present is between uses"
| aButton |
aButton := IconicButton new target: self;
borderWidth: 0;
labelGraphic: (ScriptingSystem formAtKey: #TinyMenu);
color: Color transparent;
actWhen: #buttonDown;
actionSelector: #offerMenu;
yourself.
aButton setBalloonText: 'click here to get a menu with further options'.
^ aButton
!
----- Method: CodeHolder>>messageListKey:from: (in category 'message list menu') -----
messageListKey: aChar from: view
"Respond to a Command key. I am a model with a code pane, and I also
have a listView that has a list of methods. The view knows how to get
the list and selection."
| sel class |
aChar == $D ifTrue: [^ self toggleDiffing].
sel := self selectedMessageName.
aChar == $m ifTrue: "These next two put up a type in if no message selected"
[^ self useSelector: sel orGetSelectorAndSendQuery: #browseAllImplementorsOf: to: self systemNavigation].
aChar == $n ifTrue:
[^ self useSelector: sel orGetSelectorAndSendQuery: #browseAllCallsOn: to: self systemNavigation].
"The following require a class selection"
(class := self selectedClassOrMetaClass) ifNil: [^ self arrowKey: aChar from: view].
aChar == $b ifTrue: [^ Browser fullOnClass: class selector: sel].
aChar == $N ifTrue: [^ self browseClassRefs].
aChar == $i ifTrue: [^ self methodHierarchy].
aChar == $h ifTrue: [^ self classHierarchy].
aChar == $p ifTrue: [^ self browseFullProtocol].
"The following require a method selection"
sel ifNotNil:
[aChar == $o ifTrue: [^ self fileOutMessage].
aChar == $c ifTrue: [^ self copySelector].
aChar == $v ifTrue: [^ self browseVersions].
aChar == $O ifTrue: [^ self openSingleMessageBrowser].
aChar == $x ifTrue: [^ self removeMessage].
aChar == $d ifTrue: [^ self removeMessageFromBrowser].
(aChar == $C and: [self canShowMultipleMessageCategories])
ifTrue: [^ self showHomeCategory]].
^ self arrowKey: aChar from: view!
----- Method: CodeHolder>>methodCategoryChanged (in category 'categories') -----
methodCategoryChanged
self changed: #annotation!
----- Method: CodeHolder>>modelWakeUpIn: (in category 'misc') -----
modelWakeUpIn: aWindow
"The window has been activated. Respond to possible changes that may have taken place while it was inactive"
self updateListsAndCodeIn: aWindow.
self decorateButtons.
self refreshAnnotation.
super modelWakeUpIn: aWindow!
----- Method: CodeHolder>>newSearchPane (in category 'categories & search pane') -----
newSearchPane
"Answer a new search pane for the receiver"
| aTextMorph |
aTextMorph := PluggableTextMorph on: self
text: #lastSearchString accept: #lastSearchString:
readSelection: nil menu: nil.
aTextMorph setProperty: #alwaysAccept toValue: true.
aTextMorph askBeforeDiscardingEdits: false.
aTextMorph acceptOnCR: true.
aTextMorph setBalloonText: 'Type here and hit ENTER, and all methods whose selectors match what you typed will appear in the list pane below.'.
^ aTextMorph!
----- Method: CodeHolder>>offerMenu (in category 'commands') -----
offerMenu
"Offer a menu to the user from the bar of tool buttons"
self offerDurableMenuFrom: #messageListMenu:shifted: shifted: false!
----- Method: CodeHolder>>offerShiftedClassListMenu (in category 'commands') -----
offerShiftedClassListMenu
"Offer the shifted class-list menu."
^ self offerMenuFrom: #classListMenu:shifted: shifted: true!
----- Method: CodeHolder>>offerUnshiftedClassListMenu (in category 'commands') -----
offerUnshiftedClassListMenu
"Offer the shifted class-list menu."
^ self offerMenuFrom: #classListMenu:shifted: shifted: false!
----- Method: CodeHolder>>offerWhatToShowMenu (in category 'what to show') -----
offerWhatToShowMenu
"Offer a menu governing what to show"
| builder menuSpec item |
builder := ToolBuilder default.
menuSpec := builder pluggableMenuSpec new.
self contentsSymbolQuints do: [:aQuint | aQuint == #-
ifTrue: [menuSpec addSeparator]
ifFalse: [
item := menuSpec add: (self perform: aQuint third)
target: self selector: aQuint second argumentList: #().
item help: aQuint fifth.
].
].
builder runModal: (builder open: menuSpec).!
----- Method: CodeHolder>>okayToAccept (in category 'misc') -----
okayToAccept
"Answer whether it is okay to accept the receiver's input"
self showingDocumentation ifTrue:
[self inform:
'Sorry, for the moment you can
only submit changes here when
you are showing source. Later, you
will be able to edit the isolated comment
here and save it back, but only if YOU
implement it!!.'.
^ false].
self showingAnyKindOfDiffs ifFalse:
[^ true].
^ (UIManager default chooseFrom: {
'accept anyway -- I''ll take my chances'.
'um, let me reconsider'.
} title:
'Caution!! You are "showing diffs" here, so
there is a danger that some of the text in the
code pane is contaminated by the "diff" display') = 1!
----- Method: CodeHolder>>optionalButtonPairs (in category 'controls') -----
optionalButtonPairs
"Answer a tuple (formerly pairs) defining buttons, in the format:
button label
selector to send
help message"
| aList |
aList := #(
('browse' browseMethodFull 'view this method in a browser')
('senders' browseSendersOfMessages 'browse senders of...')
('implementors' browseMessages 'browse implementors of...')
('versions' browseVersions 'browse versions')),
(Preferences decorateBrowserButtons
ifTrue:
[{#('inheritance' methodHierarchy 'browse method inheritance
green: sends to super
tan: has override(s)
mauve: both of the above
pink: is an override but doesn''t call super
pinkish tan: has override(s), also is an override but doesn''t call super' )}]
ifFalse:
[{#('inheritance' methodHierarchy 'browse method inheritance')}]),
#(
('hierarchy' classHierarchy 'browse class hierarchy')
('inst vars' browseInstVarRefs 'inst var refs...')
('class vars' browseClassVarRefs 'class var refs...')).
^ aList!
----- Method: CodeHolder>>prettyDiffButton (in category 'diffs') -----
prettyDiffButton
"Return a checkbox that lets the user decide whether prettyDiffs should be shown or not"
| outerButton aButton |
outerButton := AlignmentMorph newRow.
outerButton wrapCentering: #center; cellPositioning: #leftCenter.
outerButton color: Color transparent.
outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap.
outerButton addMorph: (aButton := UpdatingThreePhaseButtonMorph checkBox).
aButton
target: self;
actionSelector: #togglePrettyDiffing;
getSelector: #showingPrettyDiffs.
outerButton addMorphBack: (StringMorph contents: 'prettyDiffs') lock.
(self isKindOf: VersionsBrowser)
ifTrue:
[outerButton setBalloonText: 'If checked, then pretty-printed code differences from the previous version, if any, will be shown.']
ifFalse:
[outerButton setBalloonText: 'If checked, then pretty-printed code differences between the file-based method and the in-memory version, if any, will be shown.'].
^ outerButton
!
----- Method: CodeHolder>>prettyPrintString (in category 'what to show') -----
prettyPrintString
"Answer whether the receiver is showing pretty-print"
^ ((contentsSymbol == #prettyPrint)
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'prettyPrint'!
----- Method: CodeHolder>>priorSourceOrNil (in category 'misc') -----
priorSourceOrNil
"If the currently-selected method has a previous version, return its source, else return nil"
| aClass aSelector changeRecords |
(aClass := self selectedClassOrMetaClass) ifNil: [^ nil].
(aSelector := self selectedMessageName) ifNil: [^ nil].
changeRecords := aClass changeRecordsAt: aSelector.
(changeRecords == nil or: [changeRecords size <= 1]) ifTrue: [^ nil].
^ (changeRecords at: 2) string
!
----- Method: CodeHolder>>refreshAnnotation (in category 'misc') -----
refreshAnnotation
"If the receiver has an annotation pane that does not bear unaccepted edits, refresh it"
(self dependents detect: [:m | (m inheritsFromAnyIn: #('PluggableTextView' 'PluggableTextMorph')) and: [m getTextSelector == #annotation]] ifNone: [nil]) ifNotNilDo:
[:aPane | aPane hasUnacceptedEdits ifFalse:
[aPane update: #annotation]]!
----- Method: CodeHolder>>refusesToAcceptCode (in category 'misc') -----
refusesToAcceptCode
"Answer whether receiver, given its current contentsSymbol, could accept code happily if asked to"
^ (#(byteCodes documentation tiles) includes: self contentsSymbol)!
----- Method: CodeHolder>>regularDiffButton (in category 'diffs') -----
regularDiffButton
"Return a checkbox that lets the user decide whether regular diffs should be shown or not"
| outerButton aButton |
outerButton := AlignmentMorph newRow.
outerButton wrapCentering: #center; cellPositioning: #leftCenter.
outerButton color: Color transparent.
outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap.
outerButton addMorph: (aButton := UpdatingThreePhaseButtonMorph checkBox).
aButton
target: self;
actionSelector: #toggleRegularDiffing;
getSelector: #showingRegularDiffs.
outerButton addMorphBack: (StringMorph contents: 'diffs') lock.
outerButton setBalloonText: 'If checked, then code differences from the previous version, if any, will be shown.'.
^ outerButton
!
----- Method: CodeHolder>>releaseCachedState (in category 'misc') -----
releaseCachedState
"Can always be found again. Don't write on a file."
currentCompiledMethod := nil.!
----- Method: CodeHolder>>removeClass (in category 'commands') -----
removeClass
"Remove the selected class from the system, at interactive user request. Make certain the user really wants to do this, since it is not reversible. Answer true if removal actually happened."
| message className classToRemove result |
self okToChange ifFalse: [^ false].
classToRemove := self selectedClassOrMetaClass ifNil: [Beeper beep. ^ false].
classToRemove := classToRemove theNonMetaClass.
className := classToRemove name.
message := 'Are you certain that you
want to REMOVE the class ', className, '
from the system?'.
(result := self confirm: message)
ifTrue:
[classToRemove subclasses size > 0
ifTrue: [(self confirm: 'class has subclasses: ' , message)
ifFalse: [^ false]].
classToRemove removeFromSystem.
self changed: #classList.
true].
^ result!
----- Method: CodeHolder>>restoreTextualCodingPane (in category 'diffs') -----
restoreTextualCodingPane
"If the receiver is showing tiles, restore the textual coding pane"
self showingTiles ifTrue:
[contentsSymbol := #source.
self installTextualCodingPane]!
----- Method: CodeHolder>>sampleInstanceOfSelectedClass (in category 'misc') -----
sampleInstanceOfSelectedClass
| aClass |
"Return a sample instance of the class currently being pointed at"
(aClass := self selectedClassOrMetaClass) ifNil: [^ nil].
^ aClass theNonMetaClass initializedInstance!
----- Method: CodeHolder>>searchPane (in category 'categories & search pane') -----
searchPane
"Answer the search pane associated with the receiver in its window, or nil if none. Morphic only"
^ self textPaneWithSelector: #lastSearchString!
----- Method: CodeHolder>>selectedBytecodes (in category 'message list') -----
selectedBytecodes
"Answer text to show in a code pane when in showing-byte-codes mode"
^ (self selectedClassOrMetaClass compiledMethodAt: self selectedMessageName ifAbsent: [^ '' asText]) symbolic asText!
----- Method: CodeHolder>>selectedMessage (in category 'message list') -----
selectedMessage
"Answer a copy of the source code for the selected message. This generic version is probably actually never reached, since every subclass probably reimplements and does not send to super. In time, ideally, most, or all, reimplementors would vanish and all would defer instead to a universal version right here. Everything in good time."
| class selector method |
contents ifNotNil: [^ contents copy].
self showingDecompile ifTrue:[^ self decompiledSourceIntoContents].
class := self selectedClassOrMetaClass.
(class isNil or: [(selector := self selectedMessageName) isNil]) ifTrue: [^ ''].
method := class compiledMethodAt: selector ifAbsent: [^ '']. "method deleted while in another project"
currentCompiledMethod := method.
^ contents := (self showComment
ifFalse: [self sourceStringPrettifiedAndDiffed]
ifTrue: [ self commentContents])
copy asText makeSelectorBoldIn: class!
----- Method: CodeHolder>>selectedMessageCategoryName (in category 'categories') -----
selectedMessageCategoryName
"Answer the name of the message category of the message of the currently selected context."
^ self selectedClass organization categoryOfElement: self selectedMessageName!
----- Method: CodeHolder>>sendQuery:to: (in category 'misc') -----
sendQuery: querySelector to: queryPerformer
"Apply a query to the primary selector associated with the current context. If no such selection exists, obtain one by user type-in. Then send querySelector to queryPerformer with the selector as its argument."
| aSelector aString |
aSelector := self selectedMessageName ifNil:
[aString :=UIManager default request: 'Type selector:' initialAnswer: 'flag:'.
^ aString isEmptyOrNil ifFalse:
[(Symbol hasInterned: aString ifTrue:
[:aSymbol | queryPerformer perform: querySelector with: aSymbol])
ifFalse:
[self inform: 'no such selector']]].
queryPerformer perform: querySelector with: aSelector!
----- Method: CodeHolder>>setClassAndSelectorIn: (in category 'misc') -----
setClassAndSelectorIn: csBlock
"Evaluate csBlock with my selected class and and selector as its arguments; provide nil arguments if I don't have a method currently selected"
| aName |
(aName := self selectedMessageName)
ifNil:
[csBlock value: nil value: nil]
ifNotNil:
[csBlock value: self selectedClassOrMetaClass value: aName]
!
----- Method: CodeHolder>>setContentsToForceRefetch (in category 'what to show') -----
setContentsToForceRefetch
"Set the receiver's contents such that on the next update the contents will be formulated afresh. This is a critical and obscure difference between Browsers on the one hand and MessageSets on the other, and has over the years been the source of much confusion and much difficulty. By centralizing the different handling here, we don't need so many idiosyncratic overrides in MessageSet any more"
contents := nil!
----- Method: CodeHolder>>shiftedYellowButtonActivity (in category 'commands') -----
shiftedYellowButtonActivity
"Offer the shifted selector-list menu"
^ self offerMenuFrom: #messageListMenu:shifted: shifted: true!
----- Method: CodeHolder>>showByteCodes: (in category 'what to show') -----
showByteCodes: aBoolean
"Get into or out of bytecode-showoing mode"
self okToChange ifFalse: [^ self changed: #flash].
aBoolean
ifTrue:
[contentsSymbol := #byteCodes]
ifFalse:
[contentsSymbol == #byteCodes ifTrue: [contentsSymbol := #source]].
self contentsChanged!
----- Method: CodeHolder>>showComment (in category 'what to show') -----
showComment
"Answer whether the receiver should show documentation rather than, say, source code"
^ self contentsSymbol == #documentation
!
----- Method: CodeHolder>>showDecompile: (in category 'what to show') -----
showDecompile: aBoolean
"Set the decompile toggle as indicated"
self contentsSymbol: (aBoolean ifFalse: [#source] ifTrue: [#decompile])!
----- Method: CodeHolder>>showDiffs (in category 'diffs') -----
showDiffs
"Answer whether the receiver is showing diffs of source code. The preferred protocol here is #showingRegularDiffs, but this message is still sent by some preexisting buttons so is retained."
^ contentsSymbol == #showDiffs
!
----- Method: CodeHolder>>showDiffs: (in category 'diffs') -----
showDiffs: aBoolean
"Set whether I'm showing diffs as indicated; use the global preference to determine which kind of diffs to institute."
self showingAnyKindOfDiffs
ifFalse:
[aBoolean ifTrue:
[contentsSymbol := self defaultDiffsSymbol]]
ifTrue:
[aBoolean ifFalse:
[contentsSymbol := #source]].
self setContentsToForceRefetch.
self contentsChanged!
----- Method: CodeHolder>>showDocumentation: (in category 'what to show') -----
showDocumentation: aBoolean
"Set the showDocumentation toggle as indicated"
self contentsSymbol: (aBoolean ifFalse: [#source] ifTrue: [#documentation])!
----- Method: CodeHolder>>showPrettyDiffs: (in category 'diffs') -----
showPrettyDiffs: aBoolean
"Set whether I'm showing pretty diffs as indicated"
self showingPrettyDiffs
ifFalse:
[aBoolean ifTrue:
[contentsSymbol := #prettyDiffs]]
ifTrue:
[aBoolean ifFalse:
[contentsSymbol := #source]].
self setContentsToForceRefetch.
self contentsChanged!
----- Method: CodeHolder>>showRegularDiffs: (in category 'diffs') -----
showRegularDiffs: aBoolean
"Set whether I'm showing regular diffs as indicated"
self showingRegularDiffs
ifFalse:
[aBoolean ifTrue:
[contentsSymbol := #showDiffs]]
ifTrue:
[aBoolean ifFalse:
[contentsSymbol := #source]].
self setContentsToForceRefetch.
self contentsChanged!
----- Method: CodeHolder>>showUnreferencedClassVars (in category 'traits') -----
showUnreferencedClassVars
"Search for all class variables known to the selected class, and put up a
list of those that have no references anywhere in the system. The
search includes superclasses, so that you don't need to navigate your
way to the class that defines each class variable in order to determine
whether it is unreferenced"
| cls aList aReport |
((cls := self selectedClass) isNil or: [cls isTrait]) ifTrue: [^ self].
aList := self systemNavigation allUnreferencedClassVariablesOf: cls.
aList size == 0
ifTrue: [^ self inform: 'There are no unreferenced
class variables in
' , cls name].
aReport := String
streamContents: [:aStream |
aStream nextPutAll: 'Unreferenced class variable(s) in ' , cls name;
cr.
aList
do: [:el | aStream tab; nextPutAll: el; cr]].
Transcript cr; show: aReport.
UIManager default chooseFrom: aList values: aList
title: 'Unreferenced
class variables in
' , cls name!
----- Method: CodeHolder>>showUnreferencedInstVars (in category 'traits') -----
showUnreferencedInstVars
"Search for all instance variables known to the selected class, and put up a list of those that have no references anywhere in the system. The search includes superclasses, so that you don't need to navigate your way to the class that defines each inst variable in order to determine whether it is unreferenced"
| cls aList aReport |
((cls := self selectedClassOrMetaClass) isNil or: [cls isTrait]) ifTrue: [^ self].
aList := cls allUnreferencedInstanceVariables.
aList size == 0 ifTrue: [^ self inform: 'There are no unreferenced
instance variables in
', cls name].
aReport := String streamContents:
[:aStream |
aStream nextPutAll: 'Unreferenced instance variable(s) in ', cls name; cr.
aList do: [:el | aStream tab; nextPutAll: el; cr]].
Transcript cr; show: aReport.
UIManager default chooseFrom: aList values: aList title: 'Unreferenced
instance variables in
', cls name!
----- Method: CodeHolder>>showingAnyKindOfDiffs (in category 'diffs') -----
showingAnyKindOfDiffs
"Answer whether the receiver is currently set to show any kind of diffs"
^ #(showDiffs prettyDiffs) includes: contentsSymbol!
----- Method: CodeHolder>>showingByteCodes (in category 'what to show') -----
showingByteCodes
"Answer whether the receiver is showing bytecodes"
^ contentsSymbol == #byteCodes!
----- Method: CodeHolder>>showingByteCodesString (in category 'what to show') -----
showingByteCodesString
"Answer whether the receiver is showing bytecodes"
^ (self showingByteCodes
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'byteCodes'!
----- Method: CodeHolder>>showingDecompile (in category 'what to show') -----
showingDecompile
"Answer whether the receiver should show decompile rather than, say, source code"
^ self contentsSymbol == #decompile
!
----- Method: CodeHolder>>showingDecompileString (in category 'what to show') -----
showingDecompileString
"Answer a string characerizing whether decompilation is showing"
^ (self showingDecompile
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'decompile'!
----- Method: CodeHolder>>showingDiffsString (in category 'diffs') -----
showingDiffsString
"Answer a string representing whether I'm showing diffs. Not sent any more but retained so that prexisting buttons that sent this will not raise errors."
^ (self showingRegularDiffs
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'showDiffs'!
----- Method: CodeHolder>>showingDocumentation (in category 'what to show') -----
showingDocumentation
"Answer whether the receiver should show documentation rather than, say, source code"
^ self contentsSymbol == #documentation
!
----- Method: CodeHolder>>showingDocumentationString (in category 'what to show') -----
showingDocumentationString
"Answer a string characerizing whether documentation is showing"
^ (self showingDocumentation
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'documentation'!
----- Method: CodeHolder>>showingPlainSource (in category 'what to show') -----
showingPlainSource
"Answer whether the receiver is showing plain source"
^ contentsSymbol == #source!
----- Method: CodeHolder>>showingPlainSourceString (in category 'what to show') -----
showingPlainSourceString
"Answer a string telling whether the receiver is showing plain source"
^ (self showingPlainSource
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'source'!
----- Method: CodeHolder>>showingPrettyDiffs (in category 'diffs') -----
showingPrettyDiffs
"Answer whether the receiver is showing pretty diffs of source code"
^ contentsSymbol == #prettyDiffs
!
----- Method: CodeHolder>>showingPrettyDiffsString (in category 'diffs') -----
showingPrettyDiffsString
"Answer a string representing whether I'm showing pretty diffs"
^ (self showingPrettyDiffs
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'prettyDiffs'!
----- Method: CodeHolder>>showingPrettyPrint (in category 'what to show') -----
showingPrettyPrint
"Answer whether the receiver is showing pretty-print"
^ contentsSymbol == #prettyPrint!
----- Method: CodeHolder>>showingRegularDiffs (in category 'diffs') -----
showingRegularDiffs
"Answer whether the receiver is showing regular diffs of source code"
^ contentsSymbol == #showDiffs
!
----- Method: CodeHolder>>showingRegularDiffsString (in category 'diffs') -----
showingRegularDiffsString
"Answer a string representing whether I'm showing regular diffs"
^ (self showingRegularDiffs
ifTrue:
['<yes>']
ifFalse:
['<no>']), 'showDiffs'!
----- Method: CodeHolder>>showingSource (in category 'what to show') -----
showingSource
"Answer whether the receiver is currently showing source code"
^ self contentsSymbol == #source
!
----- Method: CodeHolder>>sourceAndDiffsQuintsOnly (in category 'controls') -----
sourceAndDiffsQuintsOnly
"Answer a list of quintuplets representing information on the alternative views available in the code pane for the case where the only plausible choices are showing source or either of the two kinds of diffs"
^ #(
(source togglePlainSource showingPlainSourceString 'source' 'the textual source code as writen')
(showDiffs toggleRegularDiffing showingRegularDiffsString 'showDiffs' 'the textual source diffed from its prior version')
(prettyDiffs togglePrettyDiffing showingPrettyDiffsString 'prettyDiffs' 'formatted textual source diffed from formatted form of prior version'))!
----- Method: CodeHolder>>sourceStringPrettifiedAndDiffed (in category 'message list') -----
sourceStringPrettifiedAndDiffed
"Answer a copy of the source code for the selected message, transformed by diffing and pretty-printing exigencies"
| class selector sourceString |
class := self selectedClassOrMetaClass.
selector := self selectedMessageName.
(class isNil or: [selector isNil]) ifTrue: [^'missing'].
sourceString := class ultimateSourceCodeAt: selector ifAbsent: [^'error'].
self validateMessageSource: sourceString forSelector: selector.
(#(#prettyPrint #prettyDiffs)
includes: contentsSymbol)
ifTrue:
[sourceString := class prettyPrinterClass
format: sourceString
in: class
notifying: nil].
self showingAnyKindOfDiffs
ifTrue: [sourceString := self diffFromPriorSourceFor: sourceString].
^sourceString!
----- Method: CodeHolder>>spawn: (in category 'commands') -----
spawn: aString
"Create and schedule a spawned message category browser for the currently selected message category. The initial text view contains the characters in aString. In the spawned browser, preselect the current selector (if any) as the going-in assumption, though upon acceptance this will often change"
| newBrowser aCategory aClass |
(aClass := self selectedClassOrMetaClass) isNil ifTrue:
[^ aString isEmptyOrNil ifFalse: [(Workspace new contents: aString) openLabel: 'spawned workspace']].
(aCategory := self categoryOfCurrentMethod)
ifNil:
[self buildClassBrowserEditString: aString]
ifNotNil:
[newBrowser := Browser new setClass: aClass selector: self selectedMessageName.
self suggestCategoryToSpawnedBrowser: newBrowser.
Browser openBrowserView: (newBrowser openMessageCatEditString: aString)
label: 'category "', aCategory, '" in ',
newBrowser selectedClassOrMetaClassName]!
----- Method: CodeHolder>>spawnFullProtocol (in category 'commands') -----
spawnFullProtocol
"Create and schedule a new protocol browser on the currently selected class or meta."
| aClassOrMetaclass |
(aClassOrMetaclass := self selectedClassOrMetaClass) ifNotNil:
[ProtocolBrowser openFullProtocolForClass: aClassOrMetaclass]!
----- Method: CodeHolder>>spawnHierarchy (in category 'traits') -----
spawnHierarchy
"Create and schedule a new hierarchy browser on the currently selected class or meta."
| newBrowser aSymbol aBehavior messageCatIndex selectedClassOrMetaClass |
(selectedClassOrMetaClass := self selectedClassOrMetaClass)
ifNil: [^ self].
selectedClassOrMetaClass isTrait ifTrue: [^self].
newBrowser := HierarchyBrowser new initHierarchyForClass: selectedClassOrMetaClass.
((aSymbol := self selectedMessageName) notNil and: [(MessageSet isPseudoSelector: aSymbol) not])
ifTrue:
[aBehavior := selectedClassOrMetaClass.
messageCatIndex := aBehavior organization numberOfCategoryOfElement: aSymbol.
newBrowser messageCategoryListIndex: messageCatIndex + 1.
newBrowser messageListIndex:
((aBehavior organization listAtCategoryNumber: messageCatIndex) indexOf: aSymbol)].
Browser
openBrowserView: (newBrowser openSystemCatEditString: nil)
label: newBrowser labelString.
Smalltalk isMorphic
ifTrue: ["this workaround only needed in morphic"
newBrowser assureSelectionsShow]!
----- Method: CodeHolder>>spawnProtocol (in category 'commands') -----
spawnProtocol
| aClassOrMetaclass |
"Create and schedule a new protocol browser on the currently selected class or meta."
(aClassOrMetaclass := self selectedClassOrMetaClass) ifNotNil:
[ProtocolBrowser openSubProtocolForClass: aClassOrMetaclass]!
----- Method: CodeHolder>>spawnToClass: (in category 'commands') -----
spawnToClass: aClass
"Used to copy down code from a superclass to a subclass in one easy step, if you know what you're doing. Spawns a new message-category browser for the indicated class, populating it with the source code seen in the current tool."
| aCategory newBrowser org |
(aCategory := self categoryOfCurrentMethod)
ifNil:
[self buildClassBrowserEditString: self contents]
ifNotNil:
[((org := aClass organization) categories includes: aCategory)
ifFalse: [org addCategory: aCategory].
newBrowser := Browser new setClass: aClass selector: nil.
newBrowser selectMessageCategoryNamed: aCategory.
Browser openBrowserView: (newBrowser openMessageCatEditString: self contents)
label: 'category "', aCategory, '" in ',
newBrowser selectedClassOrMetaClassName]!
----- Method: CodeHolder>>spawnToCollidingClass: (in category 'commands') -----
spawnToCollidingClass: aClass
"Potentially used to copy down code from a superclass to a subclass in one easy step, in the case where the given class already has its own version of code, which would consequently be clobbered if the spawned code were accepted."
self inform: 'That would be destructive of
some pre-existing code already in that
class for this selector. For the moment,
we will not let you do this to yourself.'!
----- Method: CodeHolder>>stepIn: (in category 'self-updating') -----
stepIn: aSystemWindow
self updateListsAndCodeIn: aSystemWindow!
----- Method: CodeHolder>>suggestCategoryToSpawnedBrowser: (in category 'misc') -----
suggestCategoryToSpawnedBrowser: aBrowser
"aBrowser is a message-category browser being spawned from the receiver. Tell it what it needs to know to get its category info properly set up."
aBrowser setOriginalCategoryIndexForCurrentMethod!
----- Method: CodeHolder>>textPaneWithSelector: (in category 'categories & search pane') -----
textPaneWithSelector: aSelector
"If, among my window's paneMorphs, there is a text pane defined with aSelector as its retriever, answer it, else answer nil"
| aWindow |
^ (aWindow := self containingWindow) ifNotNil:
[aWindow paneMorphSatisfying:
[:aMorph | (aMorph isKindOf: PluggableTextMorph) and:
[aMorph getTextSelector == aSelector]]]!
----- Method: CodeHolder>>toggleDecompile (in category 'what to show') -----
toggleDecompile
"Toggle the setting of the showingDecompile flag, unless there are unsubmitted edits that the user declines to discard"
| wasShowing |
self okToChange ifTrue:
[wasShowing := self showingDecompile.
self restoreTextualCodingPane.
self showDecompile: wasShowing not.
self setContentsToForceRefetch.
self contentsChanged]
!
----- Method: CodeHolder>>toggleDiff (in category 'diffs') -----
toggleDiff
"Retained for backward compatibility with existing buttons in existing images"
self toggleDiffing!
----- Method: CodeHolder>>toggleDiffing (in category 'diffs') -----
toggleDiffing
"Toggle whether diffs should be shown in the code pane. If any kind of diffs were being shown, stop showing diffs. If no kind of diffs were being shown, start showing whatever kind of diffs are called for by default."
| wasShowingDiffs |
self okToChange ifTrue:
[wasShowingDiffs := self showingAnyKindOfDiffs.
self restoreTextualCodingPane.
self showDiffs: wasShowingDiffs not.
self setContentsToForceRefetch.
self contentsChanged]
!
----- Method: CodeHolder>>togglePlainSource (in category 'diffs') -----
togglePlainSource
"Toggle whether plain source shown in the code pane"
| wasShowingPlainSource |
self okToChange ifTrue:
[wasShowingPlainSource := self showingPlainSource.
self restoreTextualCodingPane.
wasShowingPlainSource
ifTrue:
[self showDocumentation: true]
ifFalse:
[contentsSymbol := #source].
self setContentsToForceRefetch.
self changed: #contents]
!
----- Method: CodeHolder>>togglePrettyDiffing (in category 'diffs') -----
togglePrettyDiffing
"Toggle whether pretty-diffing should be shown in the code pane"
| wasShowingDiffs |
self okToChange ifTrue:
[wasShowingDiffs := self showingPrettyDiffs.
self restoreTextualCodingPane.
self showPrettyDiffs: wasShowingDiffs not.
self setContentsToForceRefetch.
self contentsChanged]
!
----- Method: CodeHolder>>togglePrettyPrint (in category 'diffs') -----
togglePrettyPrint
"Toggle whether pretty-print is in effectin the code pane"
self restoreTextualCodingPane.
self okToChange ifTrue:
[self showingPrettyPrint
ifTrue:
[contentsSymbol := #source]
ifFalse:
[contentsSymbol := #prettyPrint].
self setContentsToForceRefetch.
self contentsChanged]
!
----- Method: CodeHolder>>toggleRegularDiffing (in category 'diffs') -----
toggleRegularDiffing
"Toggle whether regular-diffing should be shown in the code pane"
| wasShowingDiffs |
self okToChange ifTrue:
[wasShowingDiffs := self showingRegularDiffs.
self restoreTextualCodingPane.
self showRegularDiffs: wasShowingDiffs not.
self setContentsToForceRefetch.
self contentsChanged]
!
----- Method: CodeHolder>>toggleShowDocumentation (in category 'what to show') -----
toggleShowDocumentation
"Toggle the setting of the showingDocumentation flag, unless there are unsubmitted edits that the user declines to discard"
| wasShowing |
self okToChange ifTrue:
[wasShowing := self showingDocumentation.
self restoreTextualCodingPane.
self showDocumentation: wasShowing not.
self setContentsToForceRefetch.
self contentsChanged]
!
----- Method: CodeHolder>>toggleShowingByteCodes (in category 'what to show') -----
toggleShowingByteCodes
"Toggle whether the receiver is showing bytecodes"
self restoreTextualCodingPane.
self showByteCodes: self showingByteCodes not.
self setContentsToForceRefetch.
self contentsChanged!
----- Method: CodeHolder>>unshiftedYellowButtonActivity (in category 'commands') -----
unshiftedYellowButtonActivity
"Offer the unshifted shifted selector-list menu"
^ self offerMenuFrom: #messageListMenu:shifted: shifted: false!
----- Method: CodeHolder>>updateCodePaneIfNeeded (in category 'self-updating') -----
updateCodePaneIfNeeded
"If the code for the currently selected method has changed underneath me, then update the contents of my code pane unless it holds unaccepted edits"
self didCodeChangeElsewhere
ifTrue:
[self hasUnacceptedEdits
ifFalse:
[self setContentsToForceRefetch.
self contentsChanged]
ifTrue:
[self changed: #codeChangedElsewhere]]!
----- Method: CodeHolder>>updateListsAndCodeIn: (in category 'self-updating') -----
updateListsAndCodeIn: aWindow
super updateListsAndCodeIn: aWindow.
self updateCodePaneIfNeeded!
----- Method: CodeHolder>>useSelector:orGetSelectorAndSendQuery:to: (in category 'misc') -----
useSelector: incomingSelector orGetSelectorAndSendQuery: querySelector to: queryPerformer
"If incomingSelector is not nil, use it, else obtain a selector from user type-in. Using the determined selector, send the query to the performer provided."
| aSelector |
incomingSelector
ifNotNil:
[queryPerformer perform: querySelector with: incomingSelector]
ifNil:
[aSelector :=UIManager default request: 'Type selector:' initialAnswer: 'flag:'.
aSelector isEmptyOrNil ifFalse:
[(Symbol hasInterned: aSelector ifTrue:
[:aSymbol | queryPerformer perform: querySelector with: aSymbol])
ifFalse:
[self inform: 'no such selector']]]!
----- Method: CodeHolder>>validateMessageSource:forSelector: (in category 'message list') -----
validateMessageSource: sourceString forSelector: aSelector
"Check whether there is evidence that method source is invalid"
| sourcesName |
(self selectedClass compilerClass == Object compilerClass
and: [(sourceString asString findString: aSelector keywords first ) ~= 1])
ifTrue: [sourcesName := FileDirectory localNameFor: SmalltalkImage current sourcesName.
self inform: 'There may be a problem with your sources file!!
The source code for every method should (usually) start with the
method selector but this is not the case with this method!! You may
proceed with caution but it is recommended that you get a new source file.
This can happen if you download the "' , sourcesName , '" file,
or the ".changes" file you use, as TEXT. It must be transfered
in BINARY mode, even if it looks like a text file,
to preserve the CR line ends.
Mac users: This may have been caused by Stuffit Expander.
To prevent the files above to be converted to Mac line ends
when they are expanded, do this: Start the program, then
from Preferences... in the File menu, choose the Cross
Platform panel, then select "Never" and press OK.
Then expand the compressed archive again.
(Occasionally, the source code for a method may legitimately
start with a non-alphabetic character -- for example, Behavior
method #formalHeaderPartsFor:. In such rare cases, you can
happily disregard this warning.)'].!
----- Method: CodeHolder>>wantsDiffFeedback (in category 'diffs') -----
wantsDiffFeedback
"Answer whether the receiver is showing diffs of source code"
^ self showingAnyKindOfDiffs!
----- Method: CodeHolder>>wantsStepsIn: (in category 'self-updating') -----
wantsStepsIn: aWindow
^ Preferences smartUpdating!
CodeHolder subclass: #Debugger
instanceVariableNames: 'interruptedProcess interruptedController contextStack contextStackTop contextStackIndex contextStackList receiverInspector contextVariablesInspector externalInterrupt proceedValue selectingPC debuggerMap savedCursor isolationHead failedProject errorWasInUIProcess labelString message'
classVariableNames: 'ContextStackKeystrokes ErrorRecursion'
poolDictionaries: ''
category: 'Tools-Debugger'!
!Debugger commentStamp: '<historical>' prior: 0!
I represent the machine state at the time of an interrupted process. I also represent a query path into the state of the process. The debugger is typically viewed through a window that views the stack of suspended contexts, the code for, and execution point in, the currently selected message, and inspectors on both the receiver of the currently selected message, and the variables in the current context.
Special note on recursive errors:
Some errors affect Squeak's ability to present a debugger. This is normally an unrecoverable situation. However, if such an error occurs in an isolation layer, Squeak will attempt to exit from the isolation layer and then present a debugger. Here is the chain of events in such a recovery.
* A recursive error is detected.
* The current project is queried for an isolationHead
* Changes in the isolationHead are revoked
* The parent project of isolated project is returned to
* The debugger is opened there and execution resumes.
If the user closes that debugger, execution continues in the outer project and layer. If, after repairing some damage, the user proceeds from the debugger, then the isolationHead is re-invoked, the failed project is re-entered, and execution resumes in that world. !
----- Method: Debugger class>>context: (in category 'instance creation') -----
context: aContext
"Answer an instance of me for debugging the active process starting with the given context."
^ self context: aContext isolationHead: nil!
----- Method: Debugger class>>context:isolationHead: (in category 'instance creation') -----
context: aContext isolationHead: isolationHead
"Answer an instance of me for debugging the active process starting with the given context."
^ self new
process: Processor activeProcess
controller:
((Smalltalk isMorphic not and: [ScheduledControllers inActiveControllerProcess])
ifTrue: [ScheduledControllers activeController]
ifFalse: [nil])
context: aContext
isolationHead: isolationHead
!
----- Method: Debugger class>>informExistingDebugger:label: (in category 'instance creation') -----
informExistingDebugger: aContext label: aString
"Walking the context chain, we try to find out if we're in a debugger stepping situation.
If we find the relevant contexts, we must rearrange them so they look just like they would
if the methods were excuted outside of the debugger."
| ctx quickStepMethod oldSender baseContext |
ctx := thisContext.
quickStepMethod := ContextPart compiledMethodAt: #quickSend:to:with:super:.
[ctx sender == nil or: [ctx sender method == quickStepMethod]] whileFalse: [ctx := ctx sender].
ctx sender == nil ifTrue: [^self].
baseContext := ctx.
"baseContext is now the context created by the #quickSend... method."
oldSender := ctx := ctx sender home sender.
"oldSender is the context which originally sent the #quickSend... method"
[ctx == nil or: [ctx receiver isKindOf: self]] whileFalse: [ctx := ctx sender].
ctx == nil ifTrue: [^self].
"ctx is the context of the Debugger method #doStep"
ctx receiver labelString: aString.
ctx receiver externalInterrupt: false; proceedValue: aContext receiver.
baseContext swapSender: baseContext sender sender sender. "remove intervening contexts"
thisContext swapSender: oldSender. "make myself return to debugger"
ErrorRecursion := false.
^aContext!
----- Method: Debugger class>>initialize (in category 'class initialization') -----
initialize
ErrorRecursion := false.
ContextStackKeystrokes := Dictionary new
at: $e put: #send;
at: $t put: #doStep;
at: $T put: #stepIntoBlock;
at: $p put: #proceed;
at: $r put: #restart;
at: $f put: #fullStack;
at: $w put: #where;
yourself.
"Debugger initialize"!
----- Method: Debugger class>>openContext:label:contents: (in category 'class initialization') -----
openContext: aContext label: aString contents: contentsStringOrNil
| isolationHead |
"Open a notifier in response to an error, halt, or notify. A notifier view just shows a short view of the sender stack and provides a menu that lets the user open a full debugger."
<primitive: 19> "Simulation guard"
ErrorRecursion not & Preferences logDebuggerStackToFile ifTrue:
[Smalltalk logError: aString inContext: aContext to: 'SqueakDebug.log'].
ErrorRecursion ifTrue:
[ErrorRecursion := false.
(isolationHead := Project current isolationHead)
ifNil: [self primitiveError: aString]
ifNotNil: [isolationHead revoke]].
ErrorRecursion := true.
self informExistingDebugger: aContext label: aString.
(Debugger context: aContext isolationHead: isolationHead)
openNotifierContents: contentsStringOrNil
label: aString.
ErrorRecursion := false.
Processor activeProcess suspend.
!
----- Method: Debugger class>>openInterrupt:onProcess: (in category 'opening') -----
openInterrupt: aString onProcess: interruptedProcess
"Open a notifier in response to an interrupt. An interrupt occurs when the user types the interrupt key (cmd-. on Macs, ctrl-c or alt-. on other systems) or when the low-space watcher detects that memory is low."
| debugger |
<primitive: 19> "Simulation guard"
debugger := self new.
debugger
process: interruptedProcess
controller: ((Smalltalk isMorphic not
and: [ScheduledControllers activeControllerProcess == interruptedProcess])
ifTrue: [ScheduledControllers activeController])
context: interruptedProcess suspendedContext.
debugger externalInterrupt: true.
Preferences logDebuggerStackToFile ifTrue:
[(aString includesSubString: 'Space') &
(aString includesSubString: 'low') ifTrue: [
Smalltalk logError: aString inContext: debugger interruptedContext to:'LowSpaceDebug.log']].
Preferences eToyFriendly ifTrue: [World stopRunningAll].
^ debugger
openNotifierContents: nil
label: aString
!
----- Method: Debugger class>>openOn:context:label:contents:fullView: (in category 'opening') -----
openOn: process context: context label: title contents: contentsStringOrNil fullView: bool
"Open a notifier in response to an error, halt, or notify. A notifier view just shows a short view of the sender stack and provides a menu that lets the user open a full debugger."
| controller errorWasInUIProcess |
Smalltalk isMorphic
ifTrue: [errorWasInUIProcess := Project spawnNewProcessIfThisIsUI: process]
ifFalse: [controller := ScheduledControllers activeControllerProcess == process
ifTrue: [ScheduledControllers activeController]].
WorldState addDeferredUIMessage: [
[ | debugger |
debugger := self new process: process controller: controller context: context.
Smalltalk isMorphic
ifTrue: ["schedule debugger in deferred UI message to address redraw
problems after opening a debugger e.g. from the testrunner."
"WorldState addDeferredUIMessage: ["bool
ifTrue: [debugger openFullNoSuspendLabel: title]
ifFalse: [debugger openNotifierContents: contentsStringOrNil label: title]]
ifFalse: ["deferred UI message would require special controller in MVC"
bool
ifTrue: [debugger openFullNoSuspendLabel: title]
ifFalse: [debugger openNotifierContents: contentsStringOrNil label: title]].
debugger errorWasInUIProcess: errorWasInUIProcess.
Preferences logDebuggerStackToFile ifTrue: [
Smalltalk logError: title inContext: context to: 'SqueakDebug.log'].
Smalltalk isMorphic
ifFalse: [ScheduledControllers searchForActiveController "needed since openNoTerminate (see debugger #open...) does not set up activeControllerProcess if activeProcess (this fork) is not the current activeControllerProcess (see #scheduled:from:)"].
] on: Error do: [:ex |
self primitiveError:
'Orginal error: ',
title asString, '.
Debugger error: ',
([ex description] on: Error do: ['a ', ex class printString]), ':'
]
].
process suspend.
!
----- Method: Debugger class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Debugger' brightColor: #lightRed pastelColor: #veryPaleRed helpMessage: 'The system debugger.'!
----- Method: Debugger>>abandon (in category 'context stack menu') -----
abandon
"abandon the debugger from its pre-debug notifier"
self abandon: self topView!
----- Method: Debugger>>abandon: (in category 'context stack menu') -----
abandon: aTopView
"abandon the notifier represented by aTopView"
ToolBuilder default close: aTopView!
----- Method: Debugger>>aboutToStyle: (in category 'code pane') -----
aboutToStyle: aStyler
"This is a notification that aStyler is about to re-style its text.
Set the classOrMetaClass in aStyler, so that identifiers
will be resolved correctly.
Answer true to allow styling to proceed, or false to veto the styling"
self isModeStyleable ifFalse: [^false].
aStyler
classOrMetaClass: self selectedClassOrMetaClass;
sourceMap: self debuggerMap.
^true!
----- Method: Debugger>>askForCategoryIn:default: (in category 'context stack menu') -----
askForCategoryIn: aClass default: aString
| categories index category |
categories := OrderedCollection with: 'new ...'.
categories addAll: (aClass allMethodCategoriesIntegratedThrough: Object).
index := UIManager default
chooseFrom: categories
title: 'Please provide a good category for the new method!!' translated.
index = 0 ifTrue: [^ aString].
category := index = 1 ifTrue: [UIManager default request: 'Enter category name:']
ifFalse: [categories at: index].
^ category isEmpty ifTrue: [^ aString] ifFalse: [category]!
----- Method: Debugger>>askForSuperclassOf:toImplement:ifCancel: (in category 'private') -----
askForSuperclassOf: aClass toImplement: aSelector ifCancel: cancelBlock
| classes chosenClassIndex |
classes := aClass withAllSuperclasses.
chosenClassIndex := UIManager default
chooseFrom: (classes collect: [:c | c name])
title: 'Define #', aSelector, ' in which class?'.
chosenClassIndex = 0 ifTrue: [^ cancelBlock value].
^ classes at: chosenClassIndex!
----- Method: Debugger>>browseMessages (in category 'context stack menu') -----
browseMessages
"Present a menu of all messages sent by the currently selected message.
Open a message set browser of all implementors of the message chosen.
Do nothing if no message is chosen."
contextStackIndex = 0 ifTrue: [^ self].
super browseMessages.!
----- Method: Debugger>>browseSendersOfMessages (in category 'context stack menu') -----
browseSendersOfMessages
"Present a menu of the currently selected message, as well as all
messages sent by it. Open a message set browser of all implementors
of the message chosen."
contextStackIndex = 0 ifTrue: [^ self].
super browseSendersOfMessages!
----- Method: Debugger>>browseVersions (in category 'context stack menu') -----
browseVersions
"Create and schedule a message set browser on all versions of the
currently selected message selector."
| class selector |
class := self selectedClassOrMetaClass.
selector := self selectedMessageName.
VersionsBrowser
browseVersionsOf: (class compiledMethodAt: selector)
class: self selectedClass theNonMetaClass
meta: class isMeta
category: self selectedMessageCategoryName
selector: selector!
----- Method: Debugger>>buildFullWith: (in category 'toolbuilder') -----
buildFullWith: builder
| windowSpec listSpec textSpec extent |
windowSpec := builder pluggableWindowSpec new.
windowSpec model: self.
windowSpec label: 'Debugger'.
Display height < 800 "a small screen"
ifTrue:[extent := RealEstateAgent standardWindowExtent]
ifFalse:[extent := 600 at 700].
windowSpec extent: extent.
windowSpec children: OrderedCollection new.
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #contextStackList;
getIndex: #contextStackIndex;
setIndex: #toggleContextStackIndex:;
menu: #contextStackMenu:shifted:;
keyPress: #contextStackKey:from:;
frame: (0 at 0 corner: 1 at 0.22).
windowSpec children add: listSpec.
textSpec := self buildCodePaneWith: builder.
textSpec frame: (0 at 0.22corner: 1 at 0.8).
windowSpec children add: textSpec.
listSpec := builder pluggableListSpec new.
listSpec
model: self receiverInspector;
list: #fieldList;
getIndex: #selectionIndex;
setIndex: #toggleIndex:;
menu: #fieldListMenu:;
keyPress: #inspectorKey:from:;
frame: (0 at 0.8 corner: 0.2 at 1).
windowSpec children add: listSpec.
textSpec := builder pluggableTextSpec new.
textSpec
model: self receiverInspector;
getText: #contents;
setText: #accept:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:;
frame: (0.2 at 0.8 corner: 0.5 at 1).
windowSpec children add: textSpec.
listSpec := builder pluggableListSpec new.
listSpec
model: self contextVariablesInspector;
list: #fieldList;
getIndex: #selectionIndex;
setIndex: #toggleIndex:;
menu: #fieldListMenu:;
keyPress: #inspectorKey:from:;
frame: (0.5 at 0.8 corner: 0.7 at 1).
windowSpec children add: listSpec.
textSpec := builder pluggableTextSpec new.
textSpec
model: self contextVariablesInspector;
getText: #contents;
setText: #accept:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:;
frame: (0.7 at 0.8 corner: 1 at 1).
windowSpec children add: textSpec.
^builder build: windowSpec!
----- Method: Debugger>>buildNotifierWith:label:message: (in category 'toolbuilder') -----
buildNotifierWith: builder label: label message: messageString
| windowSpec listSpec textSpec panelSpec buttonSpec quads |
windowSpec := builder pluggableWindowSpec new.
windowSpec model: self.
windowSpec extent: 450 @ 156. "nice and wide to show plenty of the error msg"
windowSpec label: label.
windowSpec children: OrderedCollection new.
panelSpec := builder pluggablePanelSpec new.
panelSpec children: OrderedCollection new.
quads := self preDebugButtonQuads.
(self interruptedContext selector == #doesNotUnderstand:) ifTrue: [
quads := quads copyWith:
{ 'Create'. #createMethod. #magenta. 'create the missing method' }
].
quads do:[:spec|
buttonSpec := builder pluggableButtonSpec new.
buttonSpec model: self.
buttonSpec label: spec first.
buttonSpec action: spec second.
buttonSpec help: spec fourth.
panelSpec children add: buttonSpec.
].
panelSpec layout: #horizontal. "buttons"
panelSpec frame: (0 at 0 corner: 1 at 0.2).
windowSpec children add: panelSpec.
Preferences eToyFriendly | messageString notNil ifFalse:[
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #contextStackList;
getIndex: #contextStackIndex;
setIndex: #debugAt:;
frame: (0 at 0.2 corner: 1 at 1).
windowSpec children add: listSpec.
] ifTrue:[
message := messageString.
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #preDebugMessageString;
setText: nil;
selection: nil;
menu: #debugProceedMenu:;
frame: (0 at 0.2corner: 1 at 1).
windowSpec children add: textSpec.
].
^windowSpec!
----- Method: Debugger>>buildWith: (in category 'toolbuilder') -----
buildWith: aBuilder
^self buildFullWith: aBuilder!
----- Method: Debugger>>checkContextSelection (in category 'private') -----
checkContextSelection
contextStackIndex = 0 ifTrue: [self contextStackIndex: 1 oldContextWas: nil].
!
----- Method: Debugger>>close: (in category 'context stack menu') -----
close: aScheduledController
"The argument is a controller on a view of the receiver.
That view is closed."
aScheduledController close
!
----- Method: Debugger>>codePaneMenu:shifted: (in category 'code pane menu') -----
codePaneMenu: aMenu shifted: shifted
aMenu add: 'run to here' target: self selector: #runToSelection: argument: thisContext sender receiver selectionInterval.
aMenu addLine.
super codePaneMenu: aMenu shifted: shifted.
^aMenu.!
----- Method: Debugger>>contents (in category 'accessing') -----
contents
"Depending on the current selection, different information is retrieved.
Answer a string description of that information. This information is the
method in the currently selected context."
contents == nil ifTrue: [^ String new].
^ contents copy!
----- Method: Debugger>>contents:notifying: (in category 'accessing') -----
contents: aText notifying: aController
"The retrieved information has changed and its source must now be updated.
In this case, the retrieved information is the method of the selected context."
| result selector classOfMethod category h ctxt newMethod |
contextStackIndex = 0 ifTrue:
[^false].
self selectedContext isExecutingBlock ifTrue:
[h := self selectedContext activeHome.
h ifNil:
[self inform: 'Method for block not found on stack, can''t edit and continue'.
^false].
(self confirm: 'I will have to revert to the method from\which this block originated. Is that OK?' withCRs) ifFalse:
[^false].
self resetContext: h.
result := self contents: aText notifying: aController.
self contentsChanged.
^result].
classOfMethod := self selectedClass.
category := self selectedMessageCategoryName.
selector := self selectedClass parserClass new parseSelector: aText.
(selector == self selectedMessageName
or: [(self selectedMessageName beginsWith: 'DoIt')
and: [selector numArgs = self selectedMessageName numArgs]]) ifFalse:
[self inform: 'can''t change selector'.
^false].
selector := classOfMethod
compile: aText
classified: category
notifying: aController.
selector ifNil: [^false]. "compile cancelled"
contents := aText.
newMethod := classOfMethod compiledMethodAt: selector.
newMethod isQuick ifTrue:
[self down.
self selectedContext jump: (self selectedContext previousPc - self selectedContext pc)].
ctxt := interruptedProcess popTo: self selectedContext.
ctxt == self selectedContext
ifFalse:
[self inform: 'Method saved, but current context unchanged\because of unwind error. Click OK to see error' withCRs]
ifTrue:
[newMethod isQuick ifFalse:
[interruptedProcess
restartTopWith: newMethod;
stepToSendOrReturn].
contextVariablesInspector object: nil].
self resetContext: ctxt.
Smalltalk isMorphic ifTrue:
[World
addAlarm: #changed:
withArguments: #(contentsSelection)
for: self
at: (Time millisecondClockValue + 200)].
^true!
----- Method: Debugger>>contentsSelection (in category 'code pane') -----
contentsSelection
^ self pcRange!
----- Method: Debugger>>contextStackIndex (in category 'context stack (message list)') -----
contextStackIndex
"Answer the index of the selected context."
^contextStackIndex!
----- Method: Debugger>>contextStackIndex:oldContextWas: (in category 'private') -----
contextStackIndex: anInteger oldContextWas: oldContext
"Change the context stack index to anInteger, perhaps in response to user selection."
| isNewMethod selectedContextSlotName index |
contextStackIndex := anInteger.
anInteger = 0 ifTrue:
[currentCompiledMethod := contents := nil.
self changed: #contextStackIndex.
self decorateButtons.
self contentsChanged.
contextVariablesInspector object: nil.
receiverInspector object: self receiver.
^self].
selectedContextSlotName := contextVariablesInspector selectedSlotName.
isNewMethod := oldContext == nil
or: [oldContext method ~~ (currentCompiledMethod := self selectedContext method)].
isNewMethod ifTrue:
[contents := self selectedMessage.
self contentsChanged.
self pcRange].
self changed: #contextStackIndex.
self decorateButtons.
contextVariablesInspector object: self selectedContext.
((index := contextVariablesInspector fieldList indexOf: selectedContextSlotName) ~= 0
and: [index ~= contextVariablesInspector selectionIndex]) ifTrue:
[contextVariablesInspector toggleIndex: index].
receiverInspector object: self receiver.
isNewMethod ifFalse:
[self changed: #contentsSelection]!
----- Method: Debugger>>contextStackKey:from: (in category 'context stack menu') -----
contextStackKey: aChar from: view
"Respond to a keystroke in the context list"
| selector |
selector := ContextStackKeystrokes at: aChar ifAbsent: [nil].
selector ifNil: [self messageListKey: aChar from: view]
ifNotNil: [self perform: selector]!
----- Method: Debugger>>contextStackList (in category 'context stack (message list)') -----
contextStackList
"Answer the array of contexts."
^contextStackList!
----- Method: Debugger>>contextStackMenu:shifted: (in category 'context stack menu') -----
contextStackMenu: aMenu shifted: shifted
"Set up the menu appropriately for the context-stack-list, either shifted or unshifted as per the parameter provided"
^ shifted ifFalse:[
aMenu addList: {
{'fullStack (f)'. #fullStack}.
{'restart (r)'. #restart}.
{'proceed (p)'. #proceed}.
{'step (t)'. #doStep}.
{'step through (T)'. #stepIntoBlock}.
{'send (e)'. #send}.
{'where (w)'. #where}.
{'peel to first like this'. #peelToFirst}.
#-.
{'return entered value'. #returnValue}.
#-.
{'toggle break on entry'. #toggleBreakOnEntry}.
{'senders of... (n)'. #browseSendersOfMessages}.
{'implementors of... (m)'. #browseMessages}.
{'inheritance (i)'. #methodHierarchy}.
#-.
{'versions (v)'. #browseVersions}.
{'inst var refs...'. #browseInstVarRefs}.
#-.
{'inst var defs...'. #browseInstVarDefs}.
{'class var refs...'. #browseClassVarRefs}.
{'class variables'. #browseClassVariables}.
#-.
{'class refs (N)'. #browseClassRefs}.
{'browse full (b)'. #browseMethodFull}.
{'file out '. #fileOutMessage}.
#-.
{'mail out bug report'. #mailOutBugReport}.
{'more...'. #shiftedYellowButtonActivity}.
}.
] ifTrue: [
aMenu addList: {
{'browse class hierarchy'. #classHierarchy}.
{'browse class'. #browseClass}.
{'browse method (O)'. #openSingleMessageBrowser}.
{'implementors of sent messages'. #browseAllMessages}.
{'change sets with this method'. #findMethodInChangeSets}.
#-.
{'inspect instances'. #inspectInstances}.
{'inspect subinstances'. #inspectSubInstances}.
#-.
{'revert to previous version'. #revertToPreviousVersion}.
{'remove from current change set'. #removeFromCurrentChanges}.
{'revert & remove from changes'. #revertAndForget}.
#-.
{'more...'. #unshiftedYellowButtonActivity}.
}
].!
----- Method: Debugger>>contextVariablesInspector (in category 'accessing') -----
contextVariablesInspector
"Answer the instance of Inspector that is providing a view of the
variables of the selected context."
^contextVariablesInspector!
----- Method: Debugger>>createMethod (in category 'private') -----
createMethod
"Should only be called when this Debugger was created in response to a
MessageNotUnderstood exception. Create a stub for the method that was
missing and proceed into it."
| msg chosenClass |
msg := contextStackTop tempAt: 1.
chosenClass := self
askForSuperclassOf: contextStackTop receiver class
toImplement: msg selector
ifCancel: [^self].
self implement: msg inClass: chosenClass.
!
----- Method: Debugger>>customButtonSpecs (in category 'initialize') -----
customButtonSpecs
"Answer an array of elements of the form wording, selector, help-message, that characterize the custom button row of a debugger."
| list |
list := #(('Proceed' proceed 'close the debugger and proceed.')
('Restart' restart 'reset this context to its start.')
('Into' send 'step Into message sends')
('Over' doStep 'step Over message sends')
('Through' stepIntoBlock 'step into a block')
('Full Stack' fullStack 'show full stack')
('Where' where 'select current pc range')
('Tally' tally 'time in milliseconds to execute')).
Preferences restartAlsoProceeds ifTrue:
[list := list collect: [:each |
each second == #restart
ifTrue: [each copy at: 3 put: 'proceed from the beginning of this context.'; yourself]
ifFalse: [each]]].
^ list!
----- Method: Debugger>>debug (in category 'notifier menu') -----
debug
"Open a full DebuggerView."
| topView |
topView := self topView.
topView model: nil. "so close won't release me."
self breakDependents.
ToolBuilder default close: topView.
^ self openFullNoSuspendLabel: topView label!
----- Method: Debugger>>debugAt: (in category 'initialize') -----
debugAt: anInteger
self toggleContextStackIndex: anInteger.
^ self debug.!
----- Method: Debugger>>debugProceedMenu: (in category 'context stack menu') -----
debugProceedMenu: aMenu
^ aMenu labels:
'proceed
debug'
lines: #()
selections: #(proceed debug )
!
----- Method: Debugger>>debuggerMap (in category 'accessing') -----
debuggerMap
^debuggerMap ifNil:
[debuggerMap := self selectedContext debuggerMap].!
----- Method: Debugger>>doItContext (in category 'code pane') -----
doItContext
"Answer the context in which a text selection can be evaluated."
contextStackIndex = 0
ifTrue: [^super doItContext]
ifFalse: [^self selectedContext]!
----- Method: Debugger>>doItReceiver (in category 'code pane') -----
doItReceiver
"Answer the object that should be informed of the result of evaluating a
text selection."
^self receiver!
----- Method: Debugger>>doNothing: (in category 'accessing') -----
doNothing: newText
"Notifier window can't accept text"!
----- Method: Debugger>>doStep (in category 'context stack menu') -----
doStep
"Send the selected message in the accessed method, and regain control
after the invoked method returns."
| currentContext newContext |
self okToChange ifFalse: [^ self].
self checkContextSelection.
currentContext := self selectedContext.
newContext := interruptedProcess completeStep: currentContext.
newContext == currentContext ifTrue: [
newContext := interruptedProcess stepToSendOrReturn].
self contextStackIndex > 1
ifTrue: [self resetContext: newContext]
ifFalse: [newContext == currentContext
ifTrue: [self changed: #contentsSelection.
self updateInspectors]
ifFalse: [self resetContext: newContext]].
!
----- Method: Debugger>>down (in category 'context stack menu') -----
down
"move down the context stack to the previous (enclosing) context"
self toggleContextStackIndex: contextStackIndex+1!
----- Method: Debugger>>errorWasInUIProcess: (in category 'initialize') -----
errorWasInUIProcess: boolean
errorWasInUIProcess := boolean!
----- Method: Debugger>>expandStack (in category 'context stack (message list)') -----
expandStack
"A Notifier is being turned into a full debugger. Show a substantial amount of stack in the context pane."
self newStack: (contextStackTop stackOfSize: 20).
contextStackIndex := 0.
receiverInspector := Inspector inspect: nil.
contextVariablesInspector := ContextVariablesInspector inspect: nil.
proceedValue := nil!
----- Method: Debugger>>externalInterrupt: (in category 'private') -----
externalInterrupt: aBoolean
externalInterrupt := aBoolean !
----- Method: Debugger>>fullStack (in category 'context stack menu') -----
fullStack
"Change from displaying the minimal stack to a full one."
self contextStackList size > 20 "Already expanded"
ifTrue:
[self changed: #flash]
ifFalse:
[self contextStackIndex = 0 ifFalse: [
self toggleContextStackIndex: self contextStackIndex].
self fullyExpandStack]!
----- Method: Debugger>>fullyExpandStack (in category 'context stack (message list)') -----
fullyExpandStack
"Expand the stack to include all of it, rather than the first four or five
contexts."
self okToChange ifFalse: [^ self].
self newStack: contextStackTop contextStack.
self changed: #contextStackList!
----- Method: Debugger>>getSelectedText (in category 'tally support') -----
getSelectedText
| m interval text |
m := self getTextMorphWithSelection.
interval := m selectionInterval.
text := m text.
^ text copyFrom: interval first to: interval last
!
----- Method: Debugger>>getTextMorphWithSelection (in category 'tally support') -----
getTextMorphWithSelection
"This is extremely ugly... We I need to get a reference of the code pane, which is not easily accessible"
^ (self dependents select: [:m| m isKindOf: PluggableTextMorph])
detect: [:m| m selectionInterval first > 1] ifNone: [nil]!
----- Method: Debugger>>implement:inClass: (in category 'context stack menu') -----
implement: aMessage inClass: aClass
aClass
compile: aMessage createStubMethod
classified: (self askForCategoryIn: aClass default: 'as yet unclassified').
self setContentsToForceRefetch.
self selectedContext privRefreshWith: (aClass lookupSelector: aMessage selector).
self selectedContext method numArgs > 0 ifTrue:
[(self selectedContext tempAt: 1) arguments withIndexDo:
[:arg :index|
self selectedContext tempAt: index put: arg]].
self resetContext: self selectedContext.
self debug.
!
----- Method: Debugger>>initialExtent (in category 'initialize') -----
initialExtent
"Make the full debugger longer!!"
dependents size < 9 ifTrue: [^ super initialExtent]. "Pre debug window"
RealEstateAgent standardWindowExtent y < 400 "a tiny screen"
ifTrue: [^ super initialExtent].
^ 600 at 700
!
----- Method: Debugger>>interruptedContext (in category 'accessing') -----
interruptedContext
"Answer the suspended context of the interrupted process."
^contextStackTop!
----- Method: Debugger>>interruptedProcess (in category 'accessing') -----
interruptedProcess
"Answer the interrupted process."
^interruptedProcess!
----- Method: Debugger>>isNotifier (in category 'accessing') -----
isNotifier
"Return true if this debugger has not been expanded into a full sized window"
^ receiverInspector == nil!
----- Method: Debugger>>isolationRecoveryAdvice (in category 'private') -----
isolationRecoveryAdvice
"Return a notifier message string to be presented in case of recovery from recursive error by revoking the changes in an isolation layer. This surely ranks as one of Squeak's longer help messages."
^ 'Warning!! You have encountered a recursive error situation.
Don''t panic, but do read the following advice. If you were just fooling around, the simplest thing to do is to quit and NOT save, and restart Squeak. If you care about recovery, then read on...
In the process of diagnosing one error, further errors occurred, making it impossible to give you a debugger to work with. Squeak has jumped to an outer project where many of the objects and code changes that might have caused this problem are not involved in normal operation. If you are looking at this window, chances are that this first level of recovery was successful. If there are changes you care a lot about, try to save them now. Then, hopefully, from the state in this debugger, you can determine what the problem was and fix it. Do not save this image until you are confident of its recovery.
You are no longer in the world that is damaged. The two most likely causes of recursive errors are malformed objects (for instance a corrupt value encountered in any display of the desktop) and recurring code errors (such as a change that causes errors in any attempt to display the desktop).
In the case of malformed objects, you can attempt to repair them by altering various bindings in the corrupted environment. Open this debugger and examine the state of the objects closest to the error.
In the case of code errors, note that you are no longer in a world where the erroneous code is in effect. The only simple option available is for you to browse to the changeSet for the project in distress, and remove one or more of the changes (later it will be possible to edit the code remotely from here).
If you feel you have repaired the problem, then you may proceed from this debugger. This will put you back in the project that failed with the changes that failed for another try. Note that the debugger from which you are proceeding is the second one that occurred; you will likely find the first one waiting for you when you reenter the failed project!! Also note that if your error occurred while displaying a morph, it may now be flagged as undisplayable (red with yellow cross); if so, use the morph debug menu to choose ''start drawing again''.
If you have not repaired the problem, you should close this debugger and delete the failed project after retrieving whatever may be of value in it.
Good luck.
- The Squeak Fairy Godmother
PS: If you feel you need the help of a quantum mechanic, do NOT close this window. Instead, the best thing to do (after saving anything that seems safe to save) would be to use the ''save as...'' command in the world menu, and give it a new image name, such as OOPS. There is a good chance that someone who knows their way around Squeak can help you out.
'!
----- Method: Debugger>>labelString (in category 'accessing') -----
labelString
^labelString!
----- Method: Debugger>>labelString: (in category 'accessing') -----
labelString: aString
labelString := aString.
self changed: #relabel!
----- Method: Debugger>>lowSpaceChoices (in category 'private') -----
lowSpaceChoices
"Return a notifier message string to be presented when space is running low."
^ 'Warning!! Squeak is almost out of memory!!
Low space detection is now disabled. It will be restored when you close or proceed from this error notifier. Don''t panic, but do proceed with caution.
Here are some suggestions:
If you suspect an infinite recursion (the same methods calling each other again and again), then close this debugger, and fix the problem.
If you want this computation to finish, then make more space available (read on) and choose "proceed" in this debugger. Here are some ways to make more space available...
> Close any windows that are not needed.
> Get rid of some large objects (e.g., images).
> Leave this window on the screen, choose "save as..." from the screen menu, quit, restart the Squeak VM with a larger memory allocation, then restart the image you just saved, and choose "proceed" in this window.
If you want to investigate further, choose "debug" in this window. Do not use the debugger "fullStack" command unless you are certain that the stack is not very deep. (Trying to show the full stack will definitely use up all remaining memory if the low-space problem is caused by an infinite recursion!!).
'
!
----- Method: Debugger>>mailOutBugReport (in category 'context stack menu') -----
mailOutBugReport
"Compose a useful bug report showing the state of the process as well as vital image statistics as suggested by Chris Norton -
'Squeak could pre-fill the bug form with lots of vital, but
oft-repeated, information like what is the image version, last update
number, VM version, platform, available RAM, author...'
and address it to the list with the appropriate subject prefix."
| messageStrm |
MailSender default ifNil: [^self].
Cursor write
showWhile:
["Prepare the message"
messageStrm := WriteStream on: (String new: 1500).
messageStrm nextPutAll: 'From: ';
nextPutAll: MailSender userName;
cr;
nextPutAll: 'To: squeak-dev at lists.squeakfoundation.org';
cr;
nextPutAll: 'Subject: ';
nextPutAll: '[BUG]'; nextPutAll: self interruptedContext printString;
cr;cr;
nextPutAll: 'here insert explanation of what you were doing, suspect changes you''ve made and so forth.';cr;cr.
self interruptedContext errorReportOn: messageStrm.
MailSender sendMessage: (MailMessage from: messageStrm contents)].
!
----- Method: Debugger>>messageListIndex (in category 'context stack (message list)') -----
messageListIndex
"Answer the index of the currently selected context."
^contextStackIndex!
----- Method: Debugger>>messageListMenu:shifted: (in category 'context stack menu') -----
messageListMenu: aMenu shifted: shifted
"The context-stack menu takes the place of the message-list menu in the debugger, so pass it on"
^ self contextStackMenu: aMenu shifted: shifted!
----- Method: Debugger>>newStack: (in category 'private') -----
newStack: stack
| oldStack diff |
oldStack := contextStack.
contextStack := stack.
(oldStack == nil or: [oldStack last ~~ stack last])
ifTrue: [contextStackList := contextStack collect: [:ctx | ctx printString].
^ self].
"May be able to re-use some of previous list"
diff := stack size - oldStack size.
contextStackList := diff <= 0
ifTrue: [contextStackList copyFrom: 1-diff to: oldStack size]
ifFalse: [diff > 1
ifTrue: [contextStack collect: [:ctx | ctx printString]]
ifFalse: [(Array with: stack first printString) , contextStackList]]!
----- Method: Debugger>>notifierButtonHeight (in category 'initialize') -----
notifierButtonHeight
^ 18!
----- Method: Debugger>>openFullNoSuspendLabel: (in category 'initialize') -----
openFullNoSuspendLabel: aString
"Create and schedule a full debugger with the given label. Do not terminate the current active process."
| oldContextStackIndex |
oldContextStackIndex := contextStackIndex.
self expandStack. "Sets contextStackIndex to zero."
ToolBuilder open: self label: aString.
self toggleContextStackIndex: oldContextStackIndex.!
----- Method: Debugger>>openNotifierContents:label: (in category 'initialize') -----
openNotifierContents: msgString label: label
"Create and schedule a notifier view with the given label and message. A notifier view shows just the message or the first several lines of the stack, with a menu that allows the user to open a full debugger if so desired."
"NOTE: When this method returns, a new process has been scheduled to run the windows, and thus this notifier, but the previous active porcess has not been suspended. The sender will do this."
| msg builder spec |
Sensor flushKeyboard.
savedCursor := Sensor currentCursor.
Sensor currentCursor: Cursor normal.
(label beginsWith: 'Space is low')
ifTrue: [msg := self lowSpaceChoices, (msgString ifNil: [''])]
ifFalse: [msg := msgString].
isolationHead ifNotNil:
["We have already revoked the isolation layer -- now jump to the parent project."
msg := self isolationRecoveryAdvice, msgString.
failedProject := Project current.
isolationHead parent enterForEmergencyRecovery].
builder := ToolBuilder default.
spec := self buildNotifierWith: builder label: label message: msg.
self expandStack.
builder open: spec.
errorWasInUIProcess := Project spawnNewProcessIfThisIsUI: interruptedProcess.
!
----- Method: Debugger>>optionalButtonPairs (in category 'initialize') -----
optionalButtonPairs
"Actually, return triples. Only the custom debugger-specific buttons are shown"
^ self customButtonSpecs!
----- Method: Debugger>>pc (in category 'code pane') -----
pc
^ self pcRange!
----- Method: Debugger>>pcRange (in category 'code pane') -----
pcRange
"Answer the indices in the source code for the method corresponding to
the selected context's program counter value."
(selectingPC and: [contextStackIndex ~= 0]) ifFalse:
[^1 to: 0].
self selectedContext isDead ifTrue:
[^1 to: 0].
^self selectedContext debuggerMap
rangeForPC: self selectedContext pc
contextIsActiveContext: contextStackIndex = 1!
----- Method: Debugger>>peelToFirst (in category 'context stack menu') -----
peelToFirst
"Peel the stack back to the second occurance of the currently selected message. Very useful for an infinite recursion. Gets back to the second call so you can see one complete recursion cycle, and how it was called at the beginning. Also frees a lot of space!!"
| ctxt |
contextStackIndex = 0 ifTrue: [^ Beeper beep].
"self okToChange ifFalse: [^ self]."
ctxt := interruptedProcess popTo: self selectedContext findSecondToOldestSimilarSender.
self resetContext: ctxt.
!
----- Method: Debugger>>perform:orSendTo: (in category 'code pane menu') -----
perform: selector orSendTo: otherTarget
"Selector was just chosen from a menu by a user. If can respond, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked."
| result |
(#(debug proceed) includes: selector) "When I am a notifier window"
ifTrue: [^ self perform: selector]
ifFalse: [result := super perform: selector orSendTo: otherTarget.
selector == #doIt ifTrue: [
result ~~ #failedDoit ifTrue: [self proceedValue: result]].
^ result]!
----- Method: Debugger>>populateImplementInMenu: (in category 'context stack menu') -----
populateImplementInMenu: aMenu
| msg |
msg := self selectedContext at: 1.
self selectedContext receiver class withAllSuperclasses do:
[:each |
aMenu add: each name target: self selector: #implement:inClass: argumentList: (Array with: msg with: each)].
^ aMenu
!
----- Method: Debugger>>preDebugButtonQuads (in category 'initialize') -----
preDebugButtonQuads
^Preferences eToyFriendly
ifTrue: [
{
{'Store log' translated. #storeLog. #blue. 'write a log of the encountered problem' translated}.
{'Abandon' translated. #abandon. #black. 'abandon this execution by closing this window' translated}.
{'Debug' translated. #debug. #red. 'bring up a debugger' translated}}]
ifFalse: [
{
{'Proceed' translated. #proceed. #blue. 'continue execution' translated}.
{'Abandon' translated. #abandon. #black. 'abandon this execution by closing this window' translated}.
{'Debug' translated. #debug. #red. 'bring up a debugger' translated}}]
!
----- Method: Debugger>>preDebugMessageString (in category 'toolbuilder') -----
preDebugMessageString
^ message ifNil: ['An error has occurred; you should probably just hit ''abandon''. Sorry!!'].!
----- Method: Debugger>>proceed (in category 'context stack menu') -----
proceed
"Proceed execution of the receiver's model, starting after the expression at
which an interruption occurred."
Smalltalk okayToProceedEvenIfSpaceIsLow ifTrue: [
self proceed: self topView].
!
----- Method: Debugger>>proceed: (in category 'context stack menu') -----
proceed: aTopView
"Proceed from the interrupted state of the currently selected context. The
argument is the topView of the receiver. That view is closed."
self okToChange ifFalse: [^ self].
self checkContextSelection.
self resumeProcess: aTopView!
----- Method: Debugger>>proceedValue (in category 'accessing') -----
proceedValue
"Answer the value to return to the selected context when the interrupted
process proceeds."
^proceedValue!
----- Method: Debugger>>proceedValue: (in category 'accessing') -----
proceedValue: anObject
"Set the value to be returned to the selected context when the interrupted
process proceeds."
proceedValue := anObject!
----- Method: Debugger>>process:controller:context: (in category 'private') -----
process: aProcess controller: aController context: aContext
^ self process: aProcess controller: aController context: aContext isolationHead: nil!
----- Method: Debugger>>process:controller:context:isolationHead: (in category 'private') -----
process: aProcess controller: aController context: aContext isolationHead: projectOrNil
super initialize.
Smalltalk at: #MessageTally ifPresentAndInMemory: [:c | c new close].
contents := nil.
interruptedProcess := aProcess.
interruptedController := aController.
contextStackTop := aContext.
self newStack: (contextStackTop stackOfSize: 1).
contextStackIndex := 1.
externalInterrupt := false.
selectingPC := true.
isolationHead := projectOrNil.
Smalltalk isMorphic ifTrue:
[errorWasInUIProcess := false]!
----- Method: Debugger>>receiver (in category 'accessing') -----
receiver
"Answer the receiver of the selected context, if any. Answer nil
otherwise."
contextStackIndex = 0
ifTrue: [^nil]
ifFalse: [^self selectedContext receiver]!
----- Method: Debugger>>receiverInspector (in category 'accessing') -----
receiverInspector
"Answer the instance of Inspector that is providing a view of the
variables of the selected context's receiver."
^receiverInspector!
----- Method: Debugger>>receiverInspectorObject:context: (in category 'accessing') -----
receiverInspectorObject: obj context: ctxt
"set context before object so it can refer to context when building field list"
receiverInspector context: ctxt.
receiverInspector object: obj.
!
----- Method: Debugger>>release (in category 'initialize') -----
release
self windowIsClosing.
super release.
!
----- Method: Debugger>>resetContext: (in category 'private') -----
resetContext: aContext
"Used when a new context becomes top-of-stack, for instance when the
method of the selected context is re-compiled, or the simulator steps or
returns to a new method. There is room for much optimization here, first
to save recomputing the whole stack list (and text), and secondly to avoid
recomposing all that text (by editing the paragraph instead of recreating it)."
| oldContext |
oldContext := self selectedContext.
contextStackTop := aContext.
self newStack: contextStackTop contextStack.
self changed: #contextStackList.
self contextStackIndex: 1 oldContextWas: oldContext.
self contentsChanged.
!
----- Method: Debugger>>restart (in category 'context stack menu') -----
restart
"Proceed from the initial state of the currently selected context. The
argument is a controller on a view of the receiver. That view is closed."
"Closing now depends on a preference #restartAlsoProceeds - hmm 9/7/2001 16:46"
| ctxt noUnwindError |
self okToChange ifFalse: [^ self].
self checkContextSelection.
ctxt := interruptedProcess popTo: self selectedContext.
noUnwindError := false.
ctxt == self selectedContext ifTrue: [
noUnwindError := true.
interruptedProcess restartTop; stepToSendOrReturn].
self resetContext: ctxt.
(Preferences restartAlsoProceeds and: [noUnwindError]) ifTrue: [self proceed].
!
----- Method: Debugger>>resumeProcess: (in category 'private') -----
resumeProcess: aTopView
Smalltalk isMorphic
ifFalse: [aTopView erase].
savedCursor
ifNotNil: [Sensor currentCursor: savedCursor].
isolationHead
ifNotNil: [failedProject enterForEmergencyRecovery.
isolationHead invoke.
isolationHead := nil].
interruptedProcess isTerminated ifFalse: [
Smalltalk isMorphic
ifTrue: [errorWasInUIProcess
ifTrue: [Project resumeProcess: interruptedProcess]
ifFalse: [interruptedProcess resume]]
ifFalse: [ScheduledControllers activeControllerNoTerminate: interruptedController andProcess: interruptedProcess]].
"if old process was terminated, just terminate current one"
interruptedProcess := nil.
"Before delete, so release doesn't terminate it"
Smalltalk isMorphic
ifTrue: [aTopView delete.
World displayWorld]
ifFalse: [aTopView controller closeAndUnscheduleNoErase].
Smalltalk installLowSpaceWatcher.
"restart low space handler"
errorWasInUIProcess == false
ifFalse: [Processor terminateActive]!
----- Method: Debugger>>returnValue (in category 'context stack menu') -----
returnValue
"Force a return of a given value to the previous context!!"
| previous selectedContext expression value |
contextStackIndex = 0 ifTrue: [^Beeper beep].
selectedContext := self selectedContext.
expression := UIManager default request: 'Enter expression for return value:'.
value := Compiler new
evaluate: expression
in: selectedContext
to: selectedContext receiver.
previous := selectedContext sender.
self resetContext: previous.
interruptedProcess popTo: previous value: value!
----- Method: Debugger>>runToSelection: (in category 'code pane menu') -----
runToSelection: selectionInterval
| currentContext |
self pc first >= selectionInterval first ifTrue: [ ^self ].
currentContext := self selectedContext.
[ currentContext == self selectedContext and: [ self pc first < selectionInterval first ] ] whileTrue: [ self doStep ].!
----- Method: Debugger>>selectPC (in category 'context stack menu') -----
selectPC
"Toggle the flag telling whether to automatically select the expression
currently being executed by the selected context."
selectingPC := selectingPC not!
----- Method: Debugger>>selectedClass (in category 'class list') -----
selectedClass
"Answer the class in which the currently selected context's method was
found."
^self selectedContext methodClass!
----- Method: Debugger>>selectedClassOrMetaClass (in category 'class list') -----
selectedClassOrMetaClass
"Answer the class in which the currently selected context's method was
found."
^self selectedClass!
----- Method: Debugger>>selectedContext (in category 'private') -----
selectedContext
contextStackIndex = 0
ifTrue: [^contextStackTop]
ifFalse: [^contextStack at: contextStackIndex]!
----- Method: Debugger>>selectedMessage (in category 'context stack (message list)') -----
selectedMessage
"Answer the source code of the currently selected context."
^contents := self selectedContext debuggerMap sourceText asText makeSelectorBold!
----- Method: Debugger>>selectedMessageCategoryName (in category 'message category list') -----
selectedMessageCategoryName
"Answer the name of the message category of the message of the
currently selected context."
^self selectedClass organization categoryOfElement: self selectedMessageName!
----- Method: Debugger>>selectedMessageName (in category 'context stack (message list)') -----
selectedMessageName
"Answer the message selector of the currently selected context.
If the method is unbound we can still usefully answer its old selector."
| selector |
selector := self selectedContext selector.
^(selector ~~ self selectedContext method selector
and: [selector beginsWith: 'DoIt'])
ifTrue: [self selectedContext method selector]
ifFalse: [selector]!
----- Method: Debugger>>send (in category 'context stack menu') -----
send
"Send the selected message in the accessed method, and take control in
the method invoked to allow further step or send."
self okToChange ifFalse: [^ self].
self checkContextSelection.
interruptedProcess step: self selectedContext.
self resetContext: interruptedProcess stepToSendOrReturn.
!
----- Method: Debugger>>step (in category 'dependents access') -----
step
"Update the inspectors."
receiverInspector ifNotNil: [receiverInspector step].
contextVariablesInspector ifNotNil: [contextVariablesInspector step].
!
----- Method: Debugger>>stepIntoBlock (in category 'context stack menu') -----
stepIntoBlock
"Send messages until you return to the present method context.
Used to step into a block in the method."
interruptedProcess stepToHome: self selectedContext.
self resetContext: interruptedProcess stepToSendOrReturn.!
----- Method: Debugger>>storeLog (in category 'notifier menu') -----
storeLog
| logFileName |
logFileName := Preferences debugLogTimestamp
ifTrue: ['SqueakDebug-' , Time totalSeconds printString , '.log']
ifFalse: ['SqueakDebug.log'].
Smalltalk logError: labelString printString inContext: contextStackTop to: logFileName
!
----- Method: Debugger>>tally (in category 'tally support') -----
tally
self getTextMorphWithSelection ifNotNilDo: [:o| o tallyIt] ifNil: [Beeper beep]
!
----- Method: Debugger>>toggleBreakOnEntry (in category 'breakpoints') -----
toggleBreakOnEntry
"Install or uninstall a halt-on-entry breakpoint"
| selectedMethod |
self selectedClassOrMetaClass isNil ifTrue:[^self].
selectedMethod := self selectedClassOrMetaClass >> self selectedMessageName.
selectedMethod hasBreakpoint
ifTrue:
[BreakpointManager unInstall: selectedMethod]
ifFalse:
[BreakpointManager
installInClass: self selectedClassOrMetaClass
selector: self selectedMessageName].!
----- Method: Debugger>>toggleContextStackIndex: (in category 'context stack (message list)') -----
toggleContextStackIndex: anInteger
"If anInteger is the same as the index of the selected context, deselect it.
Otherwise, the context whose index is anInteger becomes the selected
context."
self contextStackIndex:
(contextStackIndex = anInteger
ifTrue: [0]
ifFalse: [anInteger])
oldContextWas:
(contextStackIndex = 0
ifTrue: [nil]
ifFalse: [contextStack at: contextStackIndex])!
----- Method: Debugger>>up (in category 'context stack menu') -----
up
"move up the context stack to the next (enclosed) context"
contextStackIndex > 1 ifTrue: [self toggleContextStackIndex: contextStackIndex-1]!
----- Method: Debugger>>updateInspectors (in category 'dependents access') -----
updateInspectors
"Update the inspectors on the receiver's variables."
receiverInspector == nil ifFalse: [receiverInspector update].
contextVariablesInspector == nil ifFalse: [contextVariablesInspector update]!
----- Method: Debugger>>wantsAnnotationPane (in category 'toolbuilder') -----
wantsAnnotationPane
"Annotations don't look good in debugger. Suppress 'em."
^false!
----- Method: Debugger>>wantsOptionalButtons (in category 'initialize') -----
wantsOptionalButtons
"The debugger benefits so majorly from the optional buttons that we put them up regardless of the global setting. Some traditionalists will want to change this method manually!!"
^ true!
----- Method: Debugger>>wantsSteps (in category 'dependents access') -----
wantsSteps
^ true!
----- Method: Debugger>>where (in category 'context stack menu') -----
where
"Select the expression whose evaluation was interrupted."
selectingPC := true.
self contextStackIndex: contextStackIndex oldContextWas: self selectedContext
!
----- Method: Debugger>>windowIsClosing (in category 'initialize') -----
windowIsClosing
"My window is being closed; clean up. Restart the low space watcher."
interruptedProcess == nil ifTrue: [^ self].
interruptedProcess terminate.
interruptedProcess := nil.
interruptedController := nil.
contextStack := nil.
contextStackTop := nil.
receiverInspector := nil.
contextVariablesInspector := nil.
Smalltalk installLowSpaceWatcher. "restart low space handler"
!
CodeHolder subclass: #MethodHolder
instanceVariableNames: 'methodClass methodSelector'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Base'!
----- Method: MethodHolder class>>isolatedCodePaneForClass:selector: (in category 'instance creation') -----
isolatedCodePaneForClass: aClass selector: aSelector
"Answer a MethodMorph on the given class and selector"
| aCodePane aMethodHolder |
aMethodHolder := self new.
aMethodHolder methodClass: aClass methodSelector: aSelector.
aCodePane := MethodMorph on: aMethodHolder text: #contents accept: #contents:notifying:
readSelection: #contentsSelection menu: #codePaneMenu:shifted:.
aMethodHolder addDependent: aCodePane.
aCodePane borderWidth: 2; color: Color white.
aCodePane scrollBarOnLeft: false.
aCodePane width: 300.
^ aCodePane!
----- Method: MethodHolder class>>makeIsolatedCodePaneForClass:selector: (in category 'instance creation') -----
makeIsolatedCodePaneForClass: aClass selector: aSelector
"Create, and place in the morphic Hand, an isolated code pane bearing source code for the given class and selector"
(self isolatedCodePaneForClass: aClass selector: aSelector) openInHand!
----- Method: MethodHolder>>addModelMenuItemsTo:forMorph:hand: (in category 'menu') -----
addModelMenuItemsTo: aCustomMenu forMorph: aMorph hand: aHandMorph
aCustomMenu addLine.
aCustomMenu add: 'whose script is this?' translated target: self action: #identifyScript
!
----- Method: MethodHolder>>changeMethodSelectorTo: (in category 'miscellaneous') -----
changeMethodSelectorTo: aSelector
"Change my method selector as noted. Reset currentCompiledMethod"
methodSelector := aSelector.
currentCompiledMethod := methodClass compiledMethodAt: aSelector ifAbsent: [nil]!
----- Method: MethodHolder>>compiledMethod (in category 'miscellaneous') -----
compiledMethod
^ methodClass compiledMethodAt: methodSelector!
----- Method: MethodHolder>>contents (in category 'contents') -----
contents
"Answer the contents, with due respect for my contentsSymbol"
contents := methodClass sourceCodeAt: methodSelector ifAbsent: [''].
currentCompiledMethod := methodClass compiledMethodAt: methodSelector ifAbsent: [nil].
self showingDecompile ifTrue: [^ self decompiledSourceIntoContents].
self showingDocumentation ifTrue: [^ self commentContents].
^ contents := self sourceStringPrettifiedAndDiffed asText makeSelectorBoldIn: methodClass!
----- Method: MethodHolder>>contents:notifying: (in category 'contents') -----
contents: input notifying: aController
| selector |
(selector := methodClass parserClass new parseSelector: input asText) ifNil:
[self inform: 'Sorry - invalid format for the
method name and arguments -- cannot accept.'.
^ false].
selector == methodSelector ifFalse:
[self inform:
'You cannot change the name of
the method here -- it must continue
to be ', methodSelector.
^ false].
selector := methodClass
compile: input asText
classified: self selectedMessageCategoryName
notifying: aController.
selector == nil ifTrue: [^ false].
contents := input asString copy.
currentCompiledMethod := methodClass compiledMethodAt: methodSelector.
^ true!
----- Method: MethodHolder>>doItReceiver (in category 'menu') -----
doItReceiver
"If there is an instance associated with me, answer it, for true mapping of self. If not, then do what other code-bearing tools do, viz. give access to the class vars."
(self dependents detect: [:m | m isKindOf: MethodMorph]) ifNotNilDo:
[:mm | (mm owner isKindOf: ScriptEditorMorph) ifTrue:
[^ mm owner playerScripted]].
^ self selectedClass ifNil: [FakeClassPool new]!
----- Method: MethodHolder>>identifyScript (in category 'miscellaneous') -----
identifyScript
| msg aPlayer |
msg := methodClass isUniClass
ifTrue:
[aPlayer := methodClass someInstance.
aPlayer costume
ifNotNil:
['This holds code for a script
named ', methodSelector, ' belonging
to an object named ', aPlayer externalName]
ifNil:
['This formerly held code for a script
named ', methodSelector, ' for a Player
who once existed but now is moribund.']]
ifFalse:
['This holds code for the method
named ', methodSelector, '
for class ', methodClass name].
self inform: msg!
----- Method: MethodHolder>>methodClass:methodSelector: (in category 'miscellaneous') -----
methodClass: aClass methodSelector: aSelector
methodClass := aClass.
methodSelector := aSelector.
currentCompiledMethod := aClass compiledMethodAt: aSelector ifAbsent: [nil]!
----- Method: MethodHolder>>selectedClass (in category 'selection') -----
selectedClass
^ methodClass theNonMetaClass!
----- Method: MethodHolder>>selectedClassOrMetaClass (in category 'selection') -----
selectedClassOrMetaClass
^ methodClass!
----- Method: MethodHolder>>selectedMessageCategoryName (in category 'selection') -----
selectedMessageCategoryName
^ methodClass organization categoryOfElement: methodSelector!
----- Method: MethodHolder>>selectedMessageName (in category 'selection') -----
selectedMessageName
^ methodSelector!
----- Method: MethodHolder>>versions (in category 'miscellaneous') -----
versions
"Return a VersionsBrowser (containing a list of ChangeRecords) of older versions of this method."
^ VersionsBrowser new scanVersionsOf: self compiledMethod
class: self selectedClass
meta: methodClass isMeta
category: self selectedMessageCategoryName
"(classOfMethod whichCategoryIncludesSelector: selectorOfMethod)"
selector: methodSelector!
StringHolder subclass: #Inspector
instanceVariableNames: 'object selectionIndex timeOfLastListUpdate selectionUpdateTime context'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
!Inspector commentStamp: '<historical>' prior: 0!
I represent a query path into the internal representation of an object. As a StringHolder, the string I represent is the value of the currently selected variable of the observed object.!
Inspector subclass: #BasicInspector
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
----- Method: BasicInspector>>inspect: (in category 'as yet unclassified') -----
inspect: anObject
"Initialize the receiver so that it is inspecting anObject. There is no
current selection."
self initialize.
object := anObject.
selectionIndex := 0.
contents := ''!
Inspector subclass: #CompiledMethodInspector
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
----- Method: CompiledMethodInspector>>contentsIsString (in category 'selecting') -----
contentsIsString
"Hacked so contents empty when deselected"
^ #(0 2 3) includes: selectionIndex!
----- Method: CompiledMethodInspector>>fieldList (in category 'accessing') -----
fieldList
| keys |
keys := OrderedCollection new.
keys add: 'self'.
keys add: 'all bytecodes'.
keys add: 'header'.
1 to: object numLiterals do: [ :i |
keys add: 'literal', i printString ].
object initialPC to: object size do: [ :i |
keys add: i printString ].
^ keys asArray
!
----- Method: CompiledMethodInspector>>selection (in category 'selecting') -----
selection
| bytecodeIndex |
selectionIndex = 0 ifTrue: [^ ''].
selectionIndex = 1 ifTrue: [^ object ].
selectionIndex = 2 ifTrue: [^ object symbolic].
selectionIndex = 3 ifTrue: [^ object headerDescription].
selectionIndex <= (object numLiterals + 3)
ifTrue: [ ^ object objectAt: selectionIndex - 2 ].
bytecodeIndex := selectionIndex - object numLiterals - 3.
^ object at: object initialPC + bytecodeIndex - 1!
----- Method: CompiledMethodInspector>>selectionUnmodifiable (in category 'selecting') -----
selectionUnmodifiable
"Answer if the current selected variable is unmodifiable via acceptance in the code pane. For most inspectors, no selection and a selection of self (selectionIndex = 1) are unmodifiable"
^ true!
Inspector subclass: #ContextVariablesInspector
instanceVariableNames: 'fieldList'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Debugger'!
!ContextVariablesInspector commentStamp: '<historical>' prior: 0!
I represent a query path into the internal representation of a ContextPart. Typically this is a context at a point in the query path of a Debugger. As a StringHolder, the string I represent is the value of the currently selected variable of the observed temporary variable of the context.!
----- Method: ContextVariablesInspector>>doItContext (in category 'code') -----
doItContext
^object!
----- Method: ContextVariablesInspector>>doItReceiver (in category 'code') -----
doItReceiver
^object receiver!
----- Method: ContextVariablesInspector>>fieldList (in category 'accessing') -----
fieldList
"Refer to the comment in Inspector|fieldList."
object == nil ifTrue: [^Array with: 'thisContext'].
^fieldList ifNil:[fieldList := (Array with: 'thisContext' with: 'stack top' with: 'all temp vars') , object tempNames]!
----- Method: ContextVariablesInspector>>inspect: (in category 'accessing') -----
inspect: anObject
"Initialize the receiver so that it is inspecting anObject. There is no
current selection.
Because no object's inspectorClass method answers this class, it is OK for this method to
override Inspector >> inspect: "
fieldList := nil.
object := anObject.
self initialize.
!
----- Method: ContextVariablesInspector>>replaceSelectionValue: (in category 'selecting') -----
replaceSelectionValue: anObject
"Refer to the comment in Inspector|replaceSelectionValue:."
^selectionIndex = 1
ifTrue: [object]
ifFalse: [object namedTempAt: selectionIndex - 3 put: anObject]!
----- Method: ContextVariablesInspector>>selection (in category 'selecting') -----
selection
"Refer to the comment in Inspector|selection."
selectionIndex = 0 ifTrue:[^''].
selectionIndex = 1 ifTrue: [^object].
selectionIndex = 2 ifTrue: [^object stackPtr > 0 ifTrue: [object top]].
selectionIndex = 3 ifTrue: [^object tempsAndValues].
^object debuggerMap namedTempAt: selectionIndex - 3 in: object!
Inspector subclass: #DictionaryInspector
instanceVariableNames: 'keyArray'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
----- Method: DictionaryInspector>>addEntry (in category 'menu') -----
addEntry
| newKey aKey |
newKey := UIManager default request:
'Enter new key, then type RETURN.
(Expression will be evaluated for value.)
Examples: #Fred ''a string'' 3+4'.
aKey := Compiler evaluate: newKey.
object at: aKey put: nil.
self calculateKeyArray.
selectionIndex := self numberOfFixedFields + (keyArray indexOf: aKey).
self changed: #inspectObject.
self changed: #selectionIndex.
self changed: #fieldList.
self update!
----- Method: DictionaryInspector>>addEntry: (in category 'selecting') -----
addEntry: aKey
object at: aKey put: nil.
self calculateKeyArray.
selectionIndex := self numberOfFixedFields + (keyArray indexOf: aKey).
self changed: #inspectObject.
self changed: #selectionIndex.
self changed: #fieldList.
self update!
----- Method: DictionaryInspector>>calculateKeyArray (in category 'selecting') -----
calculateKeyArray
"Recalculate the KeyArray from the object being inspected"
keyArray := object keysSortedSafely asArray.
selectionIndex := 0.
!
----- Method: DictionaryInspector>>contentsIsString (in category 'selecting') -----
contentsIsString
"Hacked so contents empty when deselected"
^ (selectionIndex = 0)!
----- Method: DictionaryInspector>>copyName (in category 'menu') -----
copyName
"Copy the name of the current variable, so the user can paste it into the
window below and work with is. If collection, do (xxx at: 1)."
| sel |
self selectionIndex <= self numberOfFixedFields
ifTrue: [super copyName]
ifFalse: [sel := String streamContents: [:strm |
strm nextPutAll: '(self at: '.
(keyArray at: selectionIndex - self numberOfFixedFields)
storeOn: strm.
strm nextPutAll: ')'].
Clipboard clipboardText: sel asText "no undo allowed"]!
----- Method: DictionaryInspector>>fieldList (in category 'accessing') -----
fieldList
^ self baseFieldList
, (keyArray collect: [:key | key printString])!
----- Method: DictionaryInspector>>fieldListMenu: (in category 'menu') -----
fieldListMenu: aMenu
^ aMenu labels:
'inspect
copy name
references
objects pointing to this value
senders of this key
refresh view
add key
rename key
remove
basic inspect'
lines: #(6 9)
selections: #(inspectSelection copyName selectionReferences objectReferencesToSelection sendersOfSelectedKey refreshView addEntry renameEntry removeSelection inspectBasic)
!
----- Method: DictionaryInspector>>initialize (in category 'initialize-release') -----
initialize
super initialize.
self calculateKeyArray!
----- Method: DictionaryInspector>>numberOfFixedFields (in category 'private') -----
numberOfFixedFields
^ 2 + object class instSize!
----- Method: DictionaryInspector>>refreshView (in category 'selecting') -----
refreshView
| i |
i := selectionIndex.
self calculateKeyArray.
selectionIndex := i.
self changed: #fieldList.
self changed: #contents.!
----- Method: DictionaryInspector>>removeSelection (in category 'menu') -----
removeSelection
selectionIndex = 0 ifTrue: [^ self changed: #flash].
object removeKey: (keyArray at: selectionIndex - self numberOfFixedFields).
selectionIndex := 0.
contents := ''.
self calculateKeyArray.
self changed: #inspectObject.
self changed: #selectionIndex.
self changed: #fieldList.
self changed: #selection.!
----- Method: DictionaryInspector>>renameEntry (in category 'menu') -----
renameEntry
| newKey aKey value |
value := object at: (keyArray at: selectionIndex - self numberOfFixedFields).
newKey := UIManager default request:
'Enter new key, then type RETURN.
(Expression will be evaluated for value.)
Examples: #Fred ''a string'' 3+4'
initialAnswer: (keyArray at: selectionIndex - self numberOfFixedFields) printString.
aKey := Compiler evaluate: newKey.
object removeKey: (keyArray at: selectionIndex - self numberOfFixedFields).
object at: aKey put: value.
self calculateKeyArray.
selectionIndex := self numberOfFixedFields + (keyArray indexOf: aKey).
self changed: #selectionIndex.
self changed: #inspectObject.
self changed: #fieldList.
self update!
----- Method: DictionaryInspector>>replaceSelectionValue: (in category 'selecting') -----
replaceSelectionValue: anObject
selectionIndex <= self numberOfFixedFields
ifTrue: [^ super replaceSelectionValue: anObject].
^ object
at: (keyArray at: selectionIndex - self numberOfFixedFields)
put: anObject!
----- Method: DictionaryInspector>>selection (in category 'selecting') -----
selection
selectionIndex <= (self numberOfFixedFields) ifTrue: [^ super selection].
^ object at: (keyArray at: selectionIndex - self numberOfFixedFields) ifAbsent:[nil]!
----- Method: DictionaryInspector>>selectionReferences (in category 'menu') -----
selectionReferences
"Create a browser on all references to the association of the current selection."
self selectionIndex = 0 ifTrue: [^ self changed: #flash].
object class == MethodDictionary ifTrue: [^ self changed: #flash].
self systemNavigation browseAllCallsOn: (object associationAt: (keyArray at: selectionIndex - self numberOfFixedFields)).
!
----- Method: DictionaryInspector>>sendersOfSelectedKey (in category 'menu') -----
sendersOfSelectedKey
"Create a browser on all senders of the selected key"
| aKey |
self selectionIndex = 0
ifTrue: [^ self changed: #flash].
((aKey := keyArray at: selectionIndex - self numberOfFixedFields) isSymbol)
ifFalse: [^ self changed: #flash].
SystemNavigation default browseAllCallsOn: aKey!
Inspector subclass: #ExternalStructureInspector
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
----- Method: ExternalStructureInspector>>fieldList (in category 'accessing') -----
fieldList
^ (Array with: 'self: ', object defaultLabelForInspector with: 'all inst vars'), self recordFieldList!
----- Method: ExternalStructureInspector>>recordFieldList (in category 'accessing') -----
recordFieldList
| fields |
fields := object class fields.
(fields first isKindOf: Array) ifFalse: [fields := Array with: fields].
^fields collect: [ :field | field first ] thenSelect: [:name | name notNil]!
----- Method: ExternalStructureInspector>>replaceSelectionValue: (in category 'selecting') -----
replaceSelectionValue: anObject
"Add colon to fieldname to get setter selector, and send it to object with the argument.
Refer to the comment in Inspector|replaceSelectionValue:."
selectionIndex = 1
ifTrue: [^object]
ifFalse: [^object perform: ((self fieldList at: selectionIndex), ':') asSymbol with: anObject]!
----- Method: ExternalStructureInspector>>selection (in category 'selecting') -----
selection
"Refer to the comment in Inspector|selection."
selectionIndex = 0 ifTrue:[^object printString].
selectionIndex = 1 ifTrue: [^object].
selectionIndex = 2 ifTrue:[^object longPrintString].
selectionIndex > 2
ifTrue: [^object perform: (self fieldList at: selectionIndex)]!
----- Method: Inspector class>>inspect: (in category 'instance creation') -----
inspect: anObject
"Answer an instance of me to provide an inspector for anObject."
"We call basicNew to avoid a premature initialization; the instance method
inspect: anObject will do a self initialize."
^self basicNew inspect: anObject!
----- Method: Inspector class>>openOn: (in category 'instance creation') -----
openOn: anObject
"Create and schedule an instance of me on the model, anInspector. "
^ self openOn: anObject withEvalPane: true!
----- Method: Inspector class>>openOn:withEvalPane: (in category 'instance creation') -----
openOn: anObject withEvalPane: withEval
"Create and schedule an instance of me on the model, anInspector. "
^ self openOn: anObject withEvalPane: withEval withLabel: anObject defaultLabelForInspector!
----- Method: Inspector class>>openOn:withEvalPane:withLabel: (in category 'instance creation') -----
openOn: anObject withEvalPane: withEval withLabel: label
^ToolBuilder open: (self inspect: anObject) label: label!
----- Method: Inspector>>accept: (in category 'selecting') -----
accept: aString
| result |
result := self doItReceiver class evaluatorClass new
evaluate: (ReadStream on: aString)
in: self doItContext
to: self doItReceiver
notifying: nil "fix this"
ifFail: [self changed: #flash.
^ false].
result == #failedDoit ifTrue: [^ false].
self replaceSelectionValue: result.
self changed: #contents.
^ true!
----- Method: Inspector>>addCollectionItemsTo: (in category 'menu commands') -----
addCollectionItemsTo: aMenu
"If the current selection is an appropriate collection, add items to aMenu that cater to that kind of selection"
| sel |
((((sel := self selection) isMemberOf: Array) or: [sel isMemberOf: OrderedCollection]) and:
[sel size > 0]) ifTrue: [
aMenu addList: #(
('inspect element...' inspectElement))].
(sel isKindOf: MorphExtension) ifTrue: [
aMenu addList: #(
('inspect property...' inspectElement))].!
----- Method: Inspector>>baseFieldList (in category 'accessing') -----
baseFieldList
"Answer an Array consisting of 'self'
and the instance variable names of the inspected object."
^ (Array with: 'self' with: 'all inst vars')
, object class allInstVarNames!
----- Method: Inspector>>browseFullProtocol (in category 'menu commands') -----
browseFullProtocol
"Open up a protocol-category browser on the value of the receiver's current selection. If in mvc, an old-style protocol browser is opened instead."
| objectToRepresent |
Smalltalk isMorphic ifFalse: [^ self spawnProtocol].
objectToRepresent := self selectionIndex == 0 ifTrue: [object] ifFalse: [self selection].
InstanceBrowser new openOnObject: objectToRepresent showingSelector: nil!
----- Method: Inspector>>buildCodePaneWith: (in category 'toolbuilder') -----
buildCodePaneWith: builder
| textSpec |
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #trash;
setText: #trash:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:;
askBeforeDiscardingEdits: false.
^textSpec!
----- Method: Inspector>>buildFieldListWith: (in category 'toolbuilder') -----
buildFieldListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #fieldList;
getIndex: #selectionIndex;
setIndex: #toggleIndex:;
menu: #fieldListMenu:;
keyPress: #inspectorKey:from:.
^listSpec!
----- Method: Inspector>>buildValuePaneWith: (in category 'toolbuilder') -----
buildValuePaneWith: builder
| textSpec |
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #contents;
setText: #accept:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:.
^textSpec!
----- Method: Inspector>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"Inspector openOn: SystemOrganization"
| windowSpec |
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 0.3 at 0.7) -> [self buildFieldListWith: builder].
(0.3 at 0.0corner: 1 at 0.7) -> [self buildValuePaneWith: builder].
(0.0 at 0.7corner: 1 at 1) -> [self buildCodePaneWith: builder].
}.
^builder build: windowSpec!
----- Method: Inspector>>chasePointers (in category 'menu commands') -----
chasePointers
| selected saved |
self selectionIndex == 0 ifTrue: [^ self changed: #flash].
selected := self selection.
saved := self object.
[self object: nil.
(Smalltalk includesKey: #PointerFinder)
ifTrue: [PointerFinder on: selected]
ifFalse: [self inspectPointers]]
ensure: [self object: saved]!
----- Method: Inspector>>classOfSelection (in category 'menu commands') -----
classOfSelection
"Answer the class of the receiver's current selection"
self selectionUnmodifiable ifTrue: [^ object class].
^ self selection class!
----- Method: Inspector>>classVarRefs (in category 'menu commands') -----
classVarRefs
"Request a browser of methods that store into a chosen instance variable"
| aClass |
(aClass := self classOfSelection) ifNotNil:
[self systemNavigation browseClassVarRefs: aClass].
!
----- Method: Inspector>>contentsIsString (in category 'selecting') -----
contentsIsString
"Hacked so contents empty when deselected and = long printString when item 2"
^ (selectionIndex = 2) | (selectionIndex = 0)!
----- Method: Inspector>>context: (in category 'accessing') -----
context: ctxt
"Set the context of inspection. Currently only used by my subclass ClosureEnvInspector. The inst var is here because we do primitiveChangeClassTo: between subclasses (see inspect:) between different subclasses, but also context could be used as a general concept in all inspectors"
context := ctxt!
----- Method: Inspector>>convertToCurrentVersion:refStream: (in category 'object fileIn') -----
convertToCurrentVersion: varDict refStream: smartRefStrm
timeOfLastListUpdate ifNil: [timeOfLastListUpdate := 0].
^super convertToCurrentVersion: varDict refStream: smartRefStrm.
!
----- Method: Inspector>>copyName (in category 'menu commands') -----
copyName
"Copy the name of the current variable, so the user can paste it into the
window below and work with is. If collection, do (xxx at: 1)."
| sel aClass variableNames |
self selectionUnmodifiable
ifTrue: [^ self changed: #flash].
aClass := self object class.
variableNames := aClass allInstVarNames.
(aClass isVariable and: [selectionIndex > (variableNames size + 2)])
ifTrue: [sel := '(self basicAt: ' , (selectionIndex - (variableNames size + 2)) asString , ')']
ifFalse: [sel := variableNames at: selectionIndex - 2].
(self selection isKindOf: Collection)
ifTrue: [sel := '(' , sel , ' at: 1)'].
Clipboard clipboardText: sel asText!
----- Method: Inspector>>defsOfSelection (in category 'menu commands') -----
defsOfSelection
"Open a browser on all defining references to the selected instance variable, if that's what currently selected. "
| aClass sel |
self selectionUnmodifiable ifTrue: [^ self changed: #flash].
(aClass := self object class) isVariable ifTrue: [^ self changed: #flash].
sel := aClass allInstVarNames at: self selectionIndex - 2.
self systemNavigation browseAllStoresInto: sel from: aClass!
----- Method: Inspector>>doItReceiver (in category 'code') -----
doItReceiver
"Answer the object that should be informed of the result of evaluating a
text selection."
^object!
----- Method: Inspector>>explorePointers (in category 'menu commands') -----
explorePointers
PointerExplorer new openExplorerFor: self selection!
----- Method: Inspector>>exploreSelection (in category 'menu commands') -----
exploreSelection
self selectionIndex = 0 ifTrue: [^ self changed: #flash].
^ self selection explore!
----- Method: Inspector>>fieldList (in category 'accessing') -----
fieldList
"Answer the base field list plus an abbreviated list of indices."
object class isVariable ifFalse: [^ self baseFieldList].
^ self baseFieldList ,
(object basicSize <= (self i1 + self i2)
ifTrue: [(1 to: object basicSize)
collect: [:i | i printString]]
ifFalse: [(1 to: self i1) , (object basicSize-(self i2-1) to: object basicSize)
collect: [:i | i printString]])!
----- Method: Inspector>>fieldListMenu: (in category 'menu commands') -----
fieldListMenu: aMenu
"Arm the supplied menu with items for the field-list of the receiver"
Smalltalk isMorphic ifTrue:
[aMenu addStayUpItemSpecial].
aMenu addList: #(
('inspect (i)' inspectSelection)
('explore (I)' exploreSelection)).
self addCollectionItemsTo: aMenu.
aMenu addList: #(
-
('method refs to this inst var' referencesToSelection)
('methods storing into this inst var' defsOfSelection)
('objects pointing to this value' objectReferencesToSelection)
('chase pointers' chasePointers)
('explore pointers' explorePointers)
-
('browse full (b)' browseMethodFull)
('browse class' browseClass)
('browse hierarchy (h)' classHierarchy)
('browse protocol (p)' browseFullProtocol)
-
('inst var refs...' browseInstVarRefs)
('inst var defs...' browseInstVarDefs)
('class var refs...' classVarRefs)
('class variables' browseClassVariables)
('class refs (N)' browseClassRefs)
-
('copy name (c)' copyName)
('basic inspect' inspectBasic)).
Smalltalk isMorphic ifTrue:
[aMenu addList: #(
-
('tile for this value (t)' tearOffTile)
('viewer for this value (v)' viewerForValue))].
^ aMenu
" -
('alias for this value' aliasForValue)
('watcher for this slot' watcherForSlot)"
!
----- Method: Inspector>>i1 (in category 'accessing') -----
i1
"This is the max index shown before skipping to the
last i2 elements of very long arrays"
^ 100!
----- Method: Inspector>>i2 (in category 'accessing') -----
i2
"This is the number of elements to show at the end
of very long arrays"
^ 10!
----- Method: Inspector>>initialExtent (in category 'accessing') -----
initialExtent
"Answer the desired extent for the receiver when it is first opened on the screen. "
^ 250 @ 200!
----- Method: Inspector>>initialize (in category 'initialize-release') -----
initialize
selectionIndex := 0.
super initialize!
----- Method: Inspector>>inspect: (in category 'initialize-release') -----
inspect: anObject
"Initialize the receiver so that it is inspecting anObject. There is no current selection.
Normally the receiver will be of the correct class (as defined by anObject inspectorClass),
because it will have just been created by sedning inspect to anObject. However, the
debugger uses two embedded inspectors, which are re-targetted on the current receiver
each time the stack frame changes. The left-hand inspector in the debugger has its
class changed by the code here. Care should be taken if this method is overridden to
ensure that the overriding code calls 'super inspect: anObject', or otherwise ensures that
the class of these embedded inspectors are changed back."
| c |
c := anObject inspectorClass.
(self class ~= c and: [self class format = c format]) ifTrue: [
self primitiveChangeClassTo: c basicNew].
"Set 'object' before sending the initialize message, because some implementations
of initialize (e.g., in DictionaryInspector) require 'object' to be non-nil."
object := anObject.
self initialize!
----- Method: Inspector>>inspectBasic (in category 'menu commands') -----
inspectBasic
"Bring up a non-special inspector"
selectionIndex = 0 ifTrue: [^ object basicInspect].
self selection basicInspect!
----- Method: Inspector>>inspectElement (in category 'menu commands') -----
inspectElement
| sel selSize countString count nameStrs |
"Create and schedule an Inspector on an element of the receiver's model's currently selected collection."
self selectionIndex = 0 ifTrue: [^ self changed: #flash].
((sel := self selection) isKindOf: SequenceableCollection) ifFalse:
[(sel isKindOf: MorphExtension) ifTrue: [^ sel inspectElement].
^ sel inspect].
(selSize := sel size) == 1 ifTrue: [^ sel first inspect].
selSize <= 20 ifTrue:
[nameStrs := (1 to: selSize) asArray collect: [:ii |
ii printString, ' ', (((sel at: ii) printStringLimitedTo: 25) replaceAll: Character cr with: Character space)].
count := UIManager default chooseFrom: (nameStrs substrings) title: 'which element?'.
count = 0 ifTrue: [^ self].
^ (sel at: count) inspect].
countString := UIManager default request: 'Which element? (1 to ', selSize printString, ')' initialAnswer: '1'.
countString isEmptyOrNil ifTrue: [^ self].
count := Integer readFrom: (ReadStream on: countString).
(count > 0 and: [count <= selSize])
ifTrue: [(sel at: count) inspect]
ifFalse: [Beeper beep]!
----- Method: Inspector>>inspectSelection (in category 'menu commands') -----
inspectSelection
"Create and schedule an Inspector on the receiver's model's currently selected object."
self selectionIndex = 0 ifTrue: [^ self changed: #flash].
self selection inspect.
^ self selection!
----- Method: Inspector>>inspectorKey:from: (in category 'menu commands') -----
inspectorKey: aChar from: view
"Respond to a Command key issued while the cursor is over my field list"
aChar == $i ifTrue: [^ self selection inspect].
aChar == $I ifTrue: [^ self selection explore].
aChar == $b ifTrue: [^ self browseMethodFull].
aChar == $h ifTrue: [^ self classHierarchy].
aChar == $c ifTrue: [^ self copyName].
aChar == $p ifTrue: [^ self browseFullProtocol].
aChar == $N ifTrue: [^ self browseClassRefs].
aChar == $t ifTrue: [^ self tearOffTile].
aChar == $v ifTrue: [^ self viewerForValue].
^ self arrowKey: aChar from: view!
----- Method: Inspector>>modelWakeUpIn: (in category 'accessing') -----
modelWakeUpIn: aWindow
| newText |
self updateListsAndCodeIn: aWindow.
newText := self contentsIsString
ifTrue: [newText := self selection]
ifFalse: ["keep it short to reduce time to compute it"
self selectionPrintString ].
newText = contents ifFalse:
[contents := newText.
self changed: #contents]!
----- Method: Inspector>>noteSelectionIndex:for: (in category 'accessing') -----
noteSelectionIndex: anInteger for: aSymbol
aSymbol == #fieldList
ifTrue:
[selectionIndex := anInteger]!
----- Method: Inspector>>object (in category 'accessing') -----
object
"Answer the object being inspected by the receiver."
^object!
----- Method: Inspector>>object: (in category 'accessing') -----
object: anObject
"Set anObject to be the object being inspected by the receiver."
| oldIndex |
anObject == object
ifTrue: [self update]
ifFalse:
[oldIndex := selectionIndex <= 2 ifTrue: [selectionIndex] ifFalse: [0].
self inspect: anObject.
oldIndex := oldIndex min: self fieldList size.
self changed: #inspectObject.
oldIndex > 0
ifTrue: [self toggleIndex: oldIndex].
self changed: #fieldList.
self changed: #contents]!
----- Method: Inspector>>objectReferencesToSelection (in category 'menu commands') -----
objectReferencesToSelection
"Open a list inspector on all the objects that point to the value of the selected instance variable, if any. "
self selectionIndex == 0 ifTrue: [^ self changed: #flash].
self systemNavigation
browseAllObjectReferencesTo: self selection
except: (Array with: self object)
ifNone: [:obj | self changed: #flash].
!
----- Method: Inspector>>printStringErrorText (in category 'private') -----
printStringErrorText
| nm |
nm := self selectionIndex < 3
ifTrue: ['self']
ifFalse: [self selectedSlotName].
^ ('<error in printString: evaluate "' , nm , ' printString" to debug>') asText.!
----- Method: Inspector>>referencesToSelection (in category 'menu commands') -----
referencesToSelection
"Open a browser on all references to the selected instance variable, if that's what currently selected. 1/25/96 sw"
| aClass sel |
self selectionUnmodifiable ifTrue: [^ self changed: #flash].
(aClass := self object class) isVariable ifTrue: [^ self changed: #flash].
sel := aClass allInstVarNames at: self selectionIndex - 2.
self systemNavigation browseAllAccessesTo: sel from: aClass!
----- Method: Inspector>>replaceSelectionValue: (in category 'selecting') -----
replaceSelectionValue: anObject
"The receiver has a list of variables of its inspected object. One of these
is selected. The value of the selected variable is set to the value,
anObject."
| basicIndex si |
selectionIndex <= 2 ifTrue: [
self toggleIndex: (si := selectionIndex).
self toggleIndex: si.
^ object].
object class isVariable
ifFalse: [^ object instVarAt: selectionIndex - 2 put: anObject].
basicIndex := selectionIndex - 2 - object class instSize.
(object basicSize <= (self i1 + self i2) or: [basicIndex <= self i1])
ifTrue: [^object basicAt: basicIndex put: anObject]
ifFalse: [^object basicAt: object basicSize - (self i1 + self i2) + basicIndex
put: anObject]!
----- Method: Inspector>>selectedClass (in category 'accessing') -----
selectedClass
"Answer the class of the receiver's current selection"
self selectionUnmodifiable ifTrue: [^ object class].
^ self selection class!
----- Method: Inspector>>selectedSlotName (in category 'selecting') -----
selectedSlotName
^ self fieldList at: self selectionIndex ifAbsent: []!
----- Method: Inspector>>selection (in category 'selecting') -----
selection
"The receiver has a list of variables of its inspected object.
One of these is selected. Answer the value of the selected variable."
| basicIndex |
selectionIndex = 0 ifTrue: [^ ''].
selectionIndex = 1 ifTrue: [^ object].
selectionIndex = 2 ifTrue: [^ object longPrintStringLimitedTo: 20000].
(selectionIndex - 2) <= object class instSize
ifTrue: [^ object instVarAt: selectionIndex - 2].
basicIndex := selectionIndex - 2 - object class instSize.
(object basicSize <= (self i1 + self i2) or: [basicIndex <= self i1])
ifTrue: [^ object basicAt: basicIndex]
ifFalse: [^ object basicAt: object basicSize - (self i1 + self i2) + basicIndex]!
----- Method: Inspector>>selectionIndex (in category 'selecting') -----
selectionIndex
"The receiver has a list of variables of its inspected object. One of these
is selected. Answer the index into the list of the selected variable."
^selectionIndex!
----- Method: Inspector>>selectionPrintString (in category 'selecting') -----
selectionPrintString
| text |
selectionUpdateTime := [text := [self selection printStringLimitedTo: 5000]
on: Error
do: [text := self printStringErrorText.
text
addAttribute: TextColor red
from: 1
to: text size.
text]] timeToRun.
^ text!
----- Method: Inspector>>selectionUnmodifiable (in category 'selecting') -----
selectionUnmodifiable
"Answer if the current selected variable is modifiable via acceptance in the code pane. For most inspectors, no selection and a selection of 'self' (selectionIndex = 1) and 'all inst vars' (selectionIndex = 2) are unmodifiable"
^ selectionIndex <= 2!
----- Method: Inspector>>spawnFullProtocol (in category 'menu commands') -----
spawnFullProtocol
"Spawn a window showing full protocol for the receiver's selection"
| objectToRepresent |
objectToRepresent := self selectionIndex == 0 ifTrue: [object] ifFalse: [self selection].
ProtocolBrowser openFullProtocolForClass: objectToRepresent class!
----- Method: Inspector>>spawnProtocol (in category 'menu commands') -----
spawnProtocol
"Spawn a protocol on browser on the receiver's selection"
| objectToRepresent |
objectToRepresent := self selectionIndex == 0 ifTrue: [object] ifFalse: [self selection].
ProtocolBrowser openSubProtocolForClass: objectToRepresent class!
----- Method: Inspector>>stepAt:in: (in category 'stepping') -----
stepAt: millisecondClockValue in: aWindow
| newText |
(Preferences smartUpdating and: [(millisecondClockValue - self timeOfLastListUpdate) > 8000]) "Not more often than once every 8 seconds"
ifTrue:
[self updateListsAndCodeIn: aWindow.
timeOfLastListUpdate := millisecondClockValue].
newText := self contentsIsString
ifTrue: [self selection]
ifFalse: ["keep it short to reduce time to compute it"
self selectionPrintString ].
newText = contents ifFalse:
[contents := newText.
self changed: #contents]!
----- Method: Inspector>>stepTimeIn: (in category 'accessing') -----
stepTimeIn: aSystemWindow
^ (selectionUpdateTime ifNil: [0]) * 10 max: 1000!
----- Method: Inspector>>timeOfLastListUpdate (in category 'accessing') -----
timeOfLastListUpdate
^ timeOfLastListUpdate ifNil: [timeOfLastListUpdate := 0]!
----- Method: Inspector>>toggleIndex: (in category 'selecting') -----
toggleIndex: anInteger
"The receiver has a list of variables of its inspected object. One of these
is selected. If anInteger is the index of this variable, then deselect it.
Otherwise, make the variable whose index is anInteger be the selected
item."
selectionUpdateTime := 0.
selectionIndex = anInteger
ifTrue:
["same index, turn off selection"
selectionIndex := 0.
contents := '']
ifFalse:
["different index, new selection"
selectionIndex := anInteger.
self contentsIsString
ifTrue: [contents := self selection]
ifFalse: [contents := self selectionPrintString]].
self changed: #selection.
self changed: #contents.
self changed: #selectionIndex.!
----- Method: Inspector>>trash (in category 'accessing') -----
trash
"What goes in the bottom pane"
^ ''!
----- Method: Inspector>>trash: (in category 'accessing') -----
trash: newText
"Don't save it"
^ true!
----- Method: Inspector>>update (in category 'accessing') -----
update
"Reshow contents, assuming selected value may have changed."
selectionIndex = 0
ifFalse:
[self contentsIsString
ifTrue: [contents := self selection]
ifFalse: [contents := self selectionPrintString].
self changed: #contents.
self changed: #selection.
self changed: #selectionIndex]!
----- Method: Inspector>>viewerForValue (in category 'menu commands') -----
viewerForValue
"Open up a viewer on the value of the receiver's current selection"
| objectToRepresent |
objectToRepresent := self selectionIndex == 0 ifTrue: [object] ifFalse: [self selection].
objectToRepresent beViewed
!
----- Method: Inspector>>wantsSteps (in category 'accessing') -----
wantsSteps
^ true!
Inspector subclass: #InspectorBrowser
instanceVariableNames: 'fieldList msgList msgListIndex'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
----- Method: InspectorBrowser class>>openAsMorphOn: (in category 'as yet unclassified') -----
openAsMorphOn: anObject
"(InspectorBrowser openAsMorphOn: SystemOrganization) openInMVC"
| window inspector |
inspector := self inspect: anObject.
window := (SystemWindow labelled: anObject defaultLabelForInspector)
model: inspector.
window addMorph: (PluggableListMorph on: inspector list: #fieldList
selected: #selectionIndex changeSelected: #toggleIndex: menu: #fieldListMenu:)
frame: (0 at 0 corner: 0.3 at 0.5).
window addMorph: (PluggableTextMorph on: inspector text: #contents accept: #accept:
readSelection: nil menu: #codePaneMenu:shifted:)
frame: (0.3 at 0 corner: 1.0 at 0.5).
window addMorph: (PluggableListMorph on: inspector list: #msgList
selected: #msgListIndex changeSelected: #msgListIndex: menu: #msgListMenu:)
frame: (0 at 0.5 corner: 0.3 at 1.0).
window addMorph: (PluggableTextMorph on: inspector text: #msgText accept: #msgAccept:from:
readSelection: nil menu: #msgPaneMenu:shifted:)
frame: (0.3 at 0.5 corner: 1.0 at 1.0).
window setUpdatablePanesFrom: #(fieldList msgList).
window position: 16 at 0. "Room for scroll bar."
^ window!
----- Method: InspectorBrowser>>fieldList (in category 'as yet unclassified') -----
fieldList
fieldList ifNotNil: [^ fieldList].
^ (fieldList := super fieldList)!
----- Method: InspectorBrowser>>initialize (in category 'initialize-release') -----
initialize
super initialize.
fieldList := nil.
msgListIndex := 0.
self changed: #msgText
!
----- Method: InspectorBrowser>>inspect: (in category 'initialize-release') -----
inspect: anObject
"Initialize the receiver so that it is inspecting anObject. There is no current selection.
Overriden so that my class is not changed to 'anObject inspectorClass'."
object := anObject.
self initialize
!
----- Method: InspectorBrowser>>msgAccept:from: (in category 'as yet unclassified') -----
msgAccept: newText from: editor
| category |
category := msgListIndex = 0
ifTrue: [ClassOrganizer default]
ifFalse: [object class organization categoryOfElement: (msgList at: msgListIndex)].
^ (object class compile: newText classified: category notifying: editor) ~~ nil!
----- Method: InspectorBrowser>>msgList (in category 'messages') -----
msgList
msgList ifNotNil: [^ msgList].
^ (msgList := object class selectors asSortedArray)!
----- Method: InspectorBrowser>>msgListIndex (in category 'as yet unclassified') -----
msgListIndex
^msgListIndex!
----- Method: InspectorBrowser>>msgListIndex: (in category 'as yet unclassified') -----
msgListIndex: anInteger
"A selection has been made in the message pane"
msgListIndex := anInteger.
self changed: #msgText.!
----- Method: InspectorBrowser>>msgListMenu: (in category 'messages') -----
msgListMenu: aMenu
^ aMenu labels: 'Not yet implemented' lines: #(0) selections: #(flash)!
----- Method: InspectorBrowser>>msgPaneMenu:shifted: (in category 'as yet unclassified') -----
msgPaneMenu: aMenu shifted: shifted
^ aMenu labels:
'find...(f)
find again (g)
set search string (h)
do again (j)
undo (z)
copy (c)
cut (x)
paste (v)
do it (d)
print it (p)
inspect it (i)
accept (s)
cancel (l)'
lines: #(0 3 5 8 11)
selections: #(find findAgain setSearchString again undo copySelection cut paste doIt printIt inspectIt accept cancel)!
----- Method: InspectorBrowser>>msgText (in category 'as yet unclassified') -----
msgText
msgListIndex = 0 ifTrue: [^ nil].
^ object class sourceCodeAt: (msgList at: msgListIndex)!
----- Method: InspectorBrowser>>step (in category 'as yet unclassified') -----
step
| list fieldString msg |
(list := super fieldList) = fieldList ifFalse:
[fieldString := selectionIndex > 0 ifTrue: [fieldList at: selectionIndex] ifFalse: [nil].
fieldList := list.
selectionIndex := fieldList indexOf: fieldString ifAbsent: [0].
self changed: #fieldList.
self changed: #selectionIndex].
list := msgList. msgList := nil. "force recomputation"
list = self msgList ifFalse:
[msg := msgListIndex > 0 ifTrue: [list at: msgListIndex] ifFalse: [nil].
msgListIndex := msgList indexOf: msg ifAbsent: [0].
self changed: #msgList.
self changed: #msgListIndex].
super step!
----- Method: InspectorBrowser>>wantsSteps (in category 'as yet unclassified') -----
wantsSteps
^ true!
Inspector subclass: #OrderedCollectionInspector
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
----- Method: OrderedCollectionInspector>>fieldList (in category 'as yet unclassified') -----
fieldList
object ifNil: [ ^ OrderedCollection new].
^ self baseFieldList ,
(object size <= (self i1 + self i2)
ifTrue: [(1 to: object size)
collect: [:i | i printString]]
ifFalse: [(1 to: self i1) , (object size-(self i2-1) to: object size)
collect: [:i | i printString]])
"
OrderedCollection new inspect
(OrderedCollection newFrom: #(3 5 7 123)) inspect
(OrderedCollection newFrom: (1 to: 1000)) inspect
"!
----- Method: OrderedCollectionInspector>>replaceSelectionValue: (in category 'as yet unclassified') -----
replaceSelectionValue: anObject
"The receiver has a list of variables of its inspected object. One of these
is selected. The value of the selected variable is set to the value, anObject."
(selectionIndex - 2) <= object class instSize
ifTrue: [^ super replaceSelectionValue: anObject].
object at: self selectedObjectIndex put: anObject!
----- Method: OrderedCollectionInspector>>selectedObjectIndex (in category 'as yet unclassified') -----
selectedObjectIndex
"Answer the index of the inspectee's collection that the current selection refers to."
| basicIndex |
basicIndex := selectionIndex - 2 - object class instSize.
^ (object size <= (self i1 + self i2) or: [basicIndex <= self i1])
ifTrue: [basicIndex]
ifFalse: [object size - (self i1 + self i2) + basicIndex]!
----- Method: OrderedCollectionInspector>>selection (in category 'as yet unclassified') -----
selection
"The receiver has a list of variables of its inspected object.
One of these is selected. Answer the value of the selected variable."
(selectionIndex - 2) <= object class instSize
ifTrue: [^ super selection].
^ object at: self selectedObjectIndex!
Inspector subclass: #SetInspector
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
!SetInspector commentStamp: '<historical>' prior: 0!
A verison of the Inspector specialized for inspecting Sets. It displays the elements of the set like elements of an array. Note that the indices, being phyical locations in the hash table, are not meaningful outside of the set.!
----- Method: SetInspector>>arrayIndexForSelection (in category 'selecting') -----
arrayIndexForSelection
^ (self fieldList at: selectionIndex) asInteger!
----- Method: SetInspector>>copyName (in category 'menu commands') -----
copyName
"Copy the name of the current variable, so the user can paste it into the
window below and work with is. If collection, do (xxx at: 1)."
| sel |
self selectionIndex <= (2 + object class instSize)
ifTrue: [super copyName]
ifFalse: [sel := '(self array at: '
, (String streamContents:
[:strm | self arrayIndexForSelection storeOn: strm]) , ')'.
Clipboard clipboardText: sel asText]!
----- Method: SetInspector>>fieldList (in category 'accessing') -----
fieldList
object
ifNil: [^ Set new].
^ self baseFieldList
, (object array
withIndexCollect: [:each :i | each ifNotNil: [i printString]])
select: [:each | each notNil]!
----- Method: SetInspector>>fieldListMenu: (in category 'menu') -----
fieldListMenu: aMenu
^ aMenu labels:
'inspect
copy name
objects pointing to this value
refresh view
remove
basic inspect'
lines: #( 5 8)
selections: #(inspectSelection copyName objectReferencesToSelection update removeSelection inspectBasic)
!
----- Method: SetInspector>>removeSelection (in category 'menu') -----
removeSelection
(selectionIndex <= object class instSize) ifTrue: [^ self changed: #flash].
object remove: self selection.
selectionIndex := 0.
contents := ''.
self changed: #inspectObject.
self changed: #fieldList.
self changed: #selection.
self changed: #selectionIndex.!
----- Method: SetInspector>>replaceSelectionValue: (in category 'selecting') -----
replaceSelectionValue: anObject
^ object array at: self arrayIndexForSelection put: anObject!
----- Method: SetInspector>>selection (in category 'selecting') -----
selection
selectionIndex = 0 ifTrue: [^ ''].
selectionIndex = 1 ifTrue: [^ object].
selectionIndex = 2 ifTrue: [^ object longPrintString].
(selectionIndex - 2) <= object class instSize
ifTrue: [^ object instVarAt: selectionIndex - 2].
^ object array at: self arrayIndexForSelection!
SetInspector subclass: #WeakSetInspector
instanceVariableNames: 'flagObject'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Inspector'!
!WeakSetInspector commentStamp: '<historical>' prior: 0!
A verison of the SetInspector specialized for inspecting WeakSets. It knows about the flag object used to indicate empty locations in the hash table.!
----- Method: WeakSetInspector>>fieldList (in category 'accessing') -----
fieldList
| slotIndices |
object ifNil: [^ Set new].
"Implementation note: do not use objectArray withIndexCollect: as super
because this might collect indices in a WeakArray, leading to constantly changing fieldList
as explained at http://bugs.squeak.org/view.php?id=6812"
slotIndices := (Array new: object size) writeStream.
object array withIndexDo: [:each :i |
(each notNil and: [each ~= flagObject]) ifTrue: [slotIndices nextPut: i printString]].
^ self baseFieldList
, slotIndices contents!
----- Method: WeakSetInspector>>initialize (in category 'initialize-release') -----
initialize
super initialize.
flagObject := object instVarNamed: 'flag'. !
StringHolder subclass: #SelectorBrowser
instanceVariableNames: 'selectorIndex selectorList classListIndex classList'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser'!
----- Method: SelectorBrowser class>>prototypicalToolWindow (in category 'instance creation') -----
prototypicalToolWindow
"Answer an example of myself seen in a tool window, for the benefit of parts-launching tools"
| aWindow |
aWindow := ToolBuilder build: self new.
aWindow setLabel: 'Selector Browser'.
aWindow applyModelExtent.
^ aWindow!
----- Method: SelectorBrowser class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Method Finder' brightColor: #lightCyan pastelColor: #palePeach helpMessage: 'A tool for finding methods by giving sample arguments and values.'!
----- Method: SelectorBrowser>>buildClassListWith: (in category 'as yet unclassified') -----
buildClassListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #classList;
getIndex: #classListIndex;
setIndex: #classListIndex:;
keyPress: #arrowKey:from:.
^listSpec
!
----- Method: SelectorBrowser>>buildEditViewWith: (in category 'as yet unclassified') -----
buildEditViewWith: builder
| textSpec |
textSpec := builder pluggableInputFieldSpec new.
textSpec
model: self;
getText: #contents;
setText: #contents:notifying:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:.
^textSpec!
----- Method: SelectorBrowser>>buildExamplePaneWith: (in category 'as yet unclassified') -----
buildExamplePaneWith: builder
| textSpec |
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #byExample;
setText: #byExample:;
selection: #contentsSelection;
menu: #codePaneMenu:shifted:.
^textSpec!
----- Method: SelectorBrowser>>buildMessageListWith: (in category 'as yet unclassified') -----
buildMessageListWith: builder
| listSpec |
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #messageList;
getIndex: #messageListIndex;
setIndex: #messageListIndex:;
menu: #selectorMenu:;
keyPress: #messageListKey:from:.
^listSpec
!
----- Method: SelectorBrowser>>buildWith: (in category 'as yet unclassified') -----
buildWith: builder
"Create a Browser that lets you type part of a selector, shows a list of selectors, shows the classes of the one you chose, and spawns a full browser on it. Answer the window
SelectorBrowser new open "
| windowSpec |
selectorIndex := classListIndex := 0.
windowSpec := self buildWindowWith: builder specs: {
(0 at 0 corner: 0.5 at 0.14) -> [self buildEditViewWith: builder].
(0 at 0.14 corner: 0.5 at 0.6) -> [self buildMessageListWith: builder].
(0.5 at 0 corner: 1 at 0.6) -> [self buildClassListWith: builder].
(0 at 0.6 corner: 1 at 1) -> [self buildExamplePaneWith: builder].
}.
^builder build: windowSpec!
----- Method: SelectorBrowser>>byExample (in category 'as yet unclassified') -----
byExample
"The comment in the bottom pane"
false ifTrue: [MethodFinder methodFor: #( (4 3) 7 (0 5) 5 (5 5) 10)].
"to keep the method methodFor: from being removed from the system"
^ 'Type a fragment of a selector in the top pane. Accept it.
Or, use an example to find a method in the system. Type receiver, args, and answer in the top pane with periods between the items. 3. 4. 7
Or, in this pane, use examples to find a method in the system. Select the line of code and choose "print it".
MethodFinder methodFor: #( (4 3) 7 (0 5) 5 (5 5) 10).
This will discover (data1 + data2).
You supply inputs and answers and the system will find the method. Each inner array is a list of inputs. It contains the receiver and zero or more arguments. For Booleans and any computed arguments, use brace notation.
MethodFinder methodFor: { {1. 3}. true. {20. 10}. false}.
This will discover the expressions (data1 < data2), (data2 > data1), and many others.
MethodFinder methodFor: { {''29 Apr 1999'' asDate}. ''Thursday''.
{''30 Apr 1999'' asDate}. ''Friday'' }.
This will discover the expression (data1 weekday)
Receiver and arguments do not have to be in the right order.
See MethodFinder.verify for more examples.'!
----- Method: SelectorBrowser>>byExample: (in category 'as yet unclassified') -----
byExample: newText
"Don't save it"
^ true!
----- Method: SelectorBrowser>>classList (in category 'as yet unclassified') -----
classList
^ classList!
----- Method: SelectorBrowser>>classListIndex (in category 'as yet unclassified') -----
classListIndex
^ classListIndex!
----- Method: SelectorBrowser>>classListIndex: (in category 'as yet unclassified') -----
classListIndex: anInteger
classListIndex := anInteger.
classListIndex > 0 ifTrue:
[self dependents do:
[:dep | ((dep isKindOf: PluggableListView) and:
[dep setSelectionSelectorIs: #classListIndex:])
ifTrue: [dep controller controlTerminate]].
Browser fullOnClass: self selectedClass selector: self selectedMessageName.
"classListIndex := 0"]
!
----- Method: SelectorBrowser>>classListSelectorTitle (in category 'as yet unclassified') -----
classListSelectorTitle
^ 'Class List Menu'!
----- Method: SelectorBrowser>>contents:notifying: (in category 'as yet unclassified') -----
contents: aString notifying: aController
"Take what the user typed and find all selectors containing it"
| tokens raw sorted |
contents := aString.
classList := #(). classListIndex := 0.
selectorIndex := 0.
tokens := contents asString findTokens: ' .'.
selectorList := Cursor wait showWhile: [
tokens size = 1
ifTrue: [raw := (Symbol selectorsContaining: contents asString).
sorted := raw as: SortedCollection.
sorted sortBlock: [:x :y | x asLowercase <= y asLowercase].
sorted asArray]
ifFalse: [self quickList]]. "find selectors from a single example of data"
self changed: #messageList.
self changed: #classList.
^ true!
----- Method: SelectorBrowser>>implementors (in category 'as yet unclassified') -----
implementors
| aSelector |
(aSelector := self selectedMessageName) ifNotNil:
[self systemNavigation browseAllImplementorsOf: aSelector]!
----- Method: SelectorBrowser>>initialExtent (in category 'as yet unclassified') -----
initialExtent
^ 350 at 250
!
----- Method: SelectorBrowser>>listFromResult: (in category 'as yet unclassified') -----
listFromResult: resultOC
"ResultOC is of the form #('(data1 op data2)' '(...)'). Answer a sorted array."
(resultOC first beginsWith: 'no single method') ifTrue: [^ #()].
^ resultOC sortBy: [:a :b |
(a copyFrom: 6 to: a size) < (b copyFrom: 6 to: b size)].
!
----- Method: SelectorBrowser>>markMatchingClasses (in category 'as yet unclassified') -----
markMatchingClasses
"If an example is used, mark classes matching the example instance with an asterisk."
| unmarkedClassList firstPartOfSelector receiverString receiver |
self flag: #mref. "allows for old-fashioned style"
"Only 'example' queries can be marked."
(contents asString includes: $.) ifFalse: [^ self].
unmarkedClassList := classList copy.
"Get the receiver object of the selected statement in the message list."
firstPartOfSelector := (Scanner new scanTokens: (selectorList at: selectorIndex)) second.
receiverString := (ReadStream on: (selectorList at: selectorIndex))
upToAll: firstPartOfSelector.
receiver := Compiler evaluate: receiverString.
unmarkedClassList do: [ :classAndMethod | | class |
(classAndMethod isKindOf: MethodReference) ifTrue: [
(receiver isKindOf: classAndMethod actualClass) ifTrue: [
classAndMethod stringVersion: '*', classAndMethod stringVersionDefault.
]
] ifFalse: [
class := Compiler evaluate:
((ReadStream on: classAndMethod) upToAll: firstPartOfSelector).
(receiver isKindOf: class) ifTrue: [
classList add: '*', classAndMethod.
classList remove: classAndMethod
]
].
].
!
----- Method: SelectorBrowser>>messageList (in category 'as yet unclassified') -----
messageList
"Find all the selectors containing what the user typed in."
^ selectorList!
----- Method: SelectorBrowser>>messageListIndex (in category 'as yet unclassified') -----
messageListIndex
"Answer the index of the selected message selector."
^ selectorIndex!
----- Method: SelectorBrowser>>messageListIndex: (in category 'as yet unclassified') -----
messageListIndex: anInteger
"Set the selected message selector to be the one indexed by anInteger.
Find all classes it is in."
selectorIndex := anInteger.
selectorIndex = 0
ifTrue: [^ self].
classList := self systemNavigation allImplementorsOf: self selectedMessageName.
self markMatchingClasses.
classListIndex := 0.
self changed: #messageListIndex.
"update my selection"
self changed: #classList!
----- Method: SelectorBrowser>>messageListKey:from: (in category 'as yet unclassified') -----
messageListKey: aChar from: view
"Respond to a command key. Handle (m) and (n) here,
else defer to the StringHolder behaviour."
aChar == $m ifTrue: [^ self implementors].
aChar == $n ifTrue: [^ self senders].
super messageListKey: aChar from: view
!
----- Method: SelectorBrowser>>open (in category 'as yet unclassified') -----
open
"Create a Browser that lets you type part of a selector, shows a list of selectors,
shows the classes of the one you chose, and spwns a full browser on it.
SelectorBrowser new open
"
^ToolBuilder open: self!
----- Method: SelectorBrowser>>quickList (in category 'as yet unclassified') -----
quickList
"Compute the selectors for the single example of receiver and args, in the very top pane"
| data result resultArray newExp dataStrings mf dataObjects aa statements |
data := contents asString.
"delete t
railing period. This should be fixed in the Parser!!"
[data last isSeparator] whileTrue: [data := data allButLast].
data last = $. ifTrue: [data := data allButLast]. "Eval"
mf := MethodFinder new.
data := mf cleanInputs: data. "remove common mistakes"
dataObjects := Compiler evaluate: '{', data, '}'. "#( data1 data2 result )"
statements := (Compiler new parse: 'zort ' , data in: Object notifying: nil)
body statements select: [:each | (each isKindOf: ReturnNode) not].
dataStrings := statements collect:
[:node | String streamContents:
[:strm | (node isMessage) ifTrue: [strm nextPut: $(].
node shortPrintOn: strm.
(node isMessage) ifTrue: [strm nextPut: $)].]].
dataObjects size < 2 ifTrue: [self inform: 'If you are giving an example of receiver, \args, and result, please put periods between the parts.\Otherwise just type one selector fragment' withCRs. ^#()].
dataObjects := Array with: dataObjects allButLast with: dataObjects last. "#( (data1
data2) result )"
result := mf load: dataObjects; findMessage.
(result first beginsWith: 'no single method') ifFalse: [
aa := self testObjects: dataObjects strings: dataStrings.
dataObjects := aa second. dataStrings := aa third].
resultArray := self listFromResult: result.
resultArray isEmpty ifTrue: [self inform: result first].
dataStrings size = (dataObjects first size + 1) ifTrue:
[resultArray := resultArray collect: [:expression |
newExp := expression.
dataObjects first withIndexDo: [:lit :i |
newExp := newExp copyReplaceAll: 'data', i printString
with: (dataStrings at: i)].
newExp, ' --> ', dataStrings last]].
^ resultArray!
----- Method: SelectorBrowser>>searchResult: (in category 'as yet unclassified') -----
searchResult: anExternalSearchResult
self contents: ''.
classList := #(). classListIndex := 0.
selectorIndex := 0.
selectorList := self listFromResult: anExternalSearchResult.
self changed: #messageList.
self changed: #classList.
Smalltalk isMorphic ifTrue: [self changed: #contents.].
!
----- Method: SelectorBrowser>>selectedClass (in category 'as yet unclassified') -----
selectedClass
"Answer the currently selected class."
| pairString |
self flag: #mref. "allows for old-fashioned style"
classListIndex = 0 ifTrue: [^nil].
pairString := classList at: classListIndex.
(pairString isKindOf: MethodReference) ifTrue: [
^pairString actualClass
].
(pairString includes: $*) ifTrue: [pairString := pairString allButFirst].
MessageSet
parse: pairString
toClassAndSelector: [:cls :sel | ^ cls].!
----- Method: SelectorBrowser>>selectedClassName (in category 'as yet unclassified') -----
selectedClassName
"Answer the name of the currently selected class."
classListIndex = 0 ifTrue: [^nil].
^ self selectedClass name!
----- Method: SelectorBrowser>>selectedMessageName (in category 'as yet unclassified') -----
selectedMessageName
"Answer the name of the currently selected message."
| example tokens |
selectorIndex = 0 ifTrue: [^nil].
example := selectorList at: selectorIndex.
tokens := Scanner new scanTokens: example.
tokens size = 1 ifTrue: [^ tokens first].
tokens first == #'^' ifTrue: [^ nil].
(tokens second includes: $:) ifTrue: [^ example findSelector].
Symbol hasInterned: tokens second ifTrue: [:aSymbol | ^ aSymbol].
^ nil!
----- Method: SelectorBrowser>>selectorList: (in category 'as yet unclassified') -----
selectorList: anExternalList
self contents: ''.
classList := #(). classListIndex := 0.
selectorIndex := 0.
selectorList := anExternalList.
self changed: #messageList.
self changed: #classList.
Smalltalk isMorphic ifTrue: [self changed: #contents.].
!
----- Method: SelectorBrowser>>selectorMenu: (in category 'as yet unclassified') -----
selectorMenu: aMenu
^ aMenu labels:
'senders (n)
implementors (m)
copy selector to clipboard'
lines: #()
selections: #(senders implementors copyName)!
----- Method: SelectorBrowser>>selectorMenuTitle (in category 'as yet unclassified') -----
selectorMenuTitle
^ self selectedMessageName ifNil: ['<no selection>']!
----- Method: SelectorBrowser>>senders (in category 'as yet unclassified') -----
senders
| aSelector |
(aSelector := self selectedMessageName) ifNotNil:
[self systemNavigation browseAllCallsOn: aSelector]!
----- Method: SelectorBrowser>>testObjects:strings: (in category 'as yet unclassified') -----
testObjects: dataObjects strings: dataStrings
| dataObjs dataStrs selectors classes didUnmodifiedAnswer answerMod do ds result ddo dds |
"Try to make substitutions in the user's inputs and search for the selector again.
1 no change to answer.
2 answer Array -> OrderedCollection.
2 answer Character -> String
4 answer Symbol or String of len 1 -> Character
For each of these, try straight, and try converting args:
Character -> String
Symbol or String of len 1 -> Character
Return array with result, dataObjects, dataStrings. Don't ever do a find on the same set of data twice."
dataObjs := dataObjects. dataStrs := dataStrings.
selectors := {#asString. #first. #asOrderedCollection}.
classes := {Character. String. Array}.
didUnmodifiedAnswer := false.
selectors withIndexDo: [:ansSel :ansInd | "Modify the answer object"
answerMod := false.
do := dataObjs copyTwoLevel. ds := dataStrs copy.
(dataObjs last isKindOf: (classes at: ansInd)) ifTrue: [
((ansSel ~~ #first) or: [dataObjs last size = 1]) ifTrue: [
do at: do size put: (do last perform: ansSel). "asString"
ds at: ds size put: ds last, ' ', ansSel.
result := MethodFinder new load: do; findMessage.
(result first beginsWith: 'no single method') ifFalse: [
"found a selector!!"
^ Array with: result first with: do with: ds].
answerMod := true]].
selectors allButLast withIndexDo: [:argSel :argInd | "Modify an argument object"
"for args, no reason to do Array -> OrderedCollection. Identical protocol."
didUnmodifiedAnswer not | answerMod ifTrue: [
ddo := do copyTwoLevel. dds := ds copy.
dataObjs first withIndexDo: [:arg :ind |
(arg isKindOf: (classes at: argInd)) ifTrue: [
((argSel ~~ #first) or: [arg size = 1]) ifTrue: [
ddo first at: ind put: ((ddo first at: ind) perform: argSel). "asString"
dds at: ind put: (dds at: ind), ' ', argSel.
result := MethodFinder new load: ddo; findMessage.
(result first beginsWith: 'no single method') ifFalse: [
"found a selector!!"
^ Array with: result first with: ddo with: dds] .
didUnmodifiedAnswer not & answerMod not ifTrue: [
didUnmodifiedAnswer := true].
]]]]].
].
^ Array with: 'no single method does that function' with: dataObjs with: dataStrs!
----- Method: StringHolder>>browseAllMessages (in category '*Tools') -----
browseAllMessages
"Create and schedule a message set browser on all implementors of all the messages sent by the current method."
| aClass aName method filteredList |
(aName := self selectedMessageName) ifNotNil: [
method := (aClass := self selectedClassOrMetaClass) compiledMethodAt: aName.
filteredList := method messages reject:
[:each | #(new initialize = ) includes: each].
self systemNavigation browseAllImplementorsOfList: filteredList asSortedCollection
title: 'All messages sent in ', aClass name, '.', aName]
!
----- Method: StringHolder>>browseClass (in category '*Tools') -----
browseClass
"Open an class browser on this class and method"
self selectedClassOrMetaClass ifNotNil: [
Browser newOnClass: self selectedClassOrMetaClass
selector: self selectedMessageName]!
----- Method: StringHolder>>browseClassRefs (in category '*Tools-traits') -----
browseClassRefs
| cls |
cls := self selectedClass.
(cls notNil and: [cls isTrait not])
ifTrue: [self systemNavigation browseAllCallsOnClass: cls theNonMetaClass]
!
----- Method: StringHolder>>browseClassVarRefs (in category '*Tools-traits') -----
browseClassVarRefs
"1/17/96 sw: devolve responsibility to the class, so that the code that does the real work can be shared"
| cls |
cls := self selectedClass.
(cls notNil and: [cls isTrait not])
ifTrue: [self systemNavigation browseClassVarRefs: cls]!
----- Method: StringHolder>>browseClassVariables (in category '*Tools-traits') -----
browseClassVariables
"Browse the class variables of the selected class. 2/5/96 sw"
| cls |
cls := self selectedClass.
(cls notNil and: [cls isTrait not])
ifTrue: [self systemNavigation browseClassVariables: cls]
!
----- Method: StringHolder>>browseFullProtocol (in category '*Tools-traits') -----
browseFullProtocol
"Open up a protocol-category browser on the value of the receiver's current selection. If in mvc, an old-style protocol browser is opened instead. Someone who still uses mvc might wish to make the protocol-category-browser work there too, thanks."
| aClass |
(Smalltalk isMorphic and: [Smalltalk includesKey: #Lexicon]) ifFalse: [^ self spawnFullProtocol].
((aClass := self selectedClassOrMetaClass) notNil and: [aClass isTrait not]) ifTrue:
[(Smalltalk at: #Lexicon) new openOnClass: aClass showingSelector: self selectedMessageName]!
----- Method: StringHolder>>browseInstVarDefs (in category '*Tools-traits') -----
browseInstVarDefs
| cls |
cls := self selectedClassOrMetaClass.
(cls notNil and: [cls isTrait not])
ifTrue: [self systemNavigation browseInstVarDefs: cls]!
----- Method: StringHolder>>browseInstVarRefs (in category '*Tools-traits') -----
browseInstVarRefs
"1/26/96 sw: real work moved to class, so it can be shared"
| cls |
cls := self selectedClassOrMetaClass.
(cls notNil and: [cls isTrait not])
ifTrue: [self systemNavigation browseInstVarRefs: cls]!
----- Method: StringHolder>>browseLocalImplementors (in category '*Tools') -----
browseLocalImplementors
"Present a menu of all messages sent by the currently selected message.
Open a message set browser of all implementors of the message chosen in or below
the selected class.
Do nothing if no message is chosen."
self getSelectorAndSendQuery: #browseAllImplementorsOf:localTo:
to: self systemNavigation
with: { self selectedClass }!
----- Method: StringHolder>>browseLocalSendersOfMessages (in category '*Tools') -----
browseLocalSendersOfMessages
"Present a menu of the currently selected message, as well as all
messages sent by it. Open a message set browser of all implementors
of the message chosen in or below the selected class"
self getSelectorAndSendQuery: #browseAllCallsOn:localTo:
to: self systemNavigation
with: { self selectedClass }!
----- Method: StringHolder>>browseMessages (in category '*Tools') -----
browseMessages
"Present a menu of all messages sent by the currently selected message.
Open a message set browser of all implementors of the message chosen."
self getSelectorAndSendQuery: #browseAllImplementorsOf: to: self systemNavigation!
----- Method: StringHolder>>browseMethodFull (in category '*Tools') -----
browseMethodFull
"Create and schedule a full Browser and then select the current class and message."
| myClass |
(myClass := self selectedClassOrMetaClass) ifNotNil:
[Browser fullOnClass: myClass selector: self selectedMessageName]!
----- Method: StringHolder>>browseSendersOfMessages (in category '*Tools') -----
browseSendersOfMessages
"Present a menu of the currently selected message, as well as all messages sent by it. Open a message set browser of all senders of the selector chosen."
self getSelectorAndSendQuery: #browseAllCallsOn: to: self systemNavigation!
----- Method: StringHolder>>browseUnusedMethods (in category '*Tools') -----
browseUnusedMethods
| classes unsent messageList cls |
(cls := self selectedClass)
ifNil: [^ self].
classes := Array with: cls with: cls class.
unsent := Set new.
classes
do: [:c | unsent addAll: c selectors].
unsent := self systemNavigation allUnSentMessagesIn: unsent.
messageList := OrderedCollection new.
classes
do: [:c | (c selectors
select: [:s | unsent includes: s]) asSortedCollection
do: [:sel | messageList add: c name , ' ' , sel]].
self systemNavigation browseMessageList: messageList name: 'Unsent Methods in ' , cls name!
----- Method: StringHolder>>browseVersions (in category '*Tools') -----
browseVersions
"Create and schedule a Versions Browser, showing all versions of the
currently selected message. Answer the browser or nil."
| selector class |
self classCommentIndicated
ifTrue: [ ClassCommentVersionsBrowser browseCommentOf: self selectedClass.
^nil ].
(selector := self selectedMessageName)
ifNil:[ self inform: 'Sorry, only actual methods have retrievable versions.'. ^nil ]
ifNotNil: [
class := self selectedClassOrMetaClass.
^VersionsBrowser
browseVersionsOf: (class compiledMethodAt: selector)
class: self selectedClass
meta: class isMeta
category: (class organization categoryOfElement: selector)
selector: selector]!
----- Method: StringHolder>>classHierarchy (in category '*Tools') -----
classHierarchy
"Create and schedule a class list browser on the receiver's hierarchy."
self systemNavigation
spawnHierarchyForClass: self selectedClassOrMetaClass "OK if nil"
selector: self selectedMessageName
!
----- Method: StringHolder>>classListKey:from: (in category '*Tools') -----
classListKey: aChar from: view
"Respond to a Command key. I am a model with a list of classes and a
code pane, and I also have a listView that has a list of methods. The
view knows how to get the list and selection."
aChar == $f ifTrue: [^ self findMethod].
aChar == $r ifTrue: [^ self recent].
aChar == $h ifTrue: [^ self spawnHierarchy].
aChar == $x ifTrue: [^ self removeClass].
^ self messageListKey: aChar from: view!
----- Method: StringHolder>>copyName (in category '*Tools') -----
copyName
"Copy the current selector to the clipboard"
| selector |
(selector := self selectedMessageName) ifNotNil:
[Clipboard clipboardText: selector asString asText]!
----- Method: StringHolder>>copySelector (in category '*Tools') -----
copySelector
"Copy the selected selector to the clipboard"
| selector |
(selector := self selectedMessageName) ifNotNil:
[Clipboard clipboardText: selector asString]!
----- Method: StringHolder>>fileOutMessage (in category '*Tools') -----
fileOutMessage
"Put a description of the selected message on a file"
self selectedMessageName ifNotNil:
[Cursor write showWhile:
[self selectedClassOrMetaClass fileOutMethod: self selectedMessageName]]!
----- Method: StringHolder>>findMethodInChangeSets (in category '*Tools') -----
findMethodInChangeSets
"Find and open a changeSet containing the current method."
| aName |
(aName := self selectedMessageName) ifNotNil: [
ChangeSorter browseChangeSetsWithClass: self selectedClassOrMetaClass
selector: aName]!
----- Method: StringHolder>>inspectInstances (in category '*Tools') -----
inspectInstances
"Inspect all instances of the selected class."
| myClass |
(myClass := self selectedClassOrMetaClass) ifNotNil:
[myClass theNonMetaClass inspectAllInstances].
!
----- Method: StringHolder>>inspectSubInstances (in category '*Tools') -----
inspectSubInstances
"Inspect all instances of the selected class and all its subclasses"
| aClass |
(aClass := self selectedClassOrMetaClass) ifNotNil: [
aClass theNonMetaClass inspectSubInstances].
!
----- Method: StringHolder>>makeIsolatedCodePane (in category '*Tools') -----
makeIsolatedCodePane
| msgName |
(msgName := self selectedMessageName) ifNil: [^ Beeper beep].
MethodHolder makeIsolatedCodePaneForClass: self selectedClassOrMetaClass selector: msgName!
----- Method: StringHolder>>messageListKey:from: (in category '*Tools') -----
messageListKey: aChar from: view
"Respond to a Command key. I am a model with a code pane, and I also
have a listView that has a list of methods. The view knows how to get
the list and selection."
| sel class |
aChar == $D ifTrue: [^ self toggleDiffing].
sel := self selectedMessageName.
aChar == $m ifTrue: "These next two put up a type in if no message selected"
[^ self useSelector: sel orGetSelectorAndSendQuery: #browseAllImplementorsOf: to: self systemNavigation].
aChar == $n ifTrue:
[^ self useSelector: sel orGetSelectorAndSendQuery: #browseAllCallsOn: to: self systemNavigation].
"The following require a class selection"
(class := self selectedClassOrMetaClass) ifNil: [^ self arrowKey: aChar from: view].
aChar == $b ifTrue: [^ Browser fullOnClass: class selector: sel].
aChar == $N ifTrue: [^ self browseClassRefs].
aChar == $i ifTrue: [^ self methodHierarchy].
aChar == $h ifTrue: [^ self classHierarchy].
aChar == $p ifTrue: [^ self browseFullProtocol].
"The following require a method selection"
sel ifNotNil:
[aChar == $o ifTrue: [^ self fileOutMessage].
aChar == $c ifTrue: [^ self copySelector].
aChar == $v ifTrue: [^ self browseVersions].
aChar == $O ifTrue: [^ self openSingleMessageBrowser].
aChar == $x ifTrue: [^ self removeMessage]].
^ self arrowKey: aChar from: view!
----- Method: StringHolder>>messageListSelectorTitle (in category '*Tools') -----
messageListSelectorTitle
| selector aString aStamp aSize |
(selector := self selectedMessageName)
ifNil:
[aSize := self messageList size.
^ (aSize == 0 ifTrue: ['no'] ifFalse: [aSize printString]), ' message', (aSize == 1 ifTrue: [''] ifFalse: ['s'])]
ifNotNil:
[Preferences timeStampsInMenuTitles
ifFalse: [^ nil].
aString := selector truncateWithElipsisTo: 28.
^ (aStamp := self timeStamp) size > 0
ifTrue:
[aString, String cr, aStamp]
ifFalse:
[aString]]!
----- Method: StringHolder>>methodHierarchy (in category '*Tools') -----
methodHierarchy
"Create and schedule a method browser on the hierarchy of implementors."
self systemNavigation
methodHierarchyBrowserForClass: self selectedClassOrMetaClass
selector: self selectedMessageName
!
----- Method: StringHolder>>offerDurableMenuFrom:shifted: (in category '*Tools') -----
offerDurableMenuFrom: menuRetriever shifted: aBoolean
"Pop up (morphic only) a menu whose target is the receiver and whose contents are provided by sending the menuRetriever to the receiver. The menuRetriever takes two arguments: a menu, and a boolean representing the shift state; put a stay-up item at the top of the menu."
| aMenu |
aMenu := MenuMorph new defaultTarget: self.
aMenu addStayUpItem.
self perform: menuRetriever with: aMenu with: aBoolean.
aMenu popUpInWorld!
----- Method: StringHolder>>offerMenuFrom:shifted: (in category '*Tools') -----
offerMenuFrom: menuRetriever shifted: aBoolean
"Pop up, in morphic or mvc as the case may be, a menu whose target is the receiver and whose contents are provided by sending the menuRetriever to the receiver. The menuRetriever takes two arguments: a menu, and a boolean representing the shift state."
| aMenu |
Smalltalk isMorphic
ifTrue:
[aMenu := MenuMorph new defaultTarget: self.
self perform: menuRetriever with: aMenu with: aBoolean.
aMenu popUpInWorld]
ifFalse:
[aMenu := CustomMenu new.
self perform: menuRetriever with: aMenu with: aBoolean.
aMenu invokeOn: self]!
----- Method: StringHolder>>openSingleMessageBrowser (in category '*Tools') -----
openSingleMessageBrowser
| msgName mr |
"Create and schedule a message list browser populated only by the currently selected message"
(msgName := self selectedMessageName) ifNil: [^ self].
mr := MethodReference new
setStandardClass: self selectedClassOrMetaClass
methodSymbol: msgName.
self systemNavigation
browseMessageList: (Array with: mr)
name: mr asStringOrText
autoSelect: nil!
----- Method: StringHolder>>packageListKey:from: (in category '*Tools') -----
packageListKey: aChar from: view
"Respond to a Command key in the package pane in the PackageBrowser"
aChar == $f ifTrue: [^ self findClass].
^ self classListKey: aChar from: view
!
----- Method: StringHolder>>printOutMessage (in category '*Tools') -----
printOutMessage
"Write a file with the text of the selected message, for printing by a web browser"
self selectedMessageName ifNotNil: [
self selectedClassOrMetaClass fileOutMethod: self selectedMessageName
asHtml: true]!
----- Method: StringHolder>>removeFromCurrentChanges (in category '*Tools') -----
removeFromCurrentChanges
"Tell the changes mgr to forget that the current msg was changed."
ChangeSet current removeSelectorChanges: self selectedMessageName
class: self selectedClassOrMetaClass.
self changed: #annotation!
----- Method: StringHolder>>revertAndForget (in category '*Tools') -----
revertAndForget
"Revert to the previous version of the current method, and tell the changes mgr to forget that it was ever changed. Danger!! Use only if you really know what you're doing!!"
self okToChange ifFalse: [^ self].
self revertToPreviousVersion.
self removeFromCurrentChanges.
self contentsChanged
!
----- Method: StringHolder>>revertToPreviousVersion (in category '*Tools') -----
revertToPreviousVersion
"Revert to the previous version of the current method"
| aClass aSelector changeRecords |
self okToChange ifFalse: [^ self].
aClass := self selectedClassOrMetaClass.
aClass ifNil: [^ self changed: #flash].
aSelector := self selectedMessageName.
changeRecords := aClass changeRecordsAt: aSelector.
(changeRecords == nil or: [changeRecords size <= 1]) ifTrue: [self changed: #flash. ^ Beeper beep].
changeRecords second fileIn.
self contentsChanged
!
----- Method: StringHolder>>selectMessageAndEvaluate: (in category '*Tools') -----
selectMessageAndEvaluate: aBlock
"Allow the user to choose one selector, chosen from the currently selected message's selector, as well as those of all messages sent by it, and evaluate aBlock on behalf of chosen selector. If there is only one possible choice, simply make it; if there are multiple choices, put up a menu, and evaluate aBlock on behalf of the the chosen selector, doing nothing if the user declines to choose any"
| selector method messages |
(selector := self selectedMessageName) ifNil: [^ self].
method := (self selectedClassOrMetaClass ifNil: [^ self])
compiledMethodAt: selector
ifAbsent: [].
(method isNil or: [(messages := method messages) size == 0])
ifTrue: [^ aBlock value: selector].
(messages size == 1 and: [messages includes: selector])
ifTrue:
[^ aBlock value: selector]. "If only one item, there is no choice"
self systemNavigation
showMenuOf: messages
withFirstItem: selector
ifChosenDo: [:sel | aBlock value: sel]!
----- Method: StringHolder>>systemCatListKey:from: (in category '*Tools') -----
systemCatListKey: aChar from: view
"Respond to a Command key. I am a model with a code pane, and I also have a listView that has a list of methods. The view knows how to get the list and selection."
aChar == $f ifTrue: [^ self findClass].
aChar == $x ifTrue: [^ self removeSystemCategory].
^ self classListKey: aChar from: view!
----- Method: StringHolder>>timeStamp (in category '*Tools') -----
timeStamp
"Answer the time stamp for the chosen class and method, if any, else an empty string"
| selector aMethod |
(selector := self selectedMessageName) ifNotNil:
[self selectedClassOrMetaClass
ifNil:
[^ String new]
ifNotNil:
[aMethod := self selectedClassOrMetaClass compiledMethodAt: selector ifAbsent: [nil].
aMethod ifNotNil: [^ Utilities timeStampForMethod: aMethod]]].
^ String new!
StringHolder subclass: #SyntaxError
instanceVariableNames: 'class selector category debugger doitFlag'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Debugger'!
!SyntaxError commentStamp: '<historical>' prior: 0!
I represent syntax error report for syntax errors encountered when filing in class descriptions from a non-interactive source such as an external file. As a StringHolder, the string to be viewed is the method code or expression containing the error.
The user may fix the error and accept the method to continue the fileIn.
!
----- Method: SyntaxError class>>buildMVCViewOn: (in category 'instance creation') -----
buildMVCViewOn: aSyntaxError
"Answer an MVC view on the given SyntaxError."
| topView aListView aCodeView |
topView := StandardSystemView new
model: aSyntaxError;
label: 'Syntax Error';
minimumSize: 380 at 220.
aListView := PluggableListView on: aSyntaxError
list: #list
selected: #listIndex
changeSelected: nil
menu: #listMenu:.
aListView window: (0 at 0 extent: 380 at 20).
topView addSubView: aListView.
aCodeView := PluggableTextView on: aSyntaxError
text: #contents
accept: #contents:notifying:
readSelection: #contentsSelection
menu: #codePaneMenu:shifted:.
aCodeView window: (0 at 0 extent: 380 at 200).
topView addSubView: aCodeView below: aListView.
^ topView
!
----- Method: SyntaxError class>>buildMorphicViewOn: (in category 'instance creation') -----
buildMorphicViewOn: aSyntaxError
"Answer an Morphic view on the given SyntaxError."
| window |
window := (SystemWindow labelled: 'Syntax Error') model: aSyntaxError.
window addMorph: (PluggableListMorph on: aSyntaxError list: #list
selected: #listIndex changeSelected: nil menu: #listMenu:)
frame: (0 at 0 corner: 1 at 0.15).
window addMorph: (PluggableTextMorph on: aSyntaxError text: #contents
accept: #contents:notifying: readSelection: #contentsSelection
menu: #codePaneMenu:shifted:)
frame: (0 at 0.15 corner: 1 at 1).
^ window openInWorldExtent: 380 at 220!
----- Method: SyntaxError class>>errorInClass:withCode:doitFlag: (in category 'instance creation') -----
errorInClass: aClass withCode: codeString doitFlag: doit
"Open a view whose model is a syntax error. The error occurred when trying to add the given method code to the given class."
self open:
(self new setClass: aClass
code: codeString
debugger: (Debugger context: thisContext)
doitFlag: doit).
!
----- Method: SyntaxError class>>open: (in category 'instance creation') -----
open: aSyntaxError
"Answer a standard system view whose model is an instance of me."
| topView |
<primitive: 19> "Simulation guard"
Smalltalk isMorphic
ifTrue:
[self buildMorphicViewOn: aSyntaxError.
Project spawnNewProcessIfThisIsUI: Processor activeProcess.
^ Processor activeProcess suspend].
topView := self buildMVCViewOn: aSyntaxError.
topView controller openNoTerminateDisplayAt: Display extent // 2.
Cursor normal show.
Processor activeProcess suspend.
!
----- Method: SyntaxError>>category: (in category 'initialization') -----
category: aSymbol
"Record the message category of method being compiled. This is used when the user corrects the error and accepts."
category := aSymbol.
!
----- Method: SyntaxError>>contents:notifying: (in category 'other') -----
contents: aString notifying: aController
"Compile the code in aString and notify aController of any errors. If there are no errors, then automatically proceed."
doitFlag
ifTrue: [Compiler new evaluate: aString in: nil to: nil
notifying: aController ifFail: [^ false]]
ifFalse: [(class compile: aString classified: category
notifying: aController) ifNil: [^ false]].
aController hasUnacceptedEdits: false.
self proceed!
----- Method: SyntaxError>>debug (in category 'menu') -----
debug
"Show the stack of the process leading to this syntax editor, typically showing the stack of the compiler as called from fileIn."
debugger openFullNoSuspendLabel: 'Stack of the Syntax Error'.
Smalltalk isMorphic ifFalse: [Processor terminateActive].
!
----- Method: SyntaxError>>list (in category 'message list') -----
list
"Answer an array of one element made up of the class name, message category, and message selector in which the syntax error was found. This is the single item in the message list of a view/browser on the receiver."
selector ifNil: [^ Array with: (class name, ' ', category, ' ', '<none>')].
^ Array with: (class name, ' ', category, ' ', selector)
!
----- Method: SyntaxError>>listIndex (in category 'message list') -----
listIndex
"There is always exactly one element in my list and it is always selected."
^ 1
!
----- Method: SyntaxError>>listMenu: (in category 'menu') -----
listMenu: aMenu
^ aMenu labels:
'proceed
debug calling process
browse full'
lines: #()
selections: #(proceed debug browseMethodFull)
!
----- Method: SyntaxError>>notify:at:in: (in category 'other') -----
notify: error at: location in: source
"Open a syntax error view, inserting the given error message into the given source at the given location. This message is sent to the 'requestor' when the parser or compiler finds a syntax error."
| aClass aString |
aClass := thisContext sender receiver encoder classEncoding.
aString :=
source contents
copyReplaceFrom: location
to: location - 1
with: error.
self setClass: aClass
code: aString
debugger: (Debugger context: thisContext)
doitFlag: false.
self class open: self.
!
----- Method: SyntaxError>>proceed (in category 'menu') -----
proceed
"The user has has edited and presumably fixed the syntax error and the filein can now proceed."
debugger proceed: self topView.
!
----- Method: SyntaxError>>selectedClass (in category 'text menu support') -----
selectedClass
"Answer the class in which the syntax error occurred."
^ class
!
----- Method: SyntaxError>>selectedClassOrMetaClass (in category 'text menu support') -----
selectedClassOrMetaClass
"Answer the class of the method being compiled."
^ class
!
----- Method: SyntaxError>>selectedMessageName (in category 'text menu support') -----
selectedMessageName
"Answer the selector of the method being compiled."
^ selector
!
----- Method: SyntaxError>>setClass:code:debugger:doitFlag: (in category 'initialization') -----
setClass: aClass code: aString debugger: aDebugger doitFlag: flag
| types printables badChar |
class := aClass.
debugger := aDebugger.
selector := aClass parserClass new parseSelector: aString.
types := Scanner classPool at: #TypeTable. "dictionary"
printables := '!!@#$%&*-_=+<>{}?/\,·£¢§¶ªº
Úæگ׿«»`~`' asSet.
badChar := aString detect: [:aChar | (types at: aChar asciiValue ifAbsent: [#xLetter]) == #xBinary and: [
(printables includes: aChar) not]] ifNone: [nil].
contents := badChar
ifNil: [aString]
ifNotNil: ['<<<This string contains a character (ascii value ',
badChar asciiValue printString,
') that is not normally used in code>>> ', aString].
category ifNil: [category := aClass organization categoryOfElement: selector].
category ifNil: [category := ClassOrganizer default].
doitFlag := flag!
StringHolder subclass: #Workspace
instanceVariableNames: 'bindings acceptDroppedMorphs acceptAction mustDeclareVariables'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Base'!
!Workspace commentStamp: 'ls 10/14/2003 12:13' prior: 0!
A Workspace is a text area plus a lot of support for executable code. It is a great place to execute top-level commands to compute something useful, and it is a great place to develop bits of a program before those bits get put into class methods.
To open a new workspace, execute:
Workspace open
A workspace can have its own variables, called "workspace variables", to hold intermediate results. For example, if you type into a workspace "x := 5" and do-it, then later you could type in "y := x * 2" and y would become 10.
Additionally, in Morphic, a workspace can gain access to morphs that are on the screen. If acceptDroppedMorphss is turned on, then whenever a morph is dropped on the workspace, a variable will be created which references that morph. This functionality is toggled with the window-wide menu of a workspace.
The instance variables of this class are:
bindings - holds the workspace variables for this workspace
acceptDroppedMorphss - whether dropped morphs should create new variables!
----- Method: Workspace class>>prototypicalToolWindow (in category 'instance creation') -----
prototypicalToolWindow
"Answer an example of myself seen in a tool window, for the benefit of parts-launching tools"
| aWorkspace |
aWorkspace := ToolBuilder build: self new.
^ aWorkspace!
----- Method: Workspace>>acceptAction (in category 'accessing') -----
acceptAction
^acceptAction!
----- Method: Workspace>>acceptAction: (in category 'accessing') -----
acceptAction: anAction
acceptAction := anAction.!
----- Method: Workspace>>acceptContents: (in category 'accessing') -----
acceptContents: aString
acceptAction ifNotNil:[acceptAction value: aString].
^super acceptContents: aString.!
----- Method: Workspace>>acceptDroppedMorphsWording (in category 'as yet unclassified') -----
acceptDroppedMorphsWording
^ self acceptsDroppingMorphForReference
ifTrue: ['<yes> create textual references to dropped morphs' translated]
ifFalse: ['<no> create textual references to dropped morphs' translated]
!
----- Method: Workspace>>acceptDroppingMorph:event:inMorph: (in category 'drag and drop') -----
acceptDroppingMorph: dropee event: evt inMorph: targetMorph
"Return the dropee to its old position, and add a reference to it at the cursor point."
| bindingName externalName |
externalName := dropee externalName.
externalName := externalName isOctetString
ifTrue: [externalName] ifFalse: ['a' , externalName].
bindingName := externalName translateToLowercase, dropee identityHash printString.
targetMorph correctSelectionWithString: bindingName, ' '.
(self bindingOf: bindingName) value: dropee.
dropee rejectDropMorphEvent: evt.
^ true "success"
!
----- Method: Workspace>>acceptsDroppingMorphForReference (in category 'drag and drop') -----
acceptsDroppingMorphForReference
^ acceptDroppedMorphs
!
----- Method: Workspace>>acceptsDroppingMorphForReference: (in category 'drag and drop') -----
acceptsDroppingMorphForReference: trueFalse
acceptDroppedMorphs := trueFalse
!
----- Method: Workspace>>addModelItemsToWindowMenu: (in category 'menu commands') -----
addModelItemsToWindowMenu: aMenu
aMenu addLine.
aMenu
add: 'save contents to file...'
target: self
action: #saveContentsInFile.
aMenu
add: 'reset variables'
target: self
action: #initializeBindings.
aMenu
addUpdating: #mustDeclareVariableWording
target: self
action: #toggleVariableDeclarationMode.
aMenu
addUpdating: #acceptDroppedMorphsWording
target: self
action: #toggleDroppingMorphForReference!
----- Method: Workspace>>appendContentsOfFile (in category 'menu commands') -----
appendContentsOfFile
"Prompt for a file, and if one is obtained, append its contents to the contents of the receiver. Caution: as currently implemented this abandons any custom style information previously in the workspace. Someone should fix this. Also, for best results you should accept the contents of the workspace before requesting this."
| aFileStream |
(aFileStream := FileList2 modalFileSelector) ifNil: [^ self].
contents := (contents ifNil: ['']) asString, aFileStream contentsOfEntireFile.
aFileStream close.
self changed: #contents!
----- Method: Workspace>>bindingOf: (in category 'binding') -----
bindingOf: aString
mustDeclareVariables ifTrue: [^ nil].
"I want to have workspace that force the user to declare
variables. Still subclasses may want to do something else"
bindings isNil
ifTrue: [self initializeBindings].
(bindings includesKey: aString)
ifFalse: [bindings at: aString put: nil].
^ bindings associationAt: aString!
----- Method: Workspace>>convertToCurrentVersion:refStream: (in category 'object fileIn') -----
convertToCurrentVersion: varDict refStream: smartRefStrm
acceptDroppedMorphs ifNil: [acceptDroppedMorphs := false].
^super convertToCurrentVersion: varDict refStream: smartRefStrm.
!
----- Method: Workspace>>initialExtent (in category 'initialize-release') -----
initialExtent
"Start small. Window aspect ratio is 5 sqrt::1 . Good asthetics. -wiz"
^ 447 at 200!
----- Method: Workspace>>initialize (in category 'initialize-release') -----
initialize
super initialize.
acceptDroppedMorphs := false.
mustDeclareVariables := false!
----- Method: Workspace>>initializeBindings (in category 'binding') -----
initializeBindings
bindings := Dictionary new!
----- Method: Workspace>>mustDeclareVariableWording (in category 'variable declarations') -----
mustDeclareVariableWording
^ mustDeclareVariables not
ifTrue: ['<yes> automatically create variable declaration' translated]
ifFalse: ['<no> automatically create variable declaration' translated]!
----- Method: Workspace>>mustDeclareVariables: (in category 'accessing') -----
mustDeclareVariables: aBoolean
mustDeclareVariables := aBoolean!
----- Method: Workspace>>saveContentsInFile (in category 'as yet unclassified') -----
saveContentsInFile
"A bit of a hack to pass along this message to the controller or morph. (Possibly this Workspace menu item could be deleted, since it's now in the text menu.)"
| textMorph textView |
textMorph := self dependents detect: [:dep | dep isKindOf: PluggableTextMorph] ifNone: [nil].
textMorph notNil ifTrue: [^ textMorph saveContentsInFile].
textView := self dependents detect: [:dep | dep isKindOf: PluggableTextView] ifNone: [nil].
textView notNil ifTrue: [^ textView controller saveContentsInFile].
!
----- Method: Workspace>>setBindings: (in category 'accessing') -----
setBindings: aDictionary
"Sets the Workspace to use the specified dictionary as its namespace"
bindings := aDictionary.
!
----- Method: Workspace>>toggleDroppingMorphForReference (in category 'drag and drop') -----
toggleDroppingMorphForReference
acceptDroppedMorphs := acceptDroppedMorphs not.
!
----- Method: Workspace>>toggleVariableDeclarationMode (in category 'variable declarations') -----
toggleVariableDeclarationMode
mustDeclareVariables := mustDeclareVariables not!
----- Method: Workspace>>wantsAnnotationPane (in category 'accessing') -----
wantsAnnotationPane
"We have nothing to annotate"
^false!
----- Method: Workspace>>wantsDroppedMorph:event:inMorph: (in category 'drag and drop') -----
wantsDroppedMorph: dropee event: evt inMorph: target
^ acceptDroppedMorphs
!
----- Method: Workspace>>wantsOptionalButtons (in category 'accessing') -----
wantsOptionalButtons
"We have no optional buttons (yet)"
^false!
----- Method: TheWorldMenu>>browseRecentLog (in category '*Tools') -----
browseRecentLog
ChangeList browseRecentLog!
----- Method: TheWorldMenu>>inspectWorldModel (in category '*Tools') -----
inspectWorldModel
| insp |
insp := InspectorBrowser openOn: myWorld model.
myWorld addMorph: insp; startStepping: insp!
----- Method: TheWorldMenu>>openChangeSorter1 (in category '*Tools') -----
openChangeSorter1
ToolBuilder open: ChangeSorter new!
----- Method: TheWorldMenu>>openChangeSorter2 (in category '*Tools') -----
openChangeSorter2
ToolBuilder open: DualChangeSorter new!
----- Method: TheWorldMenu>>openMessageNames (in category '*Tools') -----
openMessageNames
"Bring a MessageNames tool to the front"
MessageNames openMessageNames!
----- Method: TheWorldMenu>>openProcessBrowser (in category '*Tools') -----
openProcessBrowser
ProcessBrowser open!
----- Method: TheWorldMenu>>openSelectorBrowser (in category '*Tools') -----
openSelectorBrowser
ToolBuilder open: SelectorBrowser new!
----- Method: TheWorldMenu>>startThenBrowseMessageTally (in category '*Tools') -----
startThenBrowseMessageTally
(self confirm: 'MessageTally will start now,
and stop when the cursor goes
to the top of the screen')
ifTrue: [TimeProfileBrowser
onBlock: [[Sensor peekMousePt y > 10]
whileTrue: [World doOneCycle]]]!
----- Method: BlockClosure>>timeProfile (in category '*Tools') -----
timeProfile
^TimeProfileBrowser onBlock: self!
----- Method: CompiledMethod>>explorerContents (in category '*Tools-Inspector') -----
explorerContents
"(CompiledMethod compiledMethodAt: #explorerContents) explore"
^Array streamContents:
[:s| | tokens |
tokens := Scanner new scanTokens: (self headerDescription readStream skipTo: $"; upTo: $").
s nextPut: (ObjectExplorerWrapper
with: ((0 to: tokens size by: 2) collect:
[:i| i = 0 ifTrue: [self header] ifFalse: [{tokens at: i - 1. tokens at: i}]])
name: 'header'
model: self).
(1 to: self numLiterals) do:
[:key|
s nextPut: (ObjectExplorerWrapper
with: (self literalAt: key)
name: ('literal', key printString contractTo: 32)
model: self)].
self isQuick
ifTrue: [s nextPut: (ObjectExplorerWrapper
with: self symbolic
name: #symbolic
model: self)]
ifFalse:
[self symbolicLinesDo:
[:pc :line|
pc <= 1
ifTrue:
[s nextPut: (ObjectExplorerWrapper
with: line
name: 'pragma'
model: self)]
ifFalse:
[s nextPut: (ObjectExplorerWrapper
with: line
name: pc printString
model: self)]]].
"should be self numLiterals + 1 * Smalltalk wordSize + 1"
self endPC + 1
to: self basicSize
do: [:key|
s nextPut: (ObjectExplorerWrapper
with: (self basicAt: key)
name: key printString
model: self)]]!
----- Method: CompiledMethod>>inspectorClass (in category '*Tools-Inspector') -----
inspectorClass
"Answer the class of the inspector to be used on the receiver. Called by inspect;
use basicInspect to get a normal (less useful) type of inspector."
^ CompiledMethodInspector!
AppRegistry subclass: #SystemBrowser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Base'!
!SystemBrowser commentStamp: '<historical>' prior: 0!
This is the AppRegistry class for class browsing!
----- Method: SystemBrowser class>>addRegistryMenuItemsTo:inAccountOf: (in category 'registration') -----
addRegistryMenuItemsTo: aMenu inAccountOf: aBrowser
"Add some useful options related Browser registry to the
browsers windows menu"
aMenu addLine;
add: 'Register this Browser as default'
target: [self default: aBrowser class]
action: #value;
add: 'Choose new default Browser'
target: self
action: #askForDefault!
----- Method: SystemBrowser class>>defaultOpenBrowser (in category 'instance creation') -----
defaultOpenBrowser
^self default openBrowser!
----- Method: SystemBrowser class>>initialize (in category 'class initialization') -----
initialize
| pref |
pref := Preferences preferenceAt: #browserShowsPackagePane.
Preferences
addPreference: #browserShowsPackagePane
categories: pref categoryList
default: pref defaultValue
balloonHelp: pref helpString
projectLocal: pref localToProject
changeInformee: self
changeSelector: #packagePanePreferenceChanged
!
----- Method: SystemBrowser class>>packagePanePreferenceChanged (in category 'events') -----
packagePanePreferenceChanged
| theOtherOne |
self registeredClasses size = 2
ifTrue: [theOtherOne := (self registeredClasses copyWithout: PackagePaneBrowser) first]
ifFalse: [theOtherOne := nil].
(Preferences valueOfFlag: #browserShowsPackagePane ifAbsent: [false])
ifTrue: [self default: PackagePaneBrowser]
ifFalse: [self default: theOtherOne].
SystemNavigation default browserClass: self default.!
----- Method: SystemBrowser class>>unload (in category 'initialize-release') -----
unload
| pref |
pref := Preferences preferenceAt: #browserShowsPackagePane.
Preferences
addPreference: #browserShowsPackagePane
categories: pref categoryList
default: pref defaultValue
balloonHelp: pref helpString
projectLocal: pref localToProject
changeInformee: nil
changeSelector: nil
!
ElementCategory subclass: #ChangeSetCategory
instanceVariableNames: 'membershipSelector'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!ChangeSetCategory commentStamp: '<historical>' prior: 0!
A ChangeSetCategory represents a list of change sets to be shown in a ChangeSorter. It computes whether a given change set is in the list by sending its membershipSelector to ChangeSorter (i.e. the class object) with the change set as message argument.!
----- Method: ChangeSetCategory>>acceptsManualAdditions (in category 'queries') -----
acceptsManualAdditions
"Answer whether the user is allowed manually to manipulate the contents of the change-set-category."
^ false!
----- Method: ChangeSetCategory>>changeSetList (in category 'queries') -----
changeSetList
"Answer the list of change-set names in the category"
| aChangeSet |
self reconstituteList.
keysInOrder size == 0 ifTrue:
["don't tolerate emptiness, because ChangeSorters gag when they have no change-set selected"
aChangeSet := ChangesOrganizer assuredChangeSetNamed: 'New Changes'.
self elementAt: aChangeSet name put: aChangeSet].
^ keysInOrder reversed!
----- Method: ChangeSetCategory>>defaultChangeSetToShow (in category 'miscellaneous') -----
defaultChangeSetToShow
"Answer the name of a change-set to show"
^ ChangeSet current!
----- Method: ChangeSetCategory>>fileOutAllChangeSets (in category 'services') -----
fileOutAllChangeSets
"File out all the nonempty change sets in the current category, suppressing the checks for slips that might otherwise ensue. Obtain user confirmation before undertaking this possibly prodigious task."
| aList |
aList := self elementsInOrder select:
[:aChangeSet | aChangeSet isEmpty not].
aList size == 0 ifTrue: [^ self inform: 'sorry, all the change sets in this category are empty'].
(self confirm: 'This will result in filing out ', aList size printString, ' change set(s)
Are you certain you want to do this?') ifFalse: [^ self].
Preferences setFlag: #checkForSlips toValue: false during:
[ChangesOrganizer fileOutChangeSetsNamed: (aList collect: [:m | m name]) asSortedArray]!
----- Method: ChangeSetCategory>>fillAggregateChangeSet (in category 'services') -----
fillAggregateChangeSet
"Create a change-set named Aggregate and pour into it all the changes in all the change-sets of the currently-selected category"
| aggChangeSet |
aggChangeSet := ChangesOrganizer assuredChangeSetNamed: #Aggregate.
aggChangeSet clear.
aggChangeSet setPreambleToSay: '"Change Set: Aggregate
Created at ', Time now printString, ' on ', Date today printString, ' by combining all the changes in all the change sets in the category ', categoryName printString, '"'.
(self elementsInOrder copyWithout: aggChangeSet) do:
[:aChangeSet | aggChangeSet assimilateAllChangesFoundIn: aChangeSet].
Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]
!
----- Method: ChangeSetCategory>>hasChangeForClassName:selector:otherThanIn: (in category 'queries') -----
hasChangeForClassName: aClassName selector: aSelector otherThanIn: excludedChangeSet
"Answer whether any change set in this category, other than the excluded one, has a change marked for the given class and selector"
self elementsInOrder do:
[:aChangeSet |
(aChangeSet ~~ excludedChangeSet and:
[((aChangeSet methodChangesAtClass: aClassName) includesKey: aSelector)]) ifTrue: [^ true]].
^ false!
----- Method: ChangeSetCategory>>includesChangeSet: (in category 'queries') -----
includesChangeSet: aChangeSet
"Answer whether the receiver includes aChangeSet in its retrieval list"
^ ChangesOrganizer perform: membershipSelector with: aChangeSet!
----- Method: ChangeSetCategory>>membershipSelector: (in category 'initialization') -----
membershipSelector: aSelector
"Set the membershipSelector"
membershipSelector := aSelector!
----- Method: ChangeSetCategory>>reconstituteList (in category 'miscellaneous') -----
reconstituteList
"Clear out the receiver's elements and rebuild them"
| newMembers |
"First determine newMembers and check if they have not changed..."
newMembers := ChangesOrganizer allChangeSets select:
[:aChangeSet | ChangesOrganizer perform: membershipSelector with: aChangeSet].
(newMembers collect: [:cs | cs name]) = keysInOrder ifTrue: [^ self "all current"].
"Things have changed. Need to recompute the whole category"
self clear.
newMembers do:
[:aChangeSet | self fasterElementAt: aChangeSet name asSymbol put: aChangeSet]
!
ChangeSetCategory subclass: #ChangeSetCategoryWithParameters
instanceVariableNames: 'parameters'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
----- Method: ChangeSetCategoryWithParameters>>acceptsManualAdditions (in category 'as yet unclassified') -----
acceptsManualAdditions
"Answer whether the user is allowed manually to manipulate the contents of the change-set-category."
^ true!
----- Method: ChangeSetCategoryWithParameters>>addChangeSet: (in category 'as yet unclassified') -----
addChangeSet: aChangeSet
self inform: 'sorry, you can''t do that'!
----- Method: ChangeSetCategoryWithParameters>>includesChangeSet: (in category 'as yet unclassified') -----
includesChangeSet: aChangeSet
"Answer whether the receiver includes aChangeSet in its retrieval list"
^ ChangesOrganizer perform: membershipSelector withArguments: { aChangeSet } , parameters!
----- Method: ChangeSetCategoryWithParameters>>parameters: (in category 'as yet unclassified') -----
parameters: anArray
parameters := anArray!
----- Method: ChangeSetCategoryWithParameters>>reconstituteList (in category 'as yet unclassified') -----
reconstituteList
"Clear out the receiver's elements and rebuild them"
| newMembers |
"First determine newMembers and check if they have not changed..."
newMembers := ChangesOrganizer allChangeSets select:
[:aChangeSet | ChangesOrganizer perform: membershipSelector withArguments: { aChangeSet }, parameters].
(newMembers collect: [:cs | cs name]) = keysInOrder ifTrue: [^ self "all current"].
"Things have changed. Need to recompute the whole category"
self clear.
newMembers do:
[:aChangeSet | self fasterElementAt: aChangeSet name asSymbol put: aChangeSet]!
ChangeSetCategory subclass: #StaticChangeSetCategory
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!StaticChangeSetCategory commentStamp: '<historical>' prior: 0!
StaticChangeSetCategory is a user-defined change-set category that has in it only those change sets specifically placed there.!
----- Method: StaticChangeSetCategory>>acceptsManualAdditions (in category 'queries') -----
acceptsManualAdditions
"Answer whether the user is allowed manually to manipulate the contents of the change-set-category."
^ true!
----- Method: StaticChangeSetCategory>>addChangeSet: (in category 'add') -----
addChangeSet: aChangeSet
"Add the change set manually"
self elementAt: aChangeSet name put: aChangeSet!
----- Method: StaticChangeSetCategory>>includesChangeSet: (in category 'queries') -----
includesChangeSet: aChangeSet
"Answer whether the receiver includes aChangeSet in its retrieval list"
^ elementDictionary includesKey: aChangeSet name!
----- Method: StaticChangeSetCategory>>reconstituteList (in category 'updating') -----
reconstituteList
"Reformulate the list. Here, since we have a manually-maintained list, at this juncture we only make sure change-set-names are still up to date, and we purge moribund elements"
| survivors |
survivors := elementDictionary select: [:aChangeSet | aChangeSet isMoribund not].
self clear.
(survivors asSortedCollection: [:a :b | a name <= b name]) reverseDo:
[:aChangeSet | self addChangeSet: aChangeSet]!
----- Method: FloatArray>>inspectorClass (in category '*Tools-Inspector') -----
inspectorClass
"Answer the class of the inspector to be used on the receiver. Called by inspect;
use basicInspect to get a normal (less useful) type of inspector."
^OrderedCollectionInspector!
----- Method: OrderedCollection>>inspectorClass (in category '*Tools-Inspector') -----
inspectorClass
"Answer the class of the inspector to be used on the receiver. Called by inspect;
use basicInspect to get a normal (less useful) type of inspector."
^OrderedCollectionInspector!
Model subclass: #CPUWatcher
instanceVariableNames: 'tally watcher threshold'
classVariableNames: 'CurrentCPUWatcher'
poolDictionaries: ''
category: 'Tools-Process Browser'!
!CPUWatcher commentStamp: '<historical>' prior: 0!
CPUWatcher implements a simple runaway process monitoring tool
that will suspend a process that is taking up too much of Squeak's
time and allow user interaction. By default it watches for a Process that
is taking more than 80% of the time; this threshold can be changed.
CPUWatcher can also be used to show cpu percentages for each process
from within the ProcessBrowser.
CPUWatcher startMonitoring. "process period 20 seconds, sample rate 100 msec"
CPUWatcher current monitorProcessPeriod: 10 sampleRate: 20.
CPUWatcher current threshold: 0.5. "change from 80% to 50%"
CPUWatcher stopMonitoring.
!
----- Method: CPUWatcher class>>current (in category 'as yet unclassified') -----
current
^CurrentCPUWatcher
!
----- Method: CPUWatcher class>>currentWatcherProcess (in category 'as yet unclassified') -----
currentWatcherProcess
^CurrentCPUWatcher ifNotNil: [ CurrentCPUWatcher watcherProcess ]
!
----- Method: CPUWatcher class>>dumpTallyOnTranscript (in category 'as yet unclassified') -----
dumpTallyOnTranscript
self current ifNotNil: [
ProcessBrowser dumpTallyOnTranscript: self current tally
]!
----- Method: CPUWatcher class>>initialize (in category 'as yet unclassified') -----
initialize
"CPUWatcher initialize"
Smalltalk addToStartUpList: self.
Smalltalk addToShutDownList: self.!
----- Method: CPUWatcher class>>isMonitoring (in category 'as yet unclassified') -----
isMonitoring
^CurrentCPUWatcher notNil and: [ CurrentCPUWatcher isMonitoring ]
!
----- Method: CPUWatcher class>>monitorPreferenceChanged (in category 'as yet unclassified') -----
monitorPreferenceChanged
Preferences cpuWatcherEnabled
ifTrue: [ self startMonitoring ]
ifFalse: [ self stopMonitoring ]!
----- Method: CPUWatcher class>>shutDown (in category 'as yet unclassified') -----
shutDown
self stopMonitoring.!
----- Method: CPUWatcher class>>startMonitoring (in category 'as yet unclassified') -----
startMonitoring
"CPUWatcher startMonitoring"
^self startMonitoringPeriod: 20 rate: 100 threshold: 0.8!
----- Method: CPUWatcher class>>startMonitoringPeriod:rate:threshold: (in category 'as yet unclassified') -----
startMonitoringPeriod: pd rate: rt threshold: th
"CPUWatcher startMonitoring"
CurrentCPUWatcher ifNotNil: [ ^CurrentCPUWatcher startMonitoring. ].
CurrentCPUWatcher := (self new)
monitorProcessPeriod: pd sampleRate: rt;
threshold: th;
yourself.
^CurrentCPUWatcher
!
----- Method: CPUWatcher class>>startUp (in category 'as yet unclassified') -----
startUp
self monitorPreferenceChanged.!
----- Method: CPUWatcher class>>stopMonitoring (in category 'as yet unclassified') -----
stopMonitoring
"CPUWatcher stopMonitoring"
CurrentCPUWatcher ifNotNil: [ CurrentCPUWatcher stopMonitoring. ].
CurrentCPUWatcher := nil.
!
----- Method: CPUWatcher>>catchThePig: (in category 'porcine capture') -----
catchThePig: aProcess
| rules |
"nickname, allow-stop, allow-debug"
rules := ProcessBrowser nameAndRulesFor: aProcess.
(ProcessBrowser isUIProcess: aProcess)
ifTrue: [ "aProcess debugWithTitle: 'Interrupted from the CPUWatcher'." ]
ifFalse: [ rules second ifFalse: [ ^self ].
ProcessBrowser suspendProcess: aProcess.
self openWindowForSuspendedProcess: aProcess ]
!
----- Method: CPUWatcher>>debugProcess: (in category 'process operations') -----
debugProcess: aProcess
| uiPriority oldPriority |
uiPriority := Processor activeProcess priority.
aProcess priority >= uiPriority ifTrue: [
oldPriority := ProcessBrowser setProcess: aProcess toPriority: uiPriority - 1
].
ProcessBrowser debugProcess: aProcess.!
----- Method: CPUWatcher>>debugProcess:fromMenu: (in category 'process operations') -----
debugProcess: aProcess fromMenu: aMenuMorph
aMenuMorph delete.
self debugProcess: aProcess.!
----- Method: CPUWatcher>>findThePig (in category 'porcine capture') -----
findThePig
"tally has been updated. Look at it to see if there is a bad process.
This runs at a very high priority, so make it fast"
| countAndProcess |
countAndProcess := tally sortedCounts first.
(countAndProcess key / tally size > self threshold) ifTrue: [ | proc |
proc := countAndProcess value.
proc == Processor backgroundProcess ifTrue: [ ^self ]. "idle process? OK"
self catchThePig: proc
].
!
----- Method: CPUWatcher>>isMonitoring (in category 'accessing') -----
isMonitoring
^watcher notNil!
----- Method: CPUWatcher>>monitorProcessPeriod:sampleRate: (in category 'startup-shutdown') -----
monitorProcessPeriod: secs sampleRate: msecs
self stopMonitoring.
watcher := [ [ | promise |
promise := Processor tallyCPUUsageFor: secs every: msecs.
tally := promise value.
promise := nil.
self findThePig.
] repeat ] forkAt: Processor highestPriority.
Processor yield !
----- Method: CPUWatcher>>openMVCWindowForSuspendedProcess: (in category 'porcine capture') -----
openMVCWindowForSuspendedProcess: aProcess
ProcessBrowser new openAsMVC.!
----- Method: CPUWatcher>>openMorphicWindowForSuspendedProcess: (in category 'porcine capture') -----
openMorphicWindowForSuspendedProcess: aProcess
| menu rules |
menu := MenuMorph new.
"nickname allow-stop allow-debug"
rules := ProcessBrowser nameAndRulesFor: aProcess.
menu add: 'Dismiss this menu' target: menu selector: #delete; addLine.
menu add: 'Open Process Browser' target: ProcessBrowser selector: #open.
menu add: 'Resume'
target: self
selector: #resumeProcess:fromMenu:
argumentList: { aProcess . menu }.
menu add: 'Terminate'
target: self
selector: #terminateProcess:fromMenu:
argumentList: { aProcess . menu }.
rules third ifTrue: [
menu add: 'Debug at a lower priority'
target: self
selector: #debugProcess:fromMenu:
argumentList: { aProcess . menu }.
].
menu addTitle: aProcess identityHash asString,
' ', rules first,
' is taking too much time and has been suspended.
What do you want to do with it?'.
menu stayUp: true.
menu popUpInWorld
!
----- Method: CPUWatcher>>openWindowForSuspendedProcess: (in category 'porcine capture') -----
openWindowForSuspendedProcess: aProcess
Smalltalk isMorphic
ifTrue: [ WorldState addDeferredUIMessage: [ self openMorphicWindowForSuspendedProcess: aProcess ] ]
ifFalse: [ [ self openMVCWindowForSuspendedProcess: aProcess ] forkAt: Processor userSchedulingPriority ]
!
----- Method: CPUWatcher>>resumeProcess:fromMenu: (in category 'process operations') -----
resumeProcess: aProcess fromMenu: aMenuMorph
aMenuMorph delete.
ProcessBrowser resumeProcess: aProcess.!
----- Method: CPUWatcher>>startMonitoring (in category 'startup-shutdown') -----
startMonitoring
self
monitorProcessPeriod: 20 sampleRate: 100!
----- Method: CPUWatcher>>stopMonitoring (in category 'startup-shutdown') -----
stopMonitoring
watcher ifNotNil: [
ProcessBrowser terminateProcess: watcher.
watcher := nil.
]!
----- Method: CPUWatcher>>tally (in category 'accessing') -----
tally
^tally copy!
----- Method: CPUWatcher>>terminateProcess:fromMenu: (in category 'process operations') -----
terminateProcess: aProcess fromMenu: aMenuMorph
aMenuMorph delete.
ProcessBrowser terminateProcess: aProcess.!
----- Method: CPUWatcher>>threshold (in category 'accessing') -----
threshold
"What fraction of the time can a process be the active process before we stop it?"
^threshold!
----- Method: CPUWatcher>>threshold: (in category 'accessing') -----
threshold: thresh
"What fraction of the time can a process be the active process before we stop it?"
threshold := (thresh max: 0.02) min: 1.0!
----- Method: CPUWatcher>>watcherProcess (in category 'accessing') -----
watcherProcess
^watcher!
Model subclass: #DualChangeSorter
instanceVariableNames: 'leftCngSorter rightCngSorter'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Changes'!
!DualChangeSorter commentStamp: '<historical>' prior: 0!
This class presents a view of a two change sets at once, and supports copying changes between change sets.
!
----- Method: DualChangeSorter class>>open (in category 'opening') -----
open
"Open a new instance of the receiver's class"
self new open!
----- Method: DualChangeSorter class>>prototypicalToolWindow (in category 'opening') -----
prototypicalToolWindow
"Answer an example of myself seen in a tool window, for the benefit of parts-launching tools"
^ ToolBuilder build: self new!
----- Method: DualChangeSorter class>>registerInFlapsRegistry (in category 'class initialization') -----
registerInFlapsRegistry
"Register the receiver in the system's flaps registry"
self environment
at: #Flaps
ifPresent: [:cl | cl registerQuad: #(DualChangeSorter prototypicalToolWindow 'Change Sorter' 'Shows two change sets side by side')
forFlapNamed: 'Tools']!
----- Method: DualChangeSorter class>>unload (in category 'class initialization') -----
unload
"Unload the receiver from global registries"
self environment at: #Flaps ifPresent: [:cl |
cl unregisterQuadsWithReceiver: self] !
----- Method: DualChangeSorter class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification
"Answer a WindowColorSpec object that declares my preference"
^ WindowColorSpec classSymbol: self name wording: 'Dual Change Sorter' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'Lets you view and manipulate two change sets concurrently.'!
----- Method: DualChangeSorter>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
| windowSpec window |
leftCngSorter := ChangeSorter new myChangeSet: ChangeSet current.
leftCngSorter parent: self.
rightCngSorter := ChangeSorter new myChangeSet:
ChangeSorter secondaryChangeSet.
rightCngSorter parent: self.
windowSpec := builder pluggableWindowSpec new.
windowSpec model: self.
windowSpec label: 'Change Sorter'.
windowSpec children: OrderedCollection new.
windowSpec label: #labelString.
leftCngSorter buildWith: builder in: windowSpec rect: (0 at 0 extent: 0.5 at 1).
rightCngSorter buildWith: builder in: windowSpec rect: (0.5 at 0 extent: 0.5 at 1).
window := builder build: windowSpec.
leftCngSorter addDependent: window. "so it will get changed: #relabel"
rightCngSorter addDependent: window. "so it will get changed: #relabel"
^window!
----- Method: DualChangeSorter>>isLeftSide: (in category 'other') -----
isLeftSide: theOne
"Which side am I?"
^ theOne == leftCngSorter!
----- Method: DualChangeSorter>>labelString (in category 'other') -----
labelString
"The window label"
| leftName rightName changesName |
leftName := leftCngSorter changeSetCategory categoryName.
rightName := rightCngSorter changeSetCategory categoryName.
changesName := 'Changes go to "', ChangeSet current name, '"'.
^ ((leftName ~~ #All) or: [rightName ~~ #All])
ifTrue:
['(', leftName, ') - ', changesName, ' - (', rightName, ')']
ifFalse:
[changesName]!
----- Method: DualChangeSorter>>modelWakeUp (in category 'other') -----
modelWakeUp
"A window with me as model is being entered. Make sure I am up-to-date with the changeSets."
"Dumb way"
leftCngSorter canDiscardEdits
ifTrue: [leftCngSorter update] "does both"
ifFalse: [rightCngSorter update].
!
----- Method: DualChangeSorter>>okToChange (in category 'initialization') -----
okToChange
^ leftCngSorter okToChange & rightCngSorter okToChange!
----- Method: DualChangeSorter>>open (in category 'initialization') -----
open
^ToolBuilder open: self!
----- Method: DualChangeSorter>>other: (in category 'other') -----
other: theOne
"Return the other side's ChangeSorter"
^ theOne == leftCngSorter
ifTrue: [rightCngSorter]
ifFalse: [leftCngSorter]!
----- Method: DualChangeSorter>>release (in category 'initialization') -----
release
leftCngSorter release.
rightCngSorter release.!
----- Method: Model>>addItem: (in category '*Tools') -----
addItem: classAndMethod
"Make a linked message list and put this method in it"
| list |
self flag: #mref. "classAndMethod is a String"
MessageSet
parse: classAndMethod
toClassAndSelector: [ :class :sel |
class ifNil: [^self].
list := OrderedCollection with: (
MethodReference new
setClass: class
methodSymbol: sel
stringVersion: classAndMethod
).
MessageSet
openMessageList: list
name: 'Linked by HyperText'.
]
!
Model subclass: #PointerFinder
instanceVariableNames: 'goal parents toDo toDoNext hasGemStone pointerList objectList parentsSize todoSize depth pointerListIndex'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Debugger'!
!PointerFinder commentStamp: '<historical>' prior: 0!
I can search for reasons why a certain object isn't garbage collected. I'm a quick port of a VisualWorks program written by Hans-Martin Mosner. Call me as shown below. I'll search for a path from a global variable to the given object, presenting it in a small morphic UI.
Examples:
PointerFinder on: self currentHand
PointerFinder on: StandardSystemView someInstance
Now, let's see why this image contains more HandMorphs as expected...
HandMorph allInstancesDo: [:e | PointerFinder on: e]!
----- Method: PointerFinder class>>on: (in category 'instance creation') -----
on: anObject
^ self new goal: anObject; search; open!
----- Method: PointerFinder class>>pointersTo: (in category 'utilities') -----
pointersTo: anObject
"Find all occurrences in the system of pointers to the argument anObject."
"(PointerFinder pointersTo: Browser) inspect."
^ self pointersTo: anObject except: #()
!
----- Method: PointerFinder class>>pointersTo:except: (in category 'utilities') -----
pointersTo: anObject except: objectsToExclude
"Find all occurrences in the system of pointers to the argument anObject.
Remove objects in the exclusion list from the results."
| results anObj lastObj |
Smalltalk garbageCollect.
"big collection shouldn't grow, so it's contents array is always the same"
results := OrderedCollection new: 1000.
"allObjectsDo: is expanded inline to keep spurious
method and block contexts out of the results"
anObj := self someObject.
lastObj := Object new.
[lastObj == anObj] whileFalse: [
anObj isInMemory ifTrue: [
(anObj pointsTo: anObject) ifTrue: [
"exclude the results collector and contexts in call chain"
((anObj ~~ results collector) and:
[(anObj ~~ objectsToExclude) and:
[(anObj ~~ thisContext) and:
[(anObj ~~ thisContext sender) and:
[anObj ~~ thisContext sender sender]]]])
ifTrue: [ results add: anObj ].
]].
anObj := anObj nextObject.
].
objectsToExclude do: [ :obj | results removeAllSuchThat: [ :el | el == obj]].
^ results asArray
!
----- Method: PointerFinder class>>pointersToItem:of: (in category 'utilities') -----
pointersToItem: index of: anArray
"Find all occurrences in the system of pointers to the given element of the given array.
This is useful for tracing up a pointer chain from an inspector on the results of a previous call of pointersTo:. To find out who points to the second element of the results, one would evaluate:
PointerFinder pointersToItem: 2 of: self
in the inspector."
^ self pointersTo: (anArray at: index) except: (Array with: anArray)!
----- Method: PointerFinder>>arrowKey:from: (in category 'morphic ui') -----
arrowKey: key from: aController
key = $i ifTrue: [^ self inspectObject].
^ super arrowKey: key from: aController!
----- Method: PointerFinder>>buildList (in category 'application') -----
buildList
| list obj parent object key |
list := OrderedCollection new.
obj := goal.
[list addFirst: obj.
obj := parents at: obj ifAbsent: [].
obj == nil] whileFalse.
list removeFirst.
parent := Smalltalk.
objectList := OrderedCollection new.
pointerList := OrderedCollection new.
[list isEmpty]
whileFalse:
[object := list removeFirst.
key := nil.
(parent isKindOf: Dictionary)
ifTrue: [list size >= 2
ifTrue:
[key := parent keyAtValue: list second ifAbsent: [].
key == nil
ifFalse:
[object := list removeFirst; removeFirst.
pointerList add: key printString , ' -> ' , object class name]]].
key == nil
ifTrue:
[parent class == object ifTrue: [key := 'CLASS'].
key == nil ifTrue: [1 to: parent class instSize do: [:i | key == nil ifTrue: [(parent instVarAt: i)
== object ifTrue: [key := parent class allInstVarNames at: i]]]].
key == nil ifTrue: [1 to: parent basicSize do: [:i | key == nil ifTrue: [(parent basicAt: i)
== object ifTrue: [key := i printString]]]].
key == nil ifTrue: [(parent isMorph and: [object isKindOf: Array]) ifTrue: [key := 'submorphs?']].
key == nil ifTrue: [(parent isCompiledMethod and: [object isVariableBinding]) ifTrue: [key := 'literals?']].
key == nil ifTrue: [key := '???'].
pointerList add: key , ': ' , object class name].
objectList add: object.
parent := object]!
----- Method: PointerFinder>>follow:from: (in category 'application') -----
follow: anObject from: parentObject
anObject == goal
ifTrue:
[parents at: anObject put: parentObject.
^ true].
anObject isLiteral ifTrue: [^ false].
"Remove this after switching to new CompiledMethod format --bf 2/12/2006"
(anObject class isPointers or: [anObject isCompiledMethod]) ifFalse: [^ false].
anObject class isWeak ifTrue: [^ false].
(parents includesKey: anObject)
ifTrue: [^ false].
parents at: anObject put: parentObject.
toDoNext add: anObject.
^ false!
----- Method: PointerFinder>>followObject: (in category 'application') -----
followObject: anObject
(self follow: anObject class from: anObject)
ifTrue: [^ true].
"Remove this after switching to new CompiledMethod format --bf 2/12/2006"
anObject isCompiledMethod ifTrue: [
1 to: anObject numLiterals do:
[:i |
(self follow: (anObject literalAt: i) from: anObject)
ifTrue: [^ true]].
^false].
1 to: anObject class instSize do:
[:i |
(self follow: (anObject instVarAt: i) from: anObject)
ifTrue: [^ true]].
1 to: anObject basicSize do:
[:i |
(self follow: (anObject basicAt: i) from: anObject)
ifTrue: [^ true]].
^ false!
----- Method: PointerFinder>>goal: (in category 'application') -----
goal: anObject
goal := anObject!
----- Method: PointerFinder>>initialExtent (in category 'morphic ui') -----
initialExtent
^ 300 @ 300!
----- Method: PointerFinder>>initialize (in category 'application') -----
initialize
parents := IdentityDictionary new: 20000.
parents at: Smalltalk put: nil.
parents at: Processor put: nil.
parents at: self put: nil.
toDo := OrderedCollection new: 5000.
toDo add: Smalltalk.
toDoNext := OrderedCollection new: 5000!
----- Method: PointerFinder>>inspectObject (in category 'morphic ui') -----
inspectObject
pointerListIndex = 0 ifTrue: [^ Beeper beep].
(objectList at: pointerListIndex) inspect!
----- Method: PointerFinder>>isLiteral (in category 'application') -----
isLiteral
"Horrible hack to omit other Pointer Finders from scanning."
^ true!
----- Method: PointerFinder>>isSelfEvaluating (in category 'self evaluating') -----
isSelfEvaluating
^ false!
----- Method: PointerFinder>>menu:shifted: (in category 'morphic ui') -----
menu: aMenu shifted: shifted
^ MenuMorph new
defaultTarget: self;
add: 'Inspect (i)' action: #inspectObject;
balloonTextForLastItem: 'Live long and prosper!!';
addLine;
add: 'Search again' action: #searchAgain;
balloonTextForLastItem: 'Search again\for the same object' withCRs;
yourself!
----- Method: PointerFinder>>open (in category 'morphic ui') -----
open
| window list |
window := (SystemWindow labelled: 'Pointer Finder')
model: self.
list := PluggableListMorph new
doubleClickSelector: #inspectObject;
on: self
list: #pointerList
selected: #pointerListIndex
changeSelected: #pointerListIndex:
menu: #menu:shifted:
keystroke: #arrowKey:from:.
"For doubleClick to work best disable autoDeselect"
list autoDeselect: false.
window addMorph: list frame: (0 at 0 extent: 1 at 1).
list color: Color lightMagenta.
window openInWorld!
----- Method: PointerFinder>>perform:orSendTo: (in category 'morphic ui') -----
perform: selector orSendTo: otherTarget
selector == #inspectObject ifTrue: [^ self inspectObject].
selector == #searchAgain ifTrue: [^ self searchAgain].
^ super perform: selector orSendTo: otherTarget!
----- Method: PointerFinder>>pointerList (in category 'morphic ui') -----
pointerList
^ pointerList asArray!
----- Method: PointerFinder>>pointerListIndex (in category 'morphic ui') -----
pointerListIndex
^ pointerListIndex ifNil: [0]!
----- Method: PointerFinder>>pointerListIndex: (in category 'morphic ui') -----
pointerListIndex: anInteger
pointerListIndex := anInteger.
self changed: #pointerListIndex!
----- Method: PointerFinder>>search (in category 'application') -----
search
Smalltalk garbageCollect.
self initialize.
Cursor wait showWhile: [
[[toDo isEmpty or: [self followObject: toDo removeFirst]] whileFalse.
toDo isEmpty and: [toDoNext isEmpty not]]
whileTrue:
[toDo := toDoNext.
toDoNext := OrderedCollection new: 5000]].
self buildList!
----- Method: PointerFinder>>searchAgain (in category 'morphic ui') -----
searchAgain
self pointerListIndex: 0.
self search.
self changed: #pointerList!
----- Method: PointerFinder>>update (in category 'application') -----
update
('done: ' , parents size asString , ' todo: ' , toDo size asString , ' ') displayAt: 0 at 0!
Model subclass: #ProcessBrowser
instanceVariableNames: 'selectedProcess selectedContext methodText processList processListIndex stackList stackListIndex sourceMap selectedClass selectedSelector searchString autoUpdateProcess lastUpdate startedCPUWatcher'
classVariableNames: 'Browsers SuspendedProcesses WellKnownProcesses'
poolDictionaries: ''
category: 'Tools-Process Browser'!
!ProcessBrowser commentStamp: '<historical>' prior: 0!
Change Set: ProcessBrowser
Date: 14 March 2000
Author: Ned Konz
email: ned at bike-nomad.com
This is distributed under the Squeak License.
Added 14 March:
CPUWatcher integration
automatically start and stop CPUWatcher
added CPUWatcher to process list menu
Added 29 October:
MVC version
2.8, 2.7 compatibility
rearranged menus
added pointer inspection and chasing
added suspend/resume
recognized more well-known processes
misc. bug fixes
Added 26 October: highlight pc in source code
Added 27 October: added 'signal semaphore'
added 'inspect receiver', 'explore receiver', 'message tally' to stack list menu
added 'find context', 'next context' to process list menu
added 'change priority' and 'debug' choices to process list menu
27 October mods by Bob Arning:
alters process display in Ned's ProcessBrowser to
- show process priority
- drop 'a Process in' that appears on each line
- show in priority order
- prettier names for known processes
- fix to Utilities to forget update downloading process when it ends (1 less dead
process)
- correct stack dump for the active process
!
----- Method: ProcessBrowser class>>debugProcess: (in category 'process control') -----
debugProcess: aProcess
self resumeProcess: aProcess.
aProcess debugWithTitle: 'Interrupted from the Process Browser'.
!
----- Method: ProcessBrowser class>>dumpTallyOnTranscript: (in category 'CPU utilization') -----
dumpTallyOnTranscript: tally
"tally is from ProcessorScheduler>>tallyCPUUsageFor:
Dumps lines with percentage of time, hash of process, and a friendly name"
tally sortedCounts do: [ :assoc | | procName |
procName := (self nameAndRulesFor: assoc value) first.
Transcript print: (((assoc key / tally size) * 100.0) roundTo: 1);
nextPutAll: '% ';
print: assoc value identityHash; space;
nextPutAll: procName;
cr.
].
Transcript flush.!
----- Method: ProcessBrowser class>>initialize (in category 'class initialization') -----
initialize
"ProcessBrowser initialize"
Browsers ifNil: [ Browsers := WeakSet new ].
SuspendedProcesses ifNil: [ SuspendedProcesses := IdentityDictionary new ].
Smalltalk addToStartUpList: self.
Smalltalk addToShutDownList: self.
self registerInFlapsRegistry.
self registerWellKnownProcesses!
----- Method: ProcessBrowser class>>isUIProcess: (in category 'process control') -----
isUIProcess: aProcess
^aProcess == (Smalltalk isMorphic
ifTrue: [ Project uiProcess ]
ifFalse: [ ScheduledControllers activeControllerProcess ])!
----- Method: ProcessBrowser class>>nameAndRulesFor: (in category 'process control') -----
nameAndRulesFor: aProcess
"Answer a nickname and two flags: allow-stop, and allow-debug"
^ [aProcess caseOf: WellKnownProcesses
otherwise:
[(aProcess priority = Processor timingPriority
and: [aProcess suspendedContext receiver == Delay])
ifTrue: [{'the timer interrupt watcher'. false. false}]
ifFalse: [{aProcess suspendedContext asString. true. true}]]]
ifError: [:err :rcvr | {aProcess suspendedContext asString. true. true}]!
----- Method: ProcessBrowser class>>open (in category 'instance creation') -----
open
"ProcessBrowser open"
"Create and schedule a ProcessBrowser."
Smalltalk garbageCollect.
^ToolBuilder open: self new!
----- Method: ProcessBrowser class>>prototypicalToolWindow (in category 'instance creation') -----
prototypicalToolWindow
"Answer a window representing a prototypical instance of the receiver"
^ self new asPrototypeInWindow!
----- Method: ProcessBrowser class>>registerInFlapsRegistry (in category 'class initialization') -----
registerInFlapsRegistry
"Register the receiver in the system's flaps registry"
self environment
at: #Flaps
ifPresent: [:cl | cl registerQuad: #(ProcessBrowser prototypicalToolWindow 'Processes' 'A Process Browser shows you all the running processes')
forFlapNamed: 'Tools'.]!
----- Method: ProcessBrowser class>>registerWellKnownProcess:label:allowStop:allowDebug: (in category 'process control') -----
registerWellKnownProcess: aBlockForProcess label: nickname allowStop: allowStop allowDebug: allowDebug
"Add an entry to the registry of well known processes. aBlockForProcess
evaluates to a known process to be identified by nickname, and allowStop
and allowDebug are flags controlling allowable actions for this process
in the browser."
WellKnownProcesses add: aBlockForProcess->[{nickname . allowStop . allowDebug}]!
----- Method: ProcessBrowser class>>registerWellKnownProcesses (in category 'class initialization') -----
registerWellKnownProcesses
"Associate each well-known process with a nickname and two flags: allow-stop, and allow-debug.
Additional processes may be added to this list as required"
WellKnownProcesses := OrderedCollection new.
self registerWellKnownProcess: []
label: 'no process'
allowStop: false
allowDebug: false.
self registerWellKnownProcess: [Smalltalk lowSpaceWatcherProcess]
label: 'the low space watcher'
allowStop: false
allowDebug: false.
self registerWellKnownProcess: [WeakArray runningFinalizationProcess]
label: 'the WeakArray finalization process'
allowStop: false
allowDebug: false.
self registerWellKnownProcess: [Processor activeProcess]
label: 'the UI process'
allowStop: false
allowDebug: true.
self registerWellKnownProcess: [Processor backgroundProcess]
label: 'the idle process'
allowStop: false
allowDebug: false.
self registerWellKnownProcess: [Sensor interruptWatcherProcess]
label: 'the user interrupt watcher'
allowStop: false
allowDebug: false.
self registerWellKnownProcess: [Sensor eventTicklerProcess]
label: 'the event tickler'
allowStop: false
allowDebug: false.
self registerWellKnownProcess: [Project uiProcess]
label: 'the inactive Morphic UI process'
allowStop: false
allowDebug: false.
self registerWellKnownProcess:
[Smalltalk at: #SoundPlayer ifPresent: [:sp | sp playerProcess]]
label: 'the Sound Player'
allowStop: false
allowDebug: false.
self registerWellKnownProcess:
[ScheduledControllers ifNotNil: [ScheduledControllers activeControllerProcess]]
label: 'the inactive MVC controller process'
allowStop: false
allowDebug: true.
self registerWellKnownProcess:
[Smalltalk at: #CPUWatcher ifPresent: [:cw | cw currentWatcherProcess]]
label: 'the CPUWatcher'
allowStop: false
allowDebug: false
!
----- Method: ProcessBrowser class>>resumeProcess: (in category 'process control') -----
resumeProcess: aProcess
| priority |
priority := self suspendedProcesses
removeKey: aProcess
ifAbsent: [aProcess priority].
aProcess priority: priority.
aProcess suspendedContext ifNotNil: [ aProcess resume ]
!
----- Method: ProcessBrowser class>>setProcess:toPriority: (in category 'process control') -----
setProcess: aProcess toPriority: priority
| oldPriority |
oldPriority := self suspendedProcesses at: aProcess ifAbsent: [ ].
oldPriority ifNotNil: [ self suspendedProcesses at: aProcess put: priority ].
aProcess priority: priority.
^oldPriority!
----- Method: ProcessBrowser class>>shutDown (in category 'system startup') -----
shutDown
Browsers do: [ :ea | ea isAutoUpdating ifTrue: [ ea pauseAutoUpdate ]]!
----- Method: ProcessBrowser class>>startUp (in category 'system startup') -----
startUp
Browsers
do: [:ea | | paused | ea isAutoUpdatingPaused
ifTrue: [ea initialize; startAutoUpdate]]!
----- Method: ProcessBrowser class>>suspendProcess: (in category 'process control') -----
suspendProcess: aProcess
| priority |
priority := aProcess priority.
self suspendedProcesses at: aProcess put: priority.
"Need to take the priority down below the caller's
so that it can keep control after signaling the Semaphore"
(aProcess suspendingList isKindOf: Semaphore)
ifTrue: [aProcess priority: Processor lowestPriority.
aProcess suspendingList signal].
[aProcess suspend]
on: Error
do: [:ex | self suspendedProcesses removeKey: aProcess].
aProcess priority: priority.
!
----- Method: ProcessBrowser class>>suspendedProcesses (in category 'process control') -----
suspendedProcesses
"Answer a collection of processes that my instances have suspended.
This is so that they don't get garbage collected."
^ SuspendedProcesses
ifNil: [SuspendedProcesses := IdentityDictionary new]!
----- Method: ProcessBrowser class>>tallyCPUUsageFor: (in category 'CPU utilization') -----
tallyCPUUsageFor: seconds
"Compute CPU usage using a 10-msec sample for the given number of seconds,
then dump the usage statistics on the Transcript. The UI is free to continue, meanwhile"
"ProcessBrowser tallyCPUUsageFor: 10"
^self tallyCPUUsageFor: seconds every: 10!
----- Method: ProcessBrowser class>>tallyCPUUsageFor:every: (in category 'CPU utilization') -----
tallyCPUUsageFor: seconds every: msec
"Compute CPU usage using a msec millisecond sample for the given number of seconds,
then dump the usage statistics on the Transcript. The UI is free to continue, meanwhile"
"ProcessBrowser tallyCPUUsageFor: 10 every: 100"
| promise |
promise := Processor tallyCPUUsageFor: seconds every: msec.
[ | tally |
tally := promise value.
Smalltalk isMorphic
ifTrue: [ Project current addDeferredUIMessage: [ self dumpTallyOnTranscript: tally ] ]
ifFalse: [ [ Transcript open ] forkAt: Processor userSchedulingPriority.
[ (Delay forSeconds: 1) wait.
self dumpTallyOnTranscript: tally ] forkAt: Processor userSchedulingPriority.]
] fork.!
----- Method: ProcessBrowser class>>terminateProcess: (in category 'process control') -----
terminateProcess: aProcess
aProcess ifNotNil: [
self suspendedProcesses
removeKey: aProcess
ifAbsent: [].
aProcess terminate
].
!
----- Method: ProcessBrowser class>>unload (in category 'class initialization') -----
unload
"Unload the receiver from global registries"
self environment at: #Flaps ifPresent: [:cl |
cl unregisterQuadsWithReceiver: self] !
----- Method: ProcessBrowser class>>unregisterWellKnownProcess: (in category 'process control') -----
unregisterWellKnownProcess: aProcess
"Remove the first registry entry that matches aProcess. Use
with caution if more than one registry entry may match aProcess."
"self unregisterWellKnownProcess: Smalltalk lowSpaceWatcherProcess"
| entry |
entry := WellKnownProcesses
detect: [:e | e key value == aProcess]
ifNone: [^ self].
WellKnownProcesses remove: entry!
----- Method: ProcessBrowser class>>wasProcessSuspendedByProcessBrowser: (in category 'process control') -----
wasProcessSuspendedByProcessBrowser: aProcess
^self suspendedProcesses includesKey: aProcess!
----- Method: ProcessBrowser>>asPrototypeInWindow (in category 'views') -----
asPrototypeInWindow
"Create a pluggable version of me, answer a window"
| window aTextMorph |
window := (SystemWindow labelled: 'later') model: self.
window
addMorph: ((PluggableListMorph
on: self
list: #processNameList
selected: #processListIndex
changeSelected: #processListIndex:
menu: #processListMenu:
keystroke: #processListKey:from:)
enableDragNDrop: false)
frame: (0 @ 0 extent: 0.5 @ 0.5).
window
addMorph: ((PluggableListMorph
on: self
list: #stackNameList
selected: #stackListIndex
changeSelected: #stackListIndex:
menu: #stackListMenu:
keystroke: #stackListKey:from:)
enableDragNDrop: false)
frame: (0.5 @ 0.0 extent: 0.5 @ 0.5).
aTextMorph := PluggableTextMorph
on: self
text: #selectedMethod
accept: nil
readSelection: nil
menu: nil.
window
addMorph: aTextMorph
frame: (0 @ 0.5 corner: 1 @ 1).
window setLabel: 'Process Browser'.
^ window!
----- Method: ProcessBrowser>>browseContext (in category 'stack list') -----
browseContext
selectedContext
ifNil: [^ self].
Browser newOnClass: self selectedClass selector: self selectedSelector!
----- Method: ProcessBrowser>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"Create a pluggable version of me, answer a window"
| windowSpec listSpec textSpec |
windowSpec := builder pluggableWindowSpec new.
windowSpec model: self.
windowSpec label: 'Process Browser'.
windowSpec children: OrderedCollection new.
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #processNameList;
getIndex: #processListIndex;
setIndex: #processListIndex:;
menu: #processListMenu:;
keyPress: #processListKey:from:;
frame: (0 @ 0 extent: 0.5 @ 0.5).
windowSpec children add: listSpec.
listSpec := builder pluggableListSpec new.
listSpec
model: self;
list: #stackNameList;
getIndex: #stackListIndex;
setIndex: #stackListIndex:;
menu: #stackListMenu:;
keyPress: #stackListKey:from:;
frame: (0.5 @ 0.0 extent: 0.5 @ 0.5).
windowSpec children add: listSpec.
textSpec := builder pluggableTextSpec new.
textSpec
model: self;
getText: #selectedMethod;
setText: nil;
selection: nil;
menu: nil;
frame: (0 @ 0.5 corner: 1 @ 1).
windowSpec children add: textSpec.
^builder build: windowSpec!
----- Method: ProcessBrowser>>changePriority (in category 'process actions') -----
changePriority
| str newPriority nameAndRules |
nameAndRules := self nameAndRulesForSelectedProcess.
nameAndRules third
ifFalse: [self inform: 'Nope, won''t change priority of ' , nameAndRules first.
^ self].
str := UIManager default
request: 'New priority'
initialAnswer: selectedProcess priority asString.
newPriority := str asNumber asInteger.
newPriority
ifNil: [^ self].
(newPriority < 1
or: [newPriority > Processor highestPriority])
ifTrue: [self inform: 'Bad priority'.
^ self].
self class setProcess: selectedProcess toPriority: newPriority.
self updateProcessList!
----- Method: ProcessBrowser>>changeStackListTo: (in category 'stack list') -----
changeStackListTo: aCollection
stackList := aCollection.
self changed: #stackNameList.
self stackListIndex: 0!
----- Method: ProcessBrowser>>chasePointers (in category 'process actions') -----
chasePointers
| saved |
selectedProcess
ifNil: [^ self].
saved := selectedProcess.
[selectedProcess := nil.
(Smalltalk includesKey: #PointerFinder)
ifTrue: [PointerFinder on: saved]
ifFalse: [self inspectPointers]]
ensure: [selectedProcess := saved]!
----- Method: ProcessBrowser>>debugProcess (in category 'process actions') -----
debugProcess
| nameAndRules |
nameAndRules := self nameAndRulesForSelectedProcess.
nameAndRules third
ifFalse: [self inform: 'Nope, won''t debug ' , nameAndRules first.
^ self].
self class debugProcess: selectedProcess.!
----- Method: ProcessBrowser>>exploreContext (in category 'stack list') -----
exploreContext
selectedContext explore!
----- Method: ProcessBrowser>>exploreProcess (in category 'process list') -----
exploreProcess
selectedProcess explore!
----- Method: ProcessBrowser>>exploreReceiver (in category 'stack list') -----
exploreReceiver
selectedContext ifNotNil: [ selectedContext receiver explore ]!
----- Method: ProcessBrowser>>findContext (in category 'process list') -----
findContext
| initialProcessIndex initialStackIndex found |
initialProcessIndex := self processListIndex.
initialStackIndex := self stackListIndex.
searchString := UIManager default
request: 'Enter a string to search for in the process stack lists'
initialAnswer: searchString.
searchString isEmpty
ifTrue: [^ false].
self processListIndex: 1.
self stackListIndex: 1.
found := self nextContext.
found
ifFalse: [self processListIndex: initialProcessIndex.
self stackListIndex: initialStackIndex].
^ found!
----- Method: ProcessBrowser>>hasView (in category 'views') -----
hasView
^self dependents isEmptyOrNil not!
----- Method: ProcessBrowser>>initialize (in category 'initialize-release') -----
initialize
methodText := ''.
stackListIndex := 0.
searchString := ''.
lastUpdate := 0.
startedCPUWatcher := Preferences cpuWatcherEnabled and: [ self startCPUWatcher ].
self updateProcessList; processListIndex: 1.
Browsers add: self!
----- Method: ProcessBrowser>>inspectContext (in category 'stack list') -----
inspectContext
selectedContext inspect!
----- Method: ProcessBrowser>>inspectPointers (in category 'process actions') -----
inspectPointers
| tc pointers |
selectedProcess ifNil: [^self].
tc := thisContext.
pointers := PointerFinder pointersTo: selectedProcess
except: {
self processList.
tc.
self}.
pointers isEmpty ifTrue: [^self].
OrderedCollectionInspector
openOn: pointers
withEvalPane: false
withLabel: 'Objects pointing to ' , selectedProcess browserPrintString!
----- Method: ProcessBrowser>>inspectProcess (in category 'process list') -----
inspectProcess
selectedProcess inspect!
----- Method: ProcessBrowser>>inspectReceiver (in category 'stack list') -----
inspectReceiver
selectedContext
ifNotNil: [selectedContext receiver inspect]!
----- Method: ProcessBrowser>>isAutoUpdating (in category 'updating') -----
isAutoUpdating
^autoUpdateProcess notNil and: [ autoUpdateProcess isSuspended not ]!
----- Method: ProcessBrowser>>isAutoUpdatingPaused (in category 'updating') -----
isAutoUpdatingPaused
^autoUpdateProcess notNil and: [ autoUpdateProcess isSuspended ]!
----- Method: ProcessBrowser>>messageTally (in category 'stack list') -----
messageTally
| secString secs |
secString := UIManager default request: 'Profile for how many seconds?' initialAnswer: '4'.
secs := secString asNumber asInteger.
(secs isNil
or: [secs isZero])
ifTrue: [^ self].
[ TimeProfileBrowser spyOnProcess: selectedProcess forMilliseconds: secs * 1000 ] forkAt: selectedProcess priority + 1.!
----- Method: ProcessBrowser>>moreStack (in category 'stack list') -----
moreStack
self updateStackList: 2000!
----- Method: ProcessBrowser>>nameAndRulesFor: (in category 'process actions') -----
nameAndRulesFor: aProcess
"Answer a nickname and two flags: allow-stop, and allow-debug"
aProcess == autoUpdateProcess ifTrue: [ ^{'my auto-update process'. true. true} ].
^self class nameAndRulesFor: aProcess
!
----- Method: ProcessBrowser>>nameAndRulesForSelectedProcess (in category 'process actions') -----
nameAndRulesForSelectedProcess
"Answer a nickname and two flags: allow-stop, and allow-debug"
^self nameAndRulesFor: selectedProcess!
----- Method: ProcessBrowser>>nextContext (in category 'process list') -----
nextContext
| initialProcessIndex initialStackIndex found |
searchString isEmpty ifTrue: [ ^false ].
initialProcessIndex := self processListIndex.
initialStackIndex := self stackListIndex.
found := false.
initialProcessIndex
to: self processList size
do: [:pi | found
ifFalse: [self processListIndex: pi.
self stackNameList
withIndexDo: [:name :si | (found not
and: [pi ~= initialProcessIndex
or: [si > initialStackIndex]])
ifTrue: [(name includesSubString: searchString)
ifTrue: [self stackListIndex: si.
found := true]]]]].
found
ifFalse: [self processListIndex: initialProcessIndex.
self stackListIndex: initialStackIndex].
^ found!
----- Method: ProcessBrowser>>notify:at:in: (in category 'process list') -----
notify: errorString at: location in: aStream
"A syntax error happened when I was trying to highlight my pc.
Raise a signal so that it can be ignored."
Warning signal: 'syntax error'!
----- Method: ProcessBrowser>>openAsMVC (in category 'views') -----
openAsMVC
"Create a pluggable version of me, answer a window"
| window processListView stackListView methodTextView |
window := StandardSystemView new model: self controller: StandardSystemController new.
window borderWidth: 1.
processListView := PluggableListView
on: self
list: #processNameList
selected: #processListIndex
changeSelected: #processListIndex:
menu: #processListMenu:
keystroke: #processListKey:from:.
processListView
window: (0 @ 0 extent: 300 @ 200).
window addSubView: processListView.
stackListView := PluggableListView
on: self
list: #stackNameList
selected: #stackListIndex
changeSelected: #stackListIndex:
menu: #stackListMenu:
keystroke: #stackListKey:from:.
stackListView
window: (300 @ 0 extent: 300 @ 200).
window addSubView: stackListView toRightOf: processListView.
methodTextView := PluggableTextView
on: self
text: #selectedMethod
accept: nil
readSelection: nil
menu: nil.
methodTextView askBeforeDiscardingEdits: false.
methodTextView
window: (0 @ 200 corner: 600 @ 400).
window addSubView: methodTextView below: processListView.
window setUpdatablePanesFrom: #(#processNameList #stackNameList ).
window label: 'Process Browser'.
window minimumSize: 300 @ 200.
window subViews
do: [:each | each controller].
window controller open.
startedCPUWatcher ifTrue: [ self setUpdateCallbackAfter: 7 ].
^ window!
----- Method: ProcessBrowser>>openAsMorph (in category 'views') -----
openAsMorph
"Create a pluggable version of me, answer a window"
| window aTextMorph |
window := (SystemWindow labelled: 'later')
model: self.
window
addMorph: ((PluggableListMorph
on: self
list: #processNameList
selected: #processListIndex
changeSelected: #processListIndex:
menu: #processListMenu:
keystroke: #processListKey:from:)
enableDragNDrop: false)
frame: (0 @ 0 extent: 0.5 @ 0.5).
window
addMorph: ((PluggableListMorph
on: self
list: #stackNameList
selected: #stackListIndex
changeSelected: #stackListIndex:
menu: #stackListMenu:
keystroke: #stackListKey:from:)
enableDragNDrop: false)
frame: (0.5 @ 0.0 extent: 0.5 @ 0.5).
aTextMorph := PluggableTextMorph
on: self
text: #selectedMethod
accept: nil
readSelection: nil
menu: nil.
aTextMorph askBeforeDiscardingEdits: false.
window
addMorph: aTextMorph
frame: (0 @ 0.5 corner: 1 @ 1).
window setUpdatablePanesFrom: #(#processNameList #stackNameList ).
(window setLabel: 'Process Browser') openInWorld.
startedCPUWatcher ifTrue: [ self setUpdateCallbackAfter: 7 ].
^ window!
----- Method: ProcessBrowser>>pauseAutoUpdate (in category 'updating') -----
pauseAutoUpdate
self isAutoUpdating
ifTrue: [ autoUpdateProcess suspend ].
self updateProcessList!
----- Method: ProcessBrowser>>pcRange (in category 'stack list') -----
pcRange
"Answer the indices in the source code for the method corresponding to
the selected context's program counter value."
(selectedContext isNil or: [methodText isEmptyOrNil])
ifTrue: [^ 1 to: 0].
^selectedContext debuggerMap
rangeForPC: (selectedContext pc ifNotNilDo: [:pc| pc] ifNil: [selectedContext method endPC])
contextIsActiveContext: stackListIndex = 1!
----- Method: ProcessBrowser>>perform:orSendTo: (in category 'message handling') -----
perform: selector orSendTo: otherTarget
"Selector was just chosen from a menu by a user. If can respond, then
perform it on myself. If not, send it to otherTarget, presumably the
editPane from which the menu was invoked."
(self respondsTo: selector)
ifTrue: [^ self perform: selector]
ifFalse: [^ super perform: selector orSendTo: otherTarget]!
----- Method: ProcessBrowser>>prettyNameForProcess: (in category 'process list') -----
prettyNameForProcess: aProcess
| nameAndRules |
aProcess ifNil: [ ^'<nil>' ].
nameAndRules := self nameAndRulesFor: aProcess.
^ aProcess browserPrintStringWith: nameAndRules first!
----- Method: ProcessBrowser>>processList (in category 'accessing') -----
processList
^ processList!
----- Method: ProcessBrowser>>processListIndex (in category 'accessing') -----
processListIndex
^ processListIndex!
----- Method: ProcessBrowser>>processListIndex: (in category 'accessing') -----
processListIndex: index
processListIndex := index.
selectedProcess := processList
at: index
ifAbsent: [].
self updateStackList.
self changed: #processListIndex.!
----- Method: ProcessBrowser>>processListKey:from: (in category 'process list') -----
processListKey: aKey from: aView
^ aKey caseOf: {
[$i] -> [self inspectProcess].
[$I] -> [self exploreProcess].
[$c] -> [self chasePointers].
[$P] -> [self inspectPointers].
[$t] -> [self terminateProcess].
[$r] -> [self resumeProcess].
[$s] -> [self suspendProcess].
[$d] -> [self debugProcess].
[$p] -> [self changePriority].
[$m] -> [self messageTally].
[$f] -> [self findContext].
[$g] -> [self nextContext].
[$a] -> [self toggleAutoUpdate].
[$u] -> [self updateProcessList].
[$S] -> [self signalSemaphore].
[$k] -> [self moreStack]}
otherwise: [self arrowKey: aKey from: aView]!
----- Method: ProcessBrowser>>processListMenu: (in category 'process list') -----
processListMenu: menu
| pw |
selectedProcess
ifNotNil: [| nameAndRules |
nameAndRules := self nameAndRulesForSelectedProcess.
menu addList: {{'inspect (i)'. #inspectProcess}. {'explore (I)'. #exploreProcess}. {'inspect Pointers (P)'. #inspectPointers}}.
(Smalltalk includesKey: #PointerFinder)
ifTrue: [ menu add: 'chase pointers (c)' action: #chasePointers. ].
nameAndRules second
ifTrue: [menu add: 'terminate (t)' action: #terminateProcess.
selectedProcess isSuspended
ifTrue: [menu add: 'resume (r)' action: #resumeProcess]
ifFalse: [menu add: 'suspend (s)' action: #suspendProcess]].
nameAndRules third
ifTrue: [menu addList: {{'change priority (p)'. #changePriority}. {'debug (d)'. #debugProcess}}].
menu addList: {{'profile messages (m)'. #messageTally}}.
(selectedProcess suspendingList isKindOf: Semaphore)
ifTrue: [menu add: 'signal Semaphore (S)' action: #signalSemaphore].
menu add: 'full stack (k)' action: #moreStack.
menu addLine].
menu addList: {{'find context... (f)'. #findContext}. {'find again (g)'. #nextContext}}.
menu addLine.
menu
add: (self isAutoUpdating
ifTrue: ['turn off auto-update (a)']
ifFalse: ['turn on auto-update (a)'])
action: #toggleAutoUpdate.
menu add: 'update list (u)' action: #updateProcessList.
pw := Smalltalk at: #CPUWatcher ifAbsent: [].
pw ifNotNil: [
menu addLine.
pw isMonitoring
ifTrue: [ menu add: 'stop CPUWatcher' action: #stopCPUWatcher ]
ifFalse: [ menu add: 'start CPUWatcher' action: #startCPUWatcher ]
].
^ menu!
----- Method: ProcessBrowser>>processNameList (in category 'process list') -----
processNameList
"since processList is a WeakArray, we have to strengthen the result"
| pw tally |
pw := Smalltalk at: #CPUWatcher ifAbsent: [ ].
tally := pw ifNotNil: [ pw current ifNotNil: [ pw current tally ] ].
^ (processList asOrderedCollection
copyWithout: nil)
collect: [:each | | percent |
percent := tally
ifNotNil: [ ((((tally occurrencesOf: each) * 100.0 / tally size) roundTo: 1)
asString padded: #left to: 2 with: $ ), '% ' ]
ifNil: [ '' ].
percent, (self prettyNameForProcess: each)
] !
----- Method: ProcessBrowser>>resumeProcess (in category 'process actions') -----
resumeProcess
selectedProcess
ifNil: [^ self].
self class resumeProcess: selectedProcess.
self updateProcessList!
----- Method: ProcessBrowser>>selectedClass (in category 'accessing') -----
selectedClass
"Answer the class in which the currently selected context's method was
found."
^ selectedClass
ifNil: [selectedClass := selectedContext receiver
ifNil: [selectedSelector := selectedContext method selector.
selectedContext method methodClass]
ifNotNil: [selectedContext methodClass]]!
----- Method: ProcessBrowser>>selectedMethod (in category 'accessing') -----
selectedMethod
^ methodText ifNil: [methodText := selectedContext
ifNil: ['']
ifNotNil: [| pcRange |
methodText := [ selectedContext sourceCode ]
ifError: [ :err :rcvr | 'error getting method text' ].
pcRange := self pcRange.
methodText asText
addAttribute: TextColor red
from: pcRange first
to: pcRange last;
addAttribute: TextEmphasis bold
from: pcRange first
to: pcRange last]]!
----- Method: ProcessBrowser>>selectedSelector (in category 'accessing') -----
selectedSelector
"Answer the class in which the currently selected context's method was
found."
^ selectedSelector
ifNil: [selectedSelector := selectedContext receiver
ifNil: [selectedClass := selectedContext method methodClass
selectedContext method selector]
ifNotNil: [selectedContext selector]]!
----- Method: ProcessBrowser>>setUpdateCallbackAfter: (in category 'updating') -----
setUpdateCallbackAfter: seconds
| d |
d := Delay forSeconds: seconds.
[d wait.
d := nil.
Project current addDeferredUIMessage: [self updateProcessList]] fork!
----- Method: ProcessBrowser>>signalSemaphore (in category 'process actions') -----
signalSemaphore
(selectedProcess suspendingList isKindOf: Semaphore)
ifFalse: [^ self].
[selectedProcess suspendingList signal] fork.
(Delay forMilliseconds: 300) wait.
"Hate to make the UI wait, but it's convenient..."
self updateProcessList!
----- Method: ProcessBrowser>>stackList (in category 'accessing') -----
stackList
^ stackList!
----- Method: ProcessBrowser>>stackListIndex (in category 'accessing') -----
stackListIndex
^ stackListIndex!
----- Method: ProcessBrowser>>stackListIndex: (in category 'accessing') -----
stackListIndex: index
stackListIndex := index.
selectedContext := (stackList notNil
and: [index > 0]) ifTrue:
[stackList at: index ifAbsent: []].
selectedClass := nil.
selectedSelector := nil.
methodText := nil.
self changed: #stackListIndex.
self changed: #selectedMethod!
----- Method: ProcessBrowser>>stackListKey:from: (in category 'views') -----
stackListKey: aKey from: aView
^ aKey caseOf: {
[$c] -> [self inspectContext].
[$C] -> [self exploreContext].
[$i] -> [self inspectReceiver].
[$I] -> [self exploreReceiver].
[$b] -> [self browseContext]}
otherwise: [self arrowKey: aKey from: aView]!
----- Method: ProcessBrowser>>stackListMenu: (in category 'stack list') -----
stackListMenu: aMenu
| menu |
selectedContext
ifNil: [^ aMenu].
menu := aMenu
labels: 'inspect context (c)
explore context (C)
inspect receiver (i)
explore receiver (I)
browse (b)'
lines: #(2 4 )
selections: #(#inspectContext #exploreContext #inspectReceiver #exploreReceiver #browseContext ).
^ menu!
----- Method: ProcessBrowser>>stackNameList (in category 'stack list') -----
stackNameList
^ stackList
ifNil: [#()]
ifNotNil: [stackList
collect: [:each | each asString]]!
----- Method: ProcessBrowser>>startAutoUpdate (in category 'updating') -----
startAutoUpdate
self isAutoUpdatingPaused
ifTrue: [^ autoUpdateProcess resume].
self isAutoUpdating
ifFalse: [autoUpdateProcess := [[self hasView]
whileTrue: [(Delay forSeconds: 2) wait.
Project current addDeferredUIMessage: [self updateProcessList]].
autoUpdateProcess := nil] fork].
self updateProcessList
!
----- Method: ProcessBrowser>>startCPUWatcher (in category 'initialize-release') -----
startCPUWatcher
"Answers whether I started the CPUWatcher"
| pw |
pw := Smalltalk at: #CPUWatcher ifAbsent: [ ^self ].
pw ifNotNil: [
pw isMonitoring ifFalse: [
pw startMonitoringPeriod: 5 rate: 100 threshold: 0.85.
self setUpdateCallbackAfter: 7.
^true
]
].
^false
!
----- Method: ProcessBrowser>>stopAutoUpdate (in category 'updating') -----
stopAutoUpdate
autoUpdateProcess ifNotNil: [
autoUpdateProcess terminate.
autoUpdateProcess := nil].
self updateProcessList!
----- Method: ProcessBrowser>>stopCPUWatcher (in category 'initialize-release') -----
stopCPUWatcher
| pw |
pw := Smalltalk at: #CPUWatcher ifAbsent: [ ^self ].
pw ifNotNil: [
pw stopMonitoring.
self updateProcessList.
startedCPUWatcher := false. "so a manual restart won't be killed later"
]
!
----- Method: ProcessBrowser>>suspendProcess (in category 'process actions') -----
suspendProcess
| nameAndRules |
selectedProcess isSuspended
ifTrue: [^ self].
nameAndRules := self nameAndRulesForSelectedProcess.
nameAndRules second
ifFalse: [self inform: 'Nope, won''t suspend ' , nameAndRules first.
^ self].
self class suspendProcess: selectedProcess.
self updateProcessList!
----- Method: ProcessBrowser>>terminateProcess (in category 'process actions') -----
terminateProcess
| nameAndRules |
nameAndRules := self nameAndRulesForSelectedProcess.
nameAndRules second
ifFalse: [self inform: 'Nope, won''t kill ' , nameAndRules first.
^ self].
self class terminateProcess: selectedProcess.
self updateProcessList!
----- Method: ProcessBrowser>>text (in category 'accessing') -----
text
^methodText!
----- Method: ProcessBrowser>>toggleAutoUpdate (in category 'updating') -----
toggleAutoUpdate
self isAutoUpdating
ifTrue: [ self stopAutoUpdate ]
ifFalse: [ self startAutoUpdate ].
!
----- Method: ProcessBrowser>>updateProcessList (in category 'process list') -----
updateProcessList
| oldSelectedProcess newIndex now |
now := Time millisecondClockValue.
now - lastUpdate < 500
ifTrue: [^ self].
"Don't update too fast"
lastUpdate := now.
oldSelectedProcess := selectedProcess.
processList := selectedProcess := selectedSelector := nil.
Smalltalk garbageCollectMost.
"lose defunct processes"
processList := Process allSubInstances
reject: [:each | each isTerminated].
processList := processList
sortBy: [:a :b | a priority >= b priority].
processList := WeakArray withAll: processList.
newIndex := processList
indexOf: oldSelectedProcess
ifAbsent: [0].
self changed: #processNameList.
self processListIndex: newIndex!
----- Method: ProcessBrowser>>updateStackList (in category 'stack list') -----
updateStackList
self updateStackList: 20!
----- Method: ProcessBrowser>>updateStackList: (in category 'stack list') -----
updateStackList: depth
| suspendedContext oldHighlight |
selectedProcess
ifNil: [^ self changeStackListTo: nil].
(stackList notNil and: [ stackListIndex > 0 ])
ifTrue: [oldHighlight := stackList at: stackListIndex].
selectedProcess == Processor activeProcess
ifTrue: [self
changeStackListTo: (thisContext stackOfSize: depth)]
ifFalse: [suspendedContext := selectedProcess suspendedContext.
suspendedContext
ifNil: [self changeStackListTo: nil]
ifNotNil: [self
changeStackListTo: (suspendedContext stackOfSize: depth)]].
self
stackListIndex: (stackList
ifNil: [0]
ifNotNil: [stackList indexOf: oldHighlight])!
----- Method: ProcessBrowser>>wasProcessSuspendedByProcessBrowser: (in category 'process actions') -----
wasProcessSuspendedByProcessBrowser: aProcess
^self class suspendedProcesses includesKey: aProcess!
----- Method: ProcessBrowser>>windowIsClosing (in category 'initialize-release') -----
windowIsClosing
startedCPUWatcher ifTrue: [ CPUWatcher stopMonitoring ]!
ObjectExplorerWrapper subclass: #PointerExplorerWrapper
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Explorer'!
!PointerExplorerWrapper commentStamp: 'avi 8/21/2004 19:58' prior: 0!
A subclass of ObjectExplorerWrapper for use with PointerExplorer. #contents is overridden to work backwards: it returns wrappers for the objects pointing to item rather than for the objects that item points to.!
----- Method: PointerExplorerWrapper>>contents (in category 'accessing') -----
contents
| objects |
objects := Utilities pointersTo: item except: (Array with: self with: model).
^(objects reject: [:ea | ea class = self class])
collect: [:ea| self class with: ea name: ea identityHash asString model: item]!
----- Method: PointerExplorerWrapper>>hasContents (in category 'testing') -----
hasContents
^true!
----- Method: Set>>inspectorClass (in category '*Tools-Inspector') -----
inspectorClass
"Answer the class of the inspector to be used on the receiver. Called by inspect;
use basicInspect to get a normal (less useful) type of inspector."
^ SetInspector!
----- Method: ScreenController>>browseRecentLog (in category '*Tools') -----
browseRecentLog
"Open a changelist browser on changes submitted since the last snapshot. 1/17/96 sw"
ChangeList browseRecentLog!
----- Method: ScreenController>>chooseDirtyBrowser (in category '*Tools') -----
chooseDirtyBrowser
"Put up a list of browsers with unsubmitted edits and activate the one selected by the user, if any."
"ScheduledControllers screenController chooseDirtyBrowser"
ScheduledControllers findWindowSatisfying:
[:c | (c model isKindOf: Browser) and: [c model canDiscardEdits not]].
!
----- Method: ScreenController>>openChangeManager (in category '*Tools') -----
openChangeManager
"Open a dual change sorter. For looking at two change sets at once."
DualChangeSorter new open!
----- Method: ScreenController>>openFile (in category '*Tools') -----
openFile
FileList openFileDirectly!
----- Method: ScreenController>>openFileList (in category '*Tools') -----
openFileList
"Create and schedule a FileList view for specifying files to access."
FileList open!
----- Method: ScreenController>>openPackageBrowser (in category '*Tools') -----
openPackageBrowser
"Create and schedule a Browser view for browsing code."
PackagePaneBrowser openBrowser!
----- Method: ScreenController>>openSelectorBrowser (in category '*Tools') -----
openSelectorBrowser
"Create and schedule a selector fragment window."
SelectorBrowser new open!
----- Method: ScreenController>>openSimpleChangeSorter (in category '*Tools') -----
openSimpleChangeSorter
ChangeSorter new open!
ClassTestCase subclass: #FileListTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-FileList-Tests'!
----- Method: FileListTest>>checkIsServiceIsFromDummyTool: (in category 'private') -----
checkIsServiceIsFromDummyTool: service
^ (service instVarNamed: #provider) = DummyToolWorkingWithFileList
& service label = 'menu label'
& (service instVarNamed: #selector) = #loadAFileForTheDummyTool:!
----- Method: FileListTest>>setUp (in category 'initialize') -----
setUp
DummyToolWorkingWithFileList initialize.!
----- Method: FileListTest>>tearDown (in category 'initialize') -----
tearDown
DummyToolWorkingWithFileList unregister.!
----- Method: FileListTest>>testAllRegisteredServices (in category 'test') -----
testAllRegisteredServices
"(self selector: #testAllRegisteredServices) debug"
self shouldnt: [FileList allRegisteredServices] raise: Error!
----- Method: FileListTest>>testMenuReturned (in category 'test') -----
testMenuReturned
"(self selector: #testToolRegistered) debug"
self assert: (FileList registeredFileReaderClasses includes: DummyToolWorkingWithFileList)!
----- Method: FileListTest>>testService (in category 'test') -----
testService
"a stupid test to check that the class returns a service"
"(self selector: #testService) debug"
| service |
service := (DummyToolWorkingWithFileList fileReaderServicesForFile: 'abab.kkk' suffix: 'kkk') first.
self assert: (self checkIsServiceIsFromDummyTool: service).
service := (DummyToolWorkingWithFileList fileReaderServicesForFile: 'zkk.gz' suffix: 'gz').
self assert: service isEmpty!
----- Method: FileListTest>>testServicesForFileEnding (in category 'test') -----
testServicesForFileEnding
"(self selector: #testServicesForFileEnding) debug"
self assert: (((FileList new directory: FileDirectory default; yourself) itemsForFile: 'aaa.kkk') anySatisfy: [ :ea | self checkIsServiceIsFromDummyTool: ea ]).
!
----- Method: FileListTest>>testToolRegistered (in category 'test') -----
testToolRegistered
"(self selector: #testToolRegistered) debug"
self assert: (FileList registeredFileReaderClasses includes: DummyToolWorkingWithFileList)!
----- Method: FileListTest>>testToolRegisteredUsingInterface (in category 'test') -----
testToolRegisteredUsingInterface
"(self selector: #testToolRegisteredUsingInterface) debug"
self assert: (FileList isReaderNamedRegistered: #DummyToolWorkingWithFileList)!
ClassTestCase subclass: #MethodReferenceTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Browser-Tests'!
----- Method: MethodReferenceTest>>testEquals (in category 'Running') -----
testEquals
| aMethodReference anotherMethodReference |
aMethodReference := MethodReference new.
anotherMethodReference := MethodReference new.
"
two fresh instances should be equals between them"
self
should: [aMethodReference = anotherMethodReference].
self
should: [aMethodReference hash = anotherMethodReference hash].
"
two instances representing the same method (same class and
same selector) should be equals"
aMethodReference setStandardClass: String methodSymbol: #foo.
anotherMethodReference setStandardClass: String methodSymbol: #foo.
self
should: [aMethodReference = anotherMethodReference].
self
should: [aMethodReference hash = anotherMethodReference hash] !
----- Method: MethodReferenceTest>>testNotEquals (in category 'Running') -----
testNotEquals
| aMethodReference anotherMethodReference |
aMethodReference := MethodReference new.
anotherMethodReference := MethodReference new.
""
aMethodReference setStandardClass: String methodSymbol: #foo.
anotherMethodReference setStandardClass: String class methodSymbol: #foo.
"
differente classes, same selector -> no more equals"
self
shouldnt: [aMethodReference = anotherMethodReference].
"
same classes, diferente selector -> no more equals"
anotherMethodReference setStandardClass: String methodSymbol: #bar.
self
shouldnt: [aMethodReference = anotherMethodReference] !
More information about the Packages
mailing list